Browse Source

Add File.update for writing to file without truncating (#6872)

Ben Morris 7 years ago
parent
commit
476d180548

+ 6 - 1
src/macro/eval/evalStdLib.ml

@@ -951,6 +951,10 @@ module StdFile = struct
 		create_out path binary [Open_append]
 	)
 
+	let update = vfun2 (fun path binary ->
+		create_out path binary [Open_rdonly; Open_wronly]
+	)
+
 	let getBytes = vfun1 (fun path ->
 		let path = decode_string path in
 		try encode_bytes (Bytes.unsafe_of_string (Std.input_file ~bin:true path)) with Sys_error _ -> exc_string ("Could not read file " ^ path)
@@ -2824,6 +2828,7 @@ let init_standard_library builtins =
 		"read",StdFile.read;
 		"saveBytes",StdFile.saveBytes;
 		"saveContent",StdFile.saveContent;
+		"update",StdFile.update;
 		"write",StdFile.write;
 	] [];
 	init_fields builtins (["sys";"io"],"FileInput") [] [
@@ -3067,4 +3072,4 @@ let init_standard_library builtins =
 	] [
 		"addChar",StdUtf8.addChar;
 		"toString",StdUtf8.toString;
-	]
+	]

+ 4 - 0
std/cpp/_std/sys/io/File.hx

