rev 625 - in trunk: . include/prothon pr/test src

SVN User <[email protected]> Tue, 15 Jun 2004 03:11:29 -0400
Newsgroups gmane.comp.lang.prothon.cvs
Message-ID <[email protected]>
Author: mark
Date: 2004-06-15 03:11:26 -0400 (Tue, 15 Jun 2004)
New Revision: 625

Modified:
   trunk/STATUS.txt
   trunk/include/prothon/prothon.h
   trunk/pr/test/test.pr
   trunk/src/builtins-file.c
Log:
most file methods are implemented, TextFile is started

Modified: trunk/STATUS.txt
===================================================================
--- trunk/STATUS.txt	2004-06-14 22:47:58 UTC (rev 624)
+++ trunk/STATUS.txt	2004-06-15 07:11:26 UTC (rev 625)
@@ -2,12 +2,17 @@
 ----------------------- TO-DO (highest priority first) ------------------------
 
 --- Complete File/Dir objects
+		set times, get times, perms, attrs
+		use 'b' in all files
 		http://www.prothon.org/pipermail/prothon-user/2004-April/000955.html
 --- TextFile object
 		http://www.prothon.org/pipermail/prothon-user/2004-June/001863.html
 --- support any object that supports file methods in stdxxx (duck std)
 		http://www.prothon.org/pipermail/prothon-user/2004-June/001769.html	
 
+--- Python regrets from Paul and print proposal
+	http://www.prothon.org/pipermail/prothon-user/2004-June/001970.html
+
 --- implement 1xc3fe ?
 
 --- check immutable bit for default param values or make new default object each use

Modified: trunk/include/prothon/prothon.h
===================================================================
--- trunk/include/prothon/prothon.h	2004-06-14 22:47:58 UTC (rev 624)
+++ trunk/include/prothon/prothon.h	2004-06-15 07:11:26 UTC (rev 625)
@@ -981,17 +981,25 @@
 	(var) = int2i64t(ist, index > 0 ? parms[((index)-1)*2+1] : list_item(ist, parms[1], -index)); }
 i64_t int2i64t(isp ist, obj_p self);
 
-#define STRING_PARAM(index, var)											\
+#define BYTES__PARAM(index, var)	/* obj_p var; */						\
+{	obj_p __pobj = ( index > 0 ? parms[((index)-1)*2+1] :					\
+                                 list_item(ist, parms[1], -index));			\
+	if (!has_proto(ist, __pobj, OBJ(BYTES_PROTO))) {						\
+		raise_exception( ist, OBJ(TYPE_EXC),								\
+				         "expected bytes in parameter " #index );			\
+		return NULL;														\
+	}   var = __pobj; }
+
+#define STRING_PARAM(index, var)	/* char* var; */						\
 {	if (!has_proto(ist, (index > 0 ? parms[((index)-1)*2+1] :               \
                    list_item(ist, parms[1], -index)), OBJ(STRING_PROTO))) {	\
 		raise_exception(ist, OBJ(TYPE_EXC),									\
 				"expected a string in parameter " #index);					\
 		return NULL;														\
 	} (var) = pr2c_strptr(ist, (index > 0 ? parms[((index)-1)*2+1] : list_item(ist, parms[1], -index))); }
-	
 
 #define FUNC___PARAM(index, var)	/* obj_p var; */						\
-{	if (!has_proto(ist, (index > 0 ? parms[((index)-1)*2+1] :			\
+{	if (!has_proto(ist, (index > 0 ? parms[((index)-1)*2+1] :			    \
 								list_item(ist, parms[1], -index)) ,			\
 							OBJ(FUNC_PROTO))) {								\
 		raise_exception(ist, OBJ(TYPE_EXC),									\

Modified: trunk/pr/test/test.pr
===================================================================
--- trunk/pr/test/test.pr	2004-06-14 22:47:58 UTC (rev 624)
+++ trunk/pr/test/test.pr	2004-06-15 07:11:26 UTC (rev 625)
@@ -1,5 +1,4 @@
 #!/usr/bin/env prothon
-
 /*
 d = Dir()
 print 'dir:',d, ' len:', len(d), d.path, d.name
@@ -7,12 +6,27 @@
 print '--- dirs ---'
 dirs = d.dirs()
 for i in d.numDirs():
-	print "{0:-40.40} {1:-30.30}".fmt(dirs[i].path, dirs[i].name)
+	print "{0:-60.60} {1:17.17}".fmt(dirs[i].path, dirs[i].name)
 print
 print '--- files ---'
 files = d.files()
 for i in d.numFiles():
-	print "{0:-40.40} {1:-30.30}".fmt(files[i].path, files[i].name)
+	print "{0:-60.60} {1:17.17}".fmt(files[i].path, files[i].name)
 print
+
+files[2].open('r')
+print files[2].read()
+files[2].close()
 */
-print TempDir()
\ No newline at end of file
+
+File("abc/test").delete()
+f = File("abc/test.txt")
+print f, f.exists?(), f.closed?()
+f.open('w')
+print f, f.exists?(), f.closed?()
+f.write(x"303132")
+print f, f.exists?(), f.closed?()
+f.close()
+print f, f.exists?(), f.closed?()
+
+

Modified: trunk/src/builtins-file.c
===================================================================
--- trunk/src/builtins-file.c	2004-06-14 22:47:58 UTC (rev 624)
+++ trunk/src/builtins-file.c	2004-06-15 07:11:26 UTC (rev 625)
@@ -69,27 +69,28 @@
 	int			 bufsize;	/* Size of our buffer */
 	apr_pool_t	*pool;		/* Pool associated with this file */
 	apr_int32_t	flags;		/* APR flags */
-} pr_file_t;
+} file_t;
 
-typedef pr_file_t* pr_file_p;
+typedef file_t* file_p;
 
-#define STREAM(obj)  (((pr_file_p)((obj)->data.ptr))->stream)
-#define BUFSIZE(obj) (((pr_file_p)((obj)->data.ptr))->bufsize)
-#define POOL(obj)    (((pr_file_p)((obj)->data.ptr))->pool)
-#define FLAGS(obj)   (((pr_file_p)((obj)->data.ptr))->flags)
+#define STREAM(obj)  (((file_p)((obj)->data.ptr))->stream)
+#define BUFSIZE(obj) (((file_p)((obj)->data.ptr))->bufsize)
+#define POOL(obj)    (((file_p)((obj)->data.ptr))->pool)
+#define FLAGS(obj)   (((file_p)((obj)->data.ptr))->flags)
 #define OPEN(obj)    (((obj) != File_OBJ) && STREAM(obj))
 
 MODULE_DECLARE(Dir);
 MODULE_DECLARE(TempDir);
 MODULE_DECLARE(File);
+MODULE_DECLARE(TextFile);
 
 //*************************** set_file_obj_data *******************************
 static void set_file_obj_data( isp ist, obj_p file_obj, obj_p filename, obj_p mode,
 						  	   apr_file_t *fp, int bufsize, apr_int32_t flags) {
     apr_status_t aprerr;
 	apr_pool_t *subpool = NULL;
-	pr_file_p filep;
-	char* temp, *p2, *p1, *p0, *p, *dirpath;
+	file_p filep;
+	char* temp, *p2, *p1, *p0, *p, *fullpath;
 	obj_p new_obj;
 
 	if (fp) 
@@ -99,29 +100,29 @@
 		IF_APR_ERR("out of memory in set_file_obj_data()") return;
 	}
 	temp = pr2c_strptr(ist, filename);
-	p0 = temp;
-	p2 = p0 + strlen(p0); 
-	if (*(p2-1) == '/') {
-		aprerr = apr_filepath_merge( &dirpath, NULL, temp, 
-                                     APR_FILEPATH_TRUENAME, subpool );
+	if (strcmp(temp, "<stdin>") && strcmp(temp, "<stdout>") && strcmp(temp, "<stderr>")) {
+		aprerr = apr_filepath_merge( &fullpath, NULL, temp, 
+									APR_FILEPATH_TRUENAME, subpool );
 		IF_APR_ERR("error in filepath_merge") return;
+	} else 
+		fullpath = temp;
 #ifdef WIN32
-	for(p=dirpath; *p; p++) *p = tolower(*p);
+	for(p=fullpath; *p; p++) *p = tolower(*p);
 #endif
-		set_attr(ist, file_obj, sym(ist, "path"), NEW_STRING(dirpath));
-		p0 = dirpath;
-		p2 = p0 + strlen(p0) - 1;
-	} else
-		set_attr(ist, file_obj, sym(ist, "path"), filename);
+	set_attr(ist, file_obj, sym(ist, "path"), NEW_STRING(fullpath));
+
+	p0 = fullpath;
+	p2 = p0 + strlen(p0); 
+	if (*(p2-1) == '/') p2--;
 	for (p1=p2-1; p1 >= p0 && *p1 != '/'; p1--);
 	if (*p1 != '/') new_obj = NEW_STRING("");
-	else new_obj = NEW_STRINGN(p1+1, p2-p1-1);
+	else            new_obj = NEW_STRINGN(p1+1, p2-p1-1);
 	set_attr(ist, file_obj, sym(ist, "name"), new_obj);
 
 	if (mode)
 		set_attr(ist, file_obj, sym(ist, "mode"), mode);
 
-	filep = pr_malloc(sizeof(pr_file_t));
+	filep = pr_malloc(sizeof(file_t));
 
 	if (bufsize < MIN_BUFSIZE)
 		bufsize = MIN_BUFSIZE;
@@ -171,7 +172,7 @@
     apr_status_t aprerr; 
 	const char *dirpath, *destpath = NULL;
 	char *filepath, *filepath2;
-	pr_file_p filep;
+	file_p filep;
 	obj_p uperms_obj = NULL, res = OBJ(NONE);
 	i64_t count = 0;
 	filep = self->data.ptr;
@@ -290,7 +291,7 @@
 DEF(Dir, exists_QUES, NULL) {
 	apr_finfo_t finfo;
 	char* path;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	filep = self->data.ptr;
 	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
@@ -303,7 +304,7 @@
     apr_status_t aprerr; 
 	char* path;
 	i32_t uperms;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	INT_32_PARAM(1, uperms);
 	filep = self->data.ptr;
@@ -317,7 +318,7 @@
     apr_status_t aprerr;
 	const char *rootpath, *filepath;
 	char *temp, *tempp, *respath;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	filep = self->data.ptr;
 	filepath = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
@@ -338,7 +339,7 @@
 DEF(Dir, isRoot_QUES, NULL) {
     apr_status_t aprerr; 
 	const char *rootpath, *filepath;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	filep = self->data.ptr;
 	filepath = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
@@ -381,7 +382,7 @@
 DEF(Dir, rename, FPARM1(name, NULL)) {
 	apr_status_t aprerr;
 	char *name, *dirpath, *p;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	STRING_PARAM(1, name);
 	filep = self->data.ptr;
@@ -399,7 +400,7 @@
 DEF(Dir, move, FPARM1(dir, NULL)) {
 	apr_status_t aprerr;
 	char *dirpath1, *dirpath2;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	CHECK_TYPE_EXC(parms[1], Dir_OBJ, Directory);
 	filep = self->data.ptr;
@@ -414,7 +415,7 @@
 	apr_status_t aprerr;
 	i32_t uperms;
 	char* path;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	CHECK_TYPE_EXC(parms[1], Dir_OBJ, Directory);
 	INT_32_PARAM(2, uperms);
@@ -429,7 +430,7 @@
 DEF(Dir, delete, NULL) {
 	apr_status_t aprerr;
 	char* path;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	filep = self->data.ptr;
 	dir_list(ist, self, -1, DLIST_DELETE, NULL, NULL, 0);
@@ -460,11 +461,11 @@
 	return self;
 }
 
-DEF(Dir, setFileAttrs, FPARM3(readOnly, OBJ(NONE), executable, OBJ(NONE), hidden, OBJ(NONE))) {
+DEF(Dir, setAttrs, FPARM3(readOnly, OBJ(NONE), executable, OBJ(NONE), hidden, OBJ(NONE))) {
 	apr_status_t aprerr;
 	char* path;
 	int attrs = 0, attrs_mask = 0;
-	pr_file_p filep;
+	file_p filep;
 	BIN_CONTENT_CHK(Dir);
 	filep = self->data.ptr;
 	if (parms[1] == OBJ(PR_TRUE)) { 
@@ -514,7 +515,7 @@
 	apr_temp_dir_get(&temp_dir_path, subpool);
 	set_file_obj_data( ist, TempDir_OBJ, NEW_STRING(temp_dir_path), 
 		               NULL, NULL, 0, APR_READ );
-	SET_TYPE_IF_EXC(TempDir_OBJ, TempDir_OBJ, DATA_TYPE_DATAPTR) return;
+	SET_TYPE_IF_EXC(Dir_OBJ, TempDir_OBJ, DATA_TYPE_DATAPTR) return;
 	MODULE_ADD_TO_BASE(TempDir);
 	set_unclonable(TempDir_OBJ);
 }
@@ -554,8 +555,8 @@
 	raise_exception(ist, OBJ(TYPE_EXC), "setUPerms() not allowed on TempDir");
 	return NULL;
 }
-DEF(TempDir, setFileAttrs, FPARM3(readOnly, OBJ(NONE), executable, OBJ(NONE), hidden, OBJ(NONE))) {
-	raise_exception(ist, OBJ(TYPE_EXC), "setFileAttrs() not allowed on TempDir");
+DEF(TempDir, setAttrs, FPARM3(readOnly, OBJ(NONE), executable, OBJ(NONE), hidden, OBJ(NONE))) {
+	raise_exception(ist, OBJ(TYPE_EXC), "setAttrs() not allowed on TempDir");
 	return NULL;
 }
 
@@ -571,7 +572,7 @@
 }
 
 DEF(File, init_, FPARM3( path,    NULL, 
-	                     mode,    NEW_STRING("r"),
+	                     mode,    NEW_STRING(""),
                          bufsize, NEW_INT(-1)   ) ) {
 	apr_file_t *f;
 	apr_status_t aprerr;
@@ -586,6 +587,15 @@
 	STRING_PARAM(2, mode);
 	INT_32_PARAM(3, bufsize);
 
+	if (strlen(mode) == 0) {
+		read_unlock(ist, self);
+		SET_TYPE_IF_EXC(File_OBJ, self, DATA_TYPE_DATAPTR) return NULL;
+		set_file_obj_data(ist, self, parms[1], NULL, NULL, 0, 0);
+		read_lock(ist, self);
+		self->unclonable = TRUE;
+		return OBJ(NONE);
+	}
+
 	if ((mode[0] != 'r' && mode[0] != 'w' && mode[0] != 'a') ||
 	    (mode[1] != '+' && mode[1] != '\0')) {
 		raise_exception(ist, OBJ(TYPE_EXC), "File mode `%s' is not valid", mode);
@@ -619,9 +629,6 @@
 		apr_pool_destroy(subpool);
 		return NULL;
 	}
-
-	/* set_file_obj_data(ist, ) modified the object, and thus calls write_lock.
-	 * We need to unlock in order to avoid a deadlock */
 	read_unlock(ist, self);
 	SET_TYPE_IF_EXC(File_OBJ, self, DATA_TYPE_DATAPTR) return NULL;
 	set_file_obj_data(ist, self, parms[1], parms[3], f, bufsize, flags);
@@ -639,6 +646,206 @@
 	return NEW_STRING(buf);
 }
 
+DEF(File, exists_QUES, NULL) {
+	apr_finfo_t finfo;
+	char *path;
+	file_p filep;
+	BIN_CONTENT_CHK(File);
+	filep = self->data.ptr;
+	if (OPEN(self)) return OBJ(PR_TRUE);
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	apr_stat(&finfo, path, APR_FINFO_MIN, filep->pool);
+	if (finfo.filetype == APR_REG)  return OBJ(PR_TRUE);
+	else                            return OBJ(PR_FALSE);
+	return OBJ(NONE);
+}
+
+DEF(File, rename, FPARM1(name, NULL)) {
+	apr_status_t aprerr;
+	char *name, *path, *p;
+	file_p filep;
+	BIN_CONTENT_CHK(File);
+	STRING_PARAM(1, name);
+	if (OPEN(self)) {
+		raise_exception( ist, OBJ(IO_EXC), "attempt to rename an open file");
+		return NULL;
+	}
+	for (p=name; *p; p++) 
+		if (*p == '/' || *p == '\\') {
+			raise_exception( ist, OBJ(VALUE_EXC), "slash chars not allowed in file name: \"%s\"", name);
+			return NULL;
+		}
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	filep = self->data.ptr;
+	aprerr = apr_file_rename(path, name, filep->pool);
+	IF_APR_ERR("renaming a file") return NULL;
+	return new_file_obj(ist, name);
+}
+
+DEF(File, move, FPARM1(dir, NULL)) {
+	apr_status_t aprerr;
+	obj_p res;
+	char *srcfilepath, *filename, *dstpath, *dstfilepath;
+	file_p filep;
+	BIN_CONTENT_CHK(File);
+	CHECK_TYPE_EXC(parms[1], Dir_OBJ, Directory);
+	if (OPEN(self)) {
+		raise_exception( ist, OBJ(IO_EXC), "attempt to move an open file");
+		return NULL;
+	}
+	filep = self->data.ptr;
+	srcfilepath = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	filename    = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "name")));
+	dstpath     = pr2c_strptr(ist, get_attr(ist, parms[1], sym(ist, "path")));
+	dstfilepath = pr_malloc(strlen(dstpath) + strlen(filename) + 1);
+	strcpy(dstfilepath, dstpath);  strcat(dstfilepath, filename);
+	aprerr = apr_file_rename(srcfilepath, dstfilepath, filep->pool);
+	IF_APR_ERR("moving a file") return NULL;
+	res = new_file_obj(ist, dstfilepath, NULL, NULL, 0, 0);
+	pr_free(dstfilepath);
+	return res;
+}
+
+DEF(File, copy, FPARM2(file, NULL, uperms, NEW_INT(APR_UREAD|APR_UWRITE|APR_UEXECUTE))) {
+	apr_status_t aprerr;
+	i32_t uperms;
+	char* path1, path2;
+	file_p filep;
+	BIN_CONTENT_CHK(File);
+	CHECK_TYPE_EXC(parms[1], File_OBJ, File);
+	INT_32_PARAM(2, uperms);
+	filep = self->data.ptr;
+	path1 = pr2c_strptr(ist, get_attr(ist,     self, sym(ist, "path")));
+	path2 = pr2c_strptr(ist, get_attr(ist, parms[1], sym(ist, "path")));
+	aprerr = apr_file_copy(path1, path2, uperms, filep->pool);
+	IF_APR_ERR("copying file") return NULL;
+	return parms[1];
+}
+
+DEF(File, delete, NULL) {
+	apr_status_t aprerr;
+	char* path;
+	file_p filep;
+	BIN_CONTENT_CHK(File);
+	if (OPEN(self)) {
+		raise_exception( ist, OBJ(IO_EXC), "attempt to delete an open file");
+		return NULL;
+	}
+	filep = self->data.ptr;
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	aprerr = apr_file_remove(path, filep->pool );
+	IF_APR_ERR("deleting file") return NULL;
+	return self;
+}
+
+DEF(File, eq__QUES, FORM_RPARAM) {
+	obj_p self_path, other_path;
+	BIN_CONTENT_CHK(File);
+	CHECK_TYPE_EXC(parms[1], File_OBJ, Directory);
+	self_path  = get_attr(ist,     self, sym(ist, "path"));
+	other_path = get_attr(ist, parms[1], sym(ist, "path"));
+	return call_func1(ist, self_path, SYM(EQ__QUES), other_path);
+}
+
+DEF(File, setUPerms, FPARM1(uperms, NEW_INT(APR_UREAD|APR_UWRITE|APR_UEXECUTE))) {
+	apr_status_t aprerr;
+	char* path;
+	int uperms;
+	BIN_CONTENT_CHK(File);
+	INT_32_PARAM(1, uperms);
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	aprerr = apr_file_perms_set(path, uperms);
+	IF_APR_ERR("setting unix permissions") return NULL;
+	return self;
+}
+
+DEF(File, setAttrs, FPARM3(readOnly, OBJ(NONE), executable, OBJ(NONE), hidden, OBJ(NONE))) {
+	apr_status_t aprerr;
+	char* path;
+	int attrs = 0, attrs_mask = 0;
+	file_p filep;
+	BIN_CONTENT_CHK(File);
+	filep = self->data.ptr;
+	if (parms[1] == OBJ(PR_TRUE)) { 
+		attrs |= APR_FILE_ATTR_READONLY;   attrs_mask |= APR_FILE_ATTR_READONLY;   }
+	if (parms[3] == OBJ(PR_TRUE)) { 
+		attrs |= APR_FILE_ATTR_EXECUTABLE; attrs_mask |= APR_FILE_ATTR_EXECUTABLE; }
+	if (parms[5] == OBJ(PR_TRUE)) {
+		attrs |= APR_FILE_ATTR_HIDDEN;     attrs_mask |= APR_FILE_ATTR_HIDDEN;     }
+	if (parms[1] == OBJ(PR_FALSE)) attrs_mask |= APR_FILE_ATTR_READONLY; 
+	if (parms[3] == OBJ(PR_FALSE)) attrs_mask |= APR_FILE_ATTR_EXECUTABLE;
+	if (parms[5] == OBJ(PR_FALSE)) attrs_mask |= APR_FILE_ATTR_HIDDEN;
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	aprerr = apr_file_attrs_set(path, attrs, attrs_mask, filep->pool );
+	IF_APR_ERR("setting file attributes") return NULL;
+	return self;
+}
+
+DEF(File, open, FPARM2( mode,    NEW_STRING(""),
+                        bufsize, NEW_INT(-1)   ) ) {
+	apr_file_t *f;
+	apr_status_t aprerr;
+	apr_int32_t flags = 0;
+	char err_buf[1024];
+	char *path, *mode;
+	int bufsize;	
+	file_p filep;
+
+	BIN_CONTENT_CHK(File);
+	STRING_PARAM(1, mode);
+	INT_32_PARAM(2, bufsize);
+	if (OPEN(self)) {
+		raise_exception(ist, OBJ(TYPE_EXC), "file is already open");
+		return NULL;
+	}
+	filep = self->data.ptr;
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+
+	if (strlen(mode) == 0) {
+		read_unlock(ist, self);
+		SET_TYPE_IF_EXC(File_OBJ, self, DATA_TYPE_DATAPTR) return NULL;
+		set_file_obj_data(ist, self, parms[1], NULL, NULL, 0, 0);
+		read_lock(ist, self);
+		self->unclonable = TRUE;
+		return OBJ(NONE);
+	}
+
+	if ((mode[0] != 'r' && mode[0] != 'w' && mode[0] != 'a') ||
+	    (mode[1] != '+' && mode[1] != '\0')) {
+		raise_exception(ist, OBJ(TYPE_EXC), "File mode `%s' is not valid", mode);
+		return NULL;
+	}
+
+	if (mode[0] == 'r') {
+		flags = APR_READ;
+		if (mode[1] == '+')
+			flags |= APR_WRITE;
+	} else if (mode[0] == 'w') {
+		flags = APR_WRITE | APR_CREATE | APR_TRUNCATE;
+		if (mode[1] == '+')
+			flags |= APR_READ;
+	} else if (mode[0] == 'a') {
+		flags = APR_WRITE | APR_CREATE | APR_APPEND;
+		if (mode[1] == '+')
+			flags |= APR_READ;
+	}
+
+	aprerr = apr_file_open(&f, path, flags, APR_OS_DEFAULT, filep->pool);
+	if (aprerr != APR_SUCCESS) {
+		raise_exception(ist, OBJ(IO_EXC), "Unable to open path %s: %s",
+				path, apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+		return NULL;
+	}
+
+	if (bufsize < MIN_BUFSIZE)
+		bufsize = MIN_BUFSIZE;
+
+	filep->bufsize = bufsize;
+	filep->stream  = f;
+	filep->flags = flags;
+	return OBJ(NONE);
+}
+
 DEF(File, close, NULL) {
 	BIN_CONTENT_CHK(File);
 	if (OPEN(self)) {
@@ -675,31 +882,336 @@
 	}
 	return OBJ(NONE);
 }
-#if 0
-DEF(File, fileNo, NULL) { 
+DEF(File, read, FPARM1(size, NEW_INT(-1))) {
+	int readall = FALSE;
+	size_t size, total_read=0, num_read;
+	obj_p byt_obj;
+	byt_p obj_str;
+	u8_t* buf;
+	apr_status_t aprerr;
+	char err_buf[1024];
+	i64_t size_in;
+
 	BIN_CONTENT_CHK(File);
+	INT_64_PARAM(1, size_in);
+
 	if (!has_proto(ist, self, File_OBJ)) {
-		raise_exception(ist, OBJ(TYPE_EXC), "fileNo can only be called on a File object");
+		raise_exception(ist, OBJ(TYPE_EXC), "read can only be called on a File object");
 		return NULL;
 	}
 	if (!OPEN(self)) {
-		raise_exception(ist, OBJ(IO_EXC), "fileNo called on closed file");
+		raise_exception(ist, OBJ(IO_EXC), "read called on closed file");
 		return NULL;
 	}
-	return NEW_INT(_fileno(STREAM(self)));
+	if (!(FLAGS(self) & APR_READ)) {
+		raise_exception(ist, OBJ(IO_EXC), "read called on file opened without read perms");
+		return NULL;
+	}
+	if (!size_in) return NEW_BYTES(NULL, 0);
+	if (size_in < 0) {
+		readall = TRUE;
+		size = BUFSIZE(self);
+	} else
+		size = (size_t) size_in;
+	byt_obj = NEW_OBJ(OBJ(BYTES_PROTO));
+	if (size < IMM_DATA_LEN) {
+		SET_TYPE_IF_EXC(OBJ(BYTES_PROTO), byt_obj, DATA_TYPE_IMMDATA) return NULL;
+		buf = byt_obj->data.bytes;
+	} else {
+		obj_str = obj_malloc(ist, OBJ(BYTES_PROTO), byt_obj, sizeof(str_t)+size+1);
+		buf = obj_str->byt;
+	}
+	num_read = size;
+	aprerr = apr_file_read(STREAM(self), buf, &num_read);
+	if (aprerr != APR_SUCCESS && aprerr != APR_EOF) {
+		raise_exception(ist, OBJ(IO_EXC), "%s",
+				apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+		return NULL;
+	}
+	total_read += num_read;
+	while(aprerr != APR_EOF && (readall || total_read < size)) {
+		size_t next_read = (readall ? size : size - total_read);
+		size_t new_len   =  total_read + next_read;
+		if (byt_obj->data_type == DATA_TYPE_IMMDATA && new_len >= IMM_DATA_LEN) {
+			obj_str = obj_malloc(ist, OBJ(BYTES_PROTO), byt_obj, sizeof(str_t)+new_len+1);
+			buf = obj_str->byt;
+		} else {
+			obj_str = obj_realloc(ist, byt_obj, sizeof(str_t)+new_len+1);
+			buf = obj_str->byt;
+		}
+		num_read = next_read;
+		aprerr = apr_file_read(STREAM(self), buf + total_read, &num_read);
+		if (aprerr != APR_SUCCESS && aprerr != APR_EOF) {
+			raise_exception(ist, OBJ(IO_EXC), "%s",
+					apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+			return NULL;
+		}
+		total_read += num_read;
+	}
+	if (byt_obj->data_type == DATA_TYPE_IMMDATA)
+		byt_obj->imm_data_len = (int) total_read;
+	else {
+		obj_str = obj_realloc(ist, byt_obj, sizeof(str_t)+total_read+1);
+		obj_str->len = (data_size_t) total_read;
+		buf = obj_str->byt;
+	}
+	buf[total_read] = 0;
+	byt_obj->immutable = TRUE;
+	return byt_obj;
 }
 
-DEF(File, isATty_QUES, NULL) { 
+DEF(File, seek, FPARM2( pos, NULL,
+                        how, NEW_INT(0) ) ) {
+	apr_off_t pos;
+	apr_seek_where_t origin;
+	apr_status_t aprerr;
+	char err_buf[1024];
+	i64_t pos_in, how;
+
 	BIN_CONTENT_CHK(File);
+	INT_64_PARAM(1, pos_in);
+	INT_64_PARAM(2, how);
+
+	if (!has_proto(ist, self, File_OBJ)) {
+		raise_exception(ist, OBJ(TYPE_EXC), "seek can only be called on a File object");
+		return NULL;
+	}
 	if (!OPEN(self)) {
-		raise_exception(ist, OBJ(IO_EXC), "isatty called on closed stream");
+		raise_exception(ist, OBJ(IO_EXC), "seek called on closed file");
 		return NULL;
 	}
-	if (_isatty(_fileno(STREAM(self))))  return OBJ(PR_TRUE); 
-	else						return OBJ(PR_FALSE); 
+	pos = (apr_off_t)pos_in;
+	switch(how) {
+		case 0: origin = APR_SET; break;
+		case 1: origin = APR_CUR; break;
+		case 2: origin = APR_END; break;
+		default :
+			raise_exception(ist, OBJ(TYPE_EXC), "file seek how parameter must be 0, 1, or 2");
+			return NULL;
+	}
+
+	aprerr = apr_file_seek(STREAM(self), origin, &pos);
+	if (aprerr != APR_SUCCESS) {
+		raise_exception(ist, OBJ(IO_EXC), "%s",
+				apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+		return NULL;
+	}
+	return OBJ(NONE);
 }
-#endif
-DEF(File, read, FPARM1(size, NEW_INT(-1))) {
+
+DEF(File, truncate, FPARM1(size, NULL)) {
+	apr_status_t aprerr;
+	char err_buf[1024];
+	apr_off_t size;
+	i64_t size_in;
+
+	BIN_CONTENT_CHK(File);
+	INT_64_PARAM(1, size_in);
+
+	if (!has_proto(ist, self, File_OBJ)) {
+		raise_exception(ist, OBJ(TYPE_EXC), "truncate can only be called on a File object");
+		return NULL;
+	}
+	if (!OPEN(self)) {
+		raise_exception(ist, OBJ(IO_EXC), "truncate called on closed file");
+		return NULL;
+	}
+
+	/* XXX Need a way to check for upper bounds portably. This can be
+	 * different on 64-bit systems, or systems which support LFS */
+	if (size_in < 0) {
+		raise_exception(ist, OBJ(TYPE_EXC), "file truncate size value out of range");
+		return NULL;
+	}
+	size = (apr_off_t)(size_in);
+
+	aprerr = apr_file_trunc(STREAM(self), size);
+	if(aprerr != APR_SUCCESS) {
+		raise_exception(ist, OBJ(IO_EXC), "%s",
+				apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+		return NULL;
+	}
+	return OBJ(NONE);
+}
+
+DEF(File, write, FPARM1(bytes, NULL)) {
+	apr_size_t size;
+	apr_status_t aprerr;
+	char err_buf[1024];
+	obj_p bytes;
+	u8_t* byts;
+
+	BIN_CONTENT_CHK(File);
+	BYTES__PARAM(1, bytes);
+	byts = bytes_ptr(bytes);
+	size = bytes_len(bytes);
+
+	if (!has_proto(ist, self, File_OBJ)) {
+		raise_exception(ist, OBJ(TYPE_EXC), "write can only be called from a File object");
+		return NULL;
+	}
+	if (!OPEN(self)) {
+		raise_exception(ist, OBJ(IO_EXC), "write called on closed file");
+		return NULL;
+	}
+	if (!(FLAGS(self) & APR_WRITE)) {
+		raise_exception(ist, OBJ(IO_EXC), "write called on file opened without write perms");
+		return NULL;
+	}
+	aprerr = apr_file_write(STREAM(self), byts, &size);
+	if (aprerr != APR_SUCCESS) {
+		raise_exception(ist, OBJ(IO_EXC), "%s",
+				apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+		return NULL;
+	}
+	return parms[1];
+}
+
+DEF(File, setStdOut, FORM_RPARAM) {
+	int i;
+	if (!has_proto(ist, parms[1], File_OBJ)) {
+		raise_exception(ist, OBJ(TYPE_EXC), "stdout can only be assigned to a file object");
+		return NULL;
+	}
+	su(i);  read_unlock(ist, self);
+	set_attr(ist, self, sym(ist, "stdout"), parms[1]);
+	un_su(i); read_lock(ist, self);
+	return parms[1];
+}
+
+#define __STDIN		0
+#define __STDOUT	1
+#define __STDERR	2
+
+static obj_p __open_std_file(int type, apr_pool_t *subpool)
+{
+	obj_p mode, file;
+	apr_file_t *fp;
+	apr_status_t aprerr;
+	apr_int32_t flags;
+
+	switch (type) {
+		case __STDIN:
+			aprerr = apr_file_open_stdin(&fp, subpool);
+			mode = NEW_STRING("r");
+			file = NEW_STRING("<stdin>");
+			flags = APR_READ;
+			break;
+
+		case __STDOUT:
+			aprerr = apr_file_open_stdout(&fp, subpool);
+			file = NEW_STRING("<stdout>");
+			mode = NEW_STRING("w");
+			flags = APR_WRITE;
+			break;
+
+		case __STDERR:
+			aprerr = apr_file_open_stderr(&fp, subpool);
+			file = NEW_STRING("<stderr>");
+			mode = NEW_STRING("w");
+			flags = APR_WRITE;
+			break;
+
+		default:
+			return NULL; /* XXX something stupid, do an error */
+	}
+
+	if (aprerr != APR_SUCCESS)
+		return NULL;
+
+	return new_file_obj(ist, file, mode, fp, MIN_BUFSIZE, flags);
+}
+
+DEF(File, objList_, FORM_RPARAM) {
+	return parms[1];
+}
+
+//*************************** MODULE TextFile *************************************
+
+MODULE_START(TextFile)
+{
+	TextFile_OBJ = NEW_OBJ(File_OBJ);
+	set_obj_doc(TextFile_OBJ, "TextFile object prototype");
+
+	MODULE_ADD_TO_BASE(TextFile);
+	TextFile_OBJ->unclonable = TRUE;
+}
+
+DEF(TextFile, init_, FPARM3( path,    NULL, 
+	                         mode,    NEW_STRING(""),
+                             bufsize, NEW_INT(-1)   ) ) {
+	apr_file_t *f;
+	apr_status_t aprerr;
+	apr_pool_t *subpool;
+	apr_int32_t flags = 0;
+	char err_buf[1024];
+	char *path, *mode;
+	int bufsize;
+	
+	BIN_EMPTY_CHK();
+	STRING_PARAM(1, path);
+	STRING_PARAM(2, mode);
+	INT_32_PARAM(3, bufsize);
+
+	if (strlen(mode) == 0) {
+		read_unlock(ist, self);
+		SET_TYPE_IF_EXC(TextFile_OBJ, self, DATA_TYPE_DATAPTR) return NULL;
+		set_file_obj_data(ist, self, parms[1], NULL, NULL, 0, 0);
+		read_lock(ist, self);
+		self->unclonable = TRUE;
+		return OBJ(NONE);
+	}
+
+	if ((mode[0] != 'r' && mode[0] != 'w' && mode[0] != 'a') ||
+	    (mode[1] != '+' && mode[1] != '\0')) {
+		raise_exception(ist, OBJ(TYPE_EXC), "TextFile mode `%s' is not valid", mode);
+		return NULL;
+	}
+
+	if (mode[0] == 'r') {
+		flags = APR_READ;
+		if (mode[1] == '+')
+			flags |= APR_WRITE;
+	} else if (mode[0] == 'w') {
+		flags = APR_WRITE | APR_CREATE | APR_TRUNCATE;
+		if (mode[1] == '+')
+			flags |= APR_READ;
+	} else if (mode[0] == 'a') {
+		flags = APR_WRITE | APR_CREATE | APR_APPEND;
+		if (mode[1] == '+')
+			flags |= APR_READ;
+	}
+
+	aprerr = apr_pool_create(&subpool, get_pr_head_pool());
+	if (aprerr != APR_SUCCESS) {
+		raise_exception(ist, OBJ(OUTOFMEMORY_EXC), "Out of memory opening files");
+		return NULL;
+	}
+
+	aprerr = apr_file_open(&f, path, flags, APR_OS_DEFAULT, subpool);
+	if (aprerr != APR_SUCCESS) {
+		raise_exception(ist, OBJ(IO_EXC), "Unable to open path %s: %s",
+				path, apr_strerror(aprerr, err_buf, sizeof(err_buf)));
+		apr_pool_destroy(subpool);
+		return NULL;
+	}
+	read_unlock(ist, self);
+	SET_TYPE_IF_EXC(TextFile_OBJ, self, DATA_TYPE_DATAPTR) return NULL;
+	set_file_obj_data(ist, self, parms[1], parms[3], f, bufsize, flags);
+	read_lock(ist, self);
+	self->unclonable = TRUE;
+	return OBJ(NONE);
+}
+
+DEF(TextFile, str_, NULL) {
+	char buf[300], *path;
+	BIN_STR_CHK(TextFile);
+	path = pr2c_strptr(ist, get_attr(ist, self, sym(ist, "path")));
+	apr_snprintf(buf, sizeof(buf), "<TextFile:'%s'>", path);
+	pr_free(path);
+	return NEW_STRING(buf);
+}
+DEF(TextFile, read, FPARM1(size, NEW_INT(-1))) {
 	int readall = FALSE;
 	size_t size, total_read=0, num_read;
 	obj_p byt_obj;
@@ -776,8 +1288,7 @@
 	byt_obj->immutable = TRUE;
 	return byt_obj;
 }
-
-DEF(File, readLine, FPARM1(size, NEW_INT(-1))) {
+DEF(TextFile, readLine, FPARM1(size, NEW_INT(-1))) {
 	int readall = FALSE;
 	size_t size, total_read;
 	obj_p byt_obj;
@@ -853,7 +1364,7 @@
 	return byt_obj;
 }
 
-DEF(File, readLines, FPARM1(size, NEW_INT(-1))) {
+DEF(TextFile, readLines, FPARM1(size, NEW_INT(-1))) {
 	int readall = FALSE;
 	size_t size, total_read=0, num_read;
 	obj_p byt_obj, list_obj;
@@ -937,90 +1448,7 @@
 	return list_obj;
 }
 
-DEF(File, seek, FPARM2( pos, NULL,
-                        how, NEW_INT(0) ) ) {
-	apr_off_t pos;
-	apr_seek_where_t origin;
-	apr_status_t aprerr;
-	char err_buf[1024];
-	i64_t pos_in, how;
-
-	BIN_CONTENT_CHK(File);
-	INT_64_PARAM(1, pos_in);
-	INT_64_PARAM(2, how);
-
-	if (!has_proto(ist, self, File_OBJ)) {
-		raise_exception(ist, OBJ(TYPE_EXC), "seek can only be called on a File object");
-		return NULL;
-	}
-	if (!OPEN(self)) {
-		raise_exception(ist, OBJ(IO_EXC), "seek called on closed file");
-		return NULL;
-	}
-	pos = (apr_off_t)pos_in;
-	switch(how) {
-		case 0: origin = APR_SET; break;
-		case 1: origin = APR_CUR; break;
-		case 2: origin = APR_END; break;
-		default :
-			raise_exception(ist, OBJ(TYPE_EXC), "file seek how parameter must be 0, 1, or 2");
-			return NULL;
-	}
-
-	aprerr = apr_file_seek(STREAM(self), origin, &pos);
-	if (aprerr != APR_SUCCESS) {
-		raise_exception(ist, OBJ(IO_EXC), "%s",
-				apr_strerror(aprerr, err_buf, sizeof(err_buf)));
-		return NULL;
-	}
-	return OBJ(NONE);
-}
-#if 0
-DEF(File, tell, NULL) {
-	BIN_CONTENT_CHK(File);
-	if (!OPEN(self)) {
-		raise_exception(ist, OBJ(IO_EXC), "tell called on closed file");
-		return NULL;
-	}
-	return NEW_INT(ftell(STREAM(self)));
-}
-#endif
-DEF(File, truncate, FPARM1(size, NULL)) {
-	apr_status_t aprerr;
-	char err_buf[1024];
-	apr_off_t size;
-	i64_t size_in;
-
-	BIN_CONTENT_CHK(File);
-	INT_64_PARAM(1, size_in);
-
-	if (!has_proto(ist, self, File_OBJ)) {
-		raise_exception(ist, OBJ(TYPE_EXC), "truncate can only be called on a File object");
-		return NULL;
-	}
-	if (!OPEN(self)) {
-		raise_exception(ist, OBJ(IO_EXC), "truncate called on closed file");
-		return NULL;
-	}
-
-	/* XXX Need a way to check for upper bounds portably. This can be
-	 * different on 64-bit systems, or systems which support LFS */
-	if (size_in < 0) {
-		raise_exception(ist, OBJ(TYPE_EXC), "file truncate size value out of range");
-		return NULL;
-	}
-	size = (apr_off_t)(size_in);
-
-	aprerr = apr_file_trunc(STREAM(self), size);
-	if(aprerr != APR_SUCCESS) {
-		raise_exception(ist, OBJ(IO_EXC), "%s",
-				apr_strerror(aprerr, err_buf, sizeof(err_buf)));
-		return NULL;
-	}
-	return OBJ(NONE);
-}
-
-DEF(File, write, FPARM1(str, NULL)) {
+DEF(TextFile, write, FPARM1(str, NULL)) {
 	apr_size_t size;
 	apr_status_t aprerr;
 	char err_buf[1024];
@@ -1052,7 +1480,7 @@
 	return parms[1];
 }
 
-DEF(File, writeLines, FPARM1(lst, NULL)) {
+DEF(TextFile, writeLines, FPARM1(lst, NULL)) {
 	char* str;
 	apr_size_t size;
 	apr_status_t aprerr;
@@ -1104,65 +1532,7 @@
 	return parms[1];
 }
 
-DEF(File, setStdOut, FORM_RPARAM) {
-	int i;
-	if (!has_proto(ist, parms[1], File_OBJ)) {
-		raise_exception(ist, OBJ(TYPE_EXC), "stdout can only be assigned to a file object");
-		return NULL;
-	}
-	su(i);  read_unlock(ist, self);
-	set_attr(ist, self, sym(ist, "stdout"), parms[1]);
-	un_su(i); read_lock(ist, self);
-	return parms[1];
-}
 
-#define __STDIN		0
-#define __STDOUT	1
-#define __STDERR	2
-
-static obj_p __open_std_file(int type, apr_pool_t *subpool)
-{
-	obj_p mode, file;
-	apr_file_t *fp;
-	apr_status_t aprerr;
-	apr_int32_t flags;
-
-	switch (type) {
-		case __STDIN:
-			aprerr = apr_file_open_stdin(&fp, subpool);
-			mode = NEW_STRING("r");
-			file = NEW_STRING("<stdin>");
-			flags = APR_READ;
-			break;
-
-		case __STDOUT:
-			aprerr = apr_file_open_stdout(&fp, subpool);
-			file = NEW_STRING("<stdout>");
-			mode = NEW_STRING("w");
-			flags = APR_WRITE;
-			break;
-
-		case __STDERR:
-			aprerr = apr_file_open_stderr(&fp, subpool);
-			file = NEW_STRING("<stderr>");
-			mode = NEW_STRING("w");
-			flags = APR_WRITE;
-			break;
-
-		default:
-			return NULL; /* XXX something stupid, do an error */
-	}
-
-	if (aprerr != APR_SUCCESS)
-		return NULL;
-
-	return new_file_obj(ist, file, mode, fp, MIN_BUFSIZE, flags);
-}
-
-DEF(File, objList_, FORM_RPARAM) {
-	return parms[1];
-}
-
 MAIN_MODULE_INIT(File) {
 	apr_pool_t *std_pool;
 	apr_status_t aprerr;
@@ -1187,7 +1557,7 @@
 	MODULE_ADD_SYM(Dir, delete);
 	MODULE_ADD_SYM(Dir, eq__QUES);
 	MODULE_ADD_SYM(Dir, setUPerms);
-	MODULE_ADD_SYM(Dir, setFileAttrs);
+	MODULE_ADD_SYM(Dir, setAttrs);
 	MODULE_ADD_SYM(Dir, objList_);
 
 	MODULE_SUB_INIT(TempDir);
@@ -1198,24 +1568,39 @@
 	MODULE_ADD_SYM(TempDir, move);
 	MODULE_ADD_SYM(TempDir, delete);
 	MODULE_ADD_SYM(TempDir, setUPerms);
-	MODULE_ADD_SYM(TempDir, setFileAttrs);
+	MODULE_ADD_SYM(TempDir, setAttrs);
 
 	MODULE_SUB_INIT(File);
 	MODULE_ADD_SYM(File, init_);
 	MODULE_ADD_SYM(File, str_);
+	MODULE_ADD_SYM(File, exists_QUES);
+	MODULE_ADD_SYM(File, rename);
+	MODULE_ADD_SYM(File, move);
+	MODULE_ADD_SYM(File, copy);
+	MODULE_ADD_SYM(File, delete);
+	MODULE_ADD_SYM(File, eq__QUES);
+	MODULE_ADD_SYM(File, setUPerms);
+	MODULE_ADD_SYM(File, setAttrs);
 	MODULE_ADD_SYM(File, read);
-	MODULE_ADD_SYM(File, readLine);
-	MODULE_ADD_SYM(File, readLines);
 	MODULE_ADD_SYM(File, write);
-	MODULE_ADD_SYM(File, writeLines);
 	MODULE_ADD_SYM(File, flush);
 	MODULE_ADD_SYM(File, seek);
 	MODULE_ADD_SYM(File, truncate);
+	MODULE_ADD_SYM(File, open);
 	MODULE_ADD_SYM(File, close);
 	MODULE_ADD_SYM(File, closed_QUES);
 	MODULE_ADD_SYM(File, setStdOut);
 	MODULE_ADD_SYM(File, objList_);
 
+	MODULE_SUB_INIT(TextFile);
+	MODULE_ADD_SYM(TextFile, init_);
+	MODULE_ADD_SYM(TextFile, str_);
+	MODULE_ADD_SYM(TextFile, read);
+	MODULE_ADD_SYM(TextFile, readLine);
+	MODULE_ADD_SYM(TextFile, readLines);
+	MODULE_ADD_SYM(TextFile, write);
+	MODULE_ADD_SYM(TextFile, writeLines);
+
 	/* Create base file objects for std{in,out,err} */
 	aprerr = apr_pool_create(&std_pool, get_pr_head_pool());
 	/* Need some kind of error return! */