@@ -59,6 +59,10 @@ class File {
 		return untyped new FileOutput(NativeFile.file_open(path,(if( binary ) "ab" else "a")));
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput {
+		return untyped new FileOutput(NativeFile.file_open(path,(if( binary ) "rb+" else "r+")));
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void {
 		var s = read(srcPath,true);
 		var d = write(dstPath,true);

+ 10 - 0
std/cs/_std/sys/io/File.hx

@@ -84,6 +84,16 @@ class File {
 		return new FileOutput(stream);
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput
+	{
+		#if std_buffer //standardize 4kb buffers
+		var stream = new cs.system.io.FileStream(path, OpenOrCreate, Write, ReadWrite, 4096);
+		#else
+		var stream = new cs.system.io.FileStream(path, OpenOrCreate, Write, ReadWrite);
+		#end
+		return new FileOutput(stream);
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void
 	{
 		cs.system.io.File.Copy(srcPath, dstPath, true);

+ 2 - 2
std/cs/io/NativeOutput.hx

@@ -63,14 +63,14 @@ class NativeOutput extends Output
 
 	public function seek( p : Int, pos : sys.io.FileSeek ) : Void
 	{
-		var p = switch(pos)
+		var pos = switch(pos)
 		{
 			case SeekBegin: cs.system.io.SeekOrigin.Begin;
 			case SeekCur: cs.system.io.SeekOrigin.Current;
 			case SeekEnd: cs.system.io.SeekOrigin.End;
 		};
 
-		stream.Seek(cast(p, Int64), p);
+		stream.Seek(cast(p, Int64), pos);
 	}
 
 	public function tell() : Int

+ 1 - 0
std/eval/_std/sys/io/File.hx

@@ -30,6 +30,7 @@ class File {
 	@:extern static public function read(path:String, binary:Bool = true):FileInput { return null; }
 	@:extern static public function write(path:String, binary:Bool = true):FileOutput { return null; }
 	@:extern static public function append(path:String, binary:Bool = true):FileOutput { return null; }
+	@:extern static public function update(path:String, binary:Bool = true):FileOutput { return null; }
 
 	static public function copy(srcPath:String, dstPath:String):Void {
 		var s = read(srcPath, true);

+ 6 - 0
std/hl/_std/sys/io/File.hx

@@ -73,6 +73,12 @@ typedef FileHandle = hl.Abstract<"hl_fdesc">;
 		return @:privateAccess new FileOutput(f);
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput {
+		var f = file_open(Sys.getPath(path),3,binary);
+		if( f == null ) throw new Sys.SysError("Can't open "+path+" for update");
+		return @:privateAccess new FileOutput(f);
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void {
 		var s = read(srcPath,true);
 		var d = write(dstPath,true);

+ 16 - 1
std/java/_std/sys/io/File.hx

@@ -103,6 +103,21 @@ class File {
 		}
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput
+	{
+		var f = new java.io.File(path);
+
+		try
+		{
+			var ra = new java.io.RandomAccessFile(f, "rw");
+			return new FileOutput( ra );
+		}
+		catch (e:Dynamic) //swallow checked exceptions
+		{
+			throw e;
+		}
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void
 	{
 		var r:FileInput = null;
@@ -123,4 +138,4 @@ class File {
 			throw e;
 		}
 	}
-}
+}

+ 3 - 0
std/lua/_std/sys/io/File.hx

@@ -38,7 +38,10 @@ class File {
 
 	public static function append( path : String, binary : Bool = true ) : FileOutput {
 		return new FileOutput(Io.open(path, "a"));
+	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput {
+		return new FileOutput(Io.open(path, binary ? "r+b" : "r+"));
 	}
 
 	public static function copy( srcPath : String, dstPath : String ) : Void {

+ 4 - 0
std/neko/_std/sys/io/File.hx

@@ -58,6 +58,10 @@ enum FileHandle {
 		return untyped new FileOutput(file_open(path.__s,(if( binary ) "ab" else "a").__s));
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput {
+		return untyped new FileOutput(file_open(path.__s,(if( binary ) "rb+" else "r+").__s));
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void {
 		var s = read(srcPath,true);
 		var d = write(dstPath,true);

+ 4 - 0
std/php/_std/sys/io/File.hx

@@ -56,6 +56,10 @@ import php.Global;
 		return untyped new FileOutput(fopen(path, binary ? "ab" : "a"));
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput {
+		return untyped new FileOutput(fopen(path, binary ? "rb+" : "r+"));
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void {
 		Global.copy(srcPath, dstPath);
 	}

+ 7 - 0
std/python/_std/sys/io/File.hx

@@ -77,6 +77,13 @@ class File {
 		return if (binary) IoTools.createFileOutputFromBytes(cast f) else IoTools.createFileOutputFromText(cast f);
 	}
 
+	public static function update( path : String, binary : Bool = true ) : FileOutput {
+		var mode = if (binary) "rb+" else "r+";
+		var f = python.lib.Builtins.open(path, mode, -1, null, null, binary ? null : "");
+
+		return if (binary) IoTools.createFileOutputFromBytes(cast f) else IoTools.createFileOutputFromText(cast f);
+	}
+
 	public static function copy( srcPath : String, dstPath : String ) : Void
 	{
 		return python.lib.Shutil.copy(srcPath, dstPath);

+ 7 - 0
std/sys/io/File.hx

@@ -109,6 +109,13 @@ extern class File {
 	**/
 	static function append( path : String, binary : Bool = true ) : FileOutput;
 
+	/**
+		Similar to `sys.io.File.append`. While `append` can only seek or write
+		starting from the end of the file's previous contents, `update` can
+		seek to any position, so the file's previous contents can be
+		selectively overwritten.
+	**/
+	static function update (path : String, binary : Bool = true ) : FileOutput;
 
 	/**
 		Copies the contents of the file specified by `srcPath` to the file

+ 35 - 0
tests/unit/src/unitstd/sys/io/File.unit.hx

@@ -0,0 +1,35 @@
+#if sys
+var filename = '.sys.io.file.testfile';
+if (sys.FileSystem.exists(filename)) sys.FileSystem.deleteFile(filename);
+
+// test file write
+var fw = sys.io.File.write(filename);
+fw.writeString("apple\n");
+fw.close();
+sys.io.File.getContent(filename) == "apple\n";
+
+// overwrite
+var fw = sys.io.File.write(filename);
+fw.writeString("banana\n");
+fw.close();
+sys.io.File.getContent(filename) == "banana\n";
+
+// test file append
+var fa = sys.io.File.append(filename);
+fa.writeString("apple\n");
+fa.close();
+sys.io.File.getContent(filename) == "banana\napple\n";
+
+// test file update
+var fu = sys.io.File.update(filename);
+fu.writeString("cherry\n");
+fu.close();
+sys.io.File.getContent(filename) == "cherry\napple\n";
+var fu = sys.io.File.update(filename);
+fu.seek(7, sys.io.FileSeek.SeekBegin);
+fu.writeString("banana\n");
+fu.close();
+sys.io.File.getContent(filename) == "cherry\nbanana\n";
+
+sys.FileSystem.deleteFile(filename);
+#end