From: Yusuke Endoh Date: 2011-05-31T22:20:47+09:00 Subject: [ruby-core:36630] [Ruby 1.9 - Feature #1081] add File::write() convenience method Issue #1081 has been updated by Yusuke Endoh. Hello, 2011/5/31 Shota Fukumori : > Patch has updated, Now File.binwrite accepts Hash for specifying options: > > https://gist.github.com/69c544ec245f3a07aabd Great! I tested your patch and noticed no problem. Roger and rubyspec folks, could you also check it? In terms of maintainability, I think that it would be better to define a common function for rb_io_s_write and rb_io_s_binwrite: http://www.atdot.net/sp/view/pw92ml diff --git a/io.c b/io.c index 4e1945c..25e0974 100644 --- a/io.c +++ b/io.c @@ -805,6 +805,12 @@ struct binwrite_arg {http://www.atdot.net/sp/view/pw92ml long length; }; +struct write_arg { + VALUE io; + VALUE str; + int nosync; +}; + static VALUE io_binwrite_string(VALUE arg) { @@ -8366,6 +8372,124 @@ rb_io_s_binread(int argc, VALUE *argv, VALUE io) return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io); } +static VALUE +io_s_write0(struct write_arg *arg) +{ + return io_write(arg->io,arg->str,arg->nosync); +} + +static VALUE +io_s_write(int argc, VALUE *argv, int binary) +{ + VALUE string, offset, opt; + struct foreach_arg arg; + struct write_arg warg; + + rb_scan_args(argc, argv, "21:", NULL, &string, &offset, &opt); + + if (NIL_P(opt)) opt = rb_hash_new(); + else opt = rb_hash_dup(opt); + + + if (NIL_P(rb_hash_aref(opt,sym_mode))) { + int mode = O_WRONLY|O_CREAT; +#ifdef O_BINARY + if (binary) mode |= O_BINARY; +#endif + if (NIL_P(offset)) mode |= O_TRUNC; + rb_hash_aset(opt,sym_mode,INT2NUM(mode)); + } + open_key_args(argc,argv,opt,&arg); + +#ifndef O_BINARY + if (binary) rb_io_binmode_m(arg.io); +#endif + + if (NIL_P(arg.io)) return Qnil; + if (!NIL_P(offset)) { + struct seek_arg sarg; + int state = 0; + sarg.io = arg.io; + sarg.offset = offset; + sarg.mode = SEEK_SET; + rb_protect(seek_before_access, (VALUE)&sarg, &state); + if (state) { + rb_io_close(arg.io); + rb_jump_tag(state); + } + } + + warg.io = arg.io; + warg.str = string; + warg.nosync = 0; + + return rb_ensure(io_s_write0, (VALUE)&warg, rb_io_close, arg.io); +} + +/* + * call-seq: + * IO.write(name, string, [offset] ) => fixnum + * IO.write(name, string, [offset], open_args ) => fixnum + * + * Opens the file, optionally seeks to the given offset, writes + * string, then returns the length written. + * write ensures the file is closed before returning. + * If offset is not given, the file is truncated. Otherwise, + * it is not truncated. + * + * If the last argument is a hash, it specifies option for internal + * open(). The key would be the following. open_args: is exclusive + * to others. + * + * encoding: string or encoding + * + * specifies encoding of the read string. encoding will be ignored + * if length is specified. + * + * mode: string + * + * specifies mode argument for open(). it should start with "w" or "a" or "r+" + * otherwise it would cause error. + * + * perm: fixnum + * + * specifies perm argument for open(). + * + * open_args: array of strings + * + * specifies arguments for open() as an array. + * + * IO.write("testfile", "0123456789") #=> "0123456789" + * IO.write("testfile", "0123456789", 20) #=> "This is line one\nThi0123456789two\nThis is line three\nAnd so on...\n" + */ + +static VALUE +rb_io_s_write(int argc, VALUE *argv, VALUE io) +{ + io_s_write(argc, argv, 0); +} + +/* + * call-seq: + * IO.binwrite(name, string, [offset] ) => fixnum + * + * Opens the file, optionally seeks to the given offset, write + * string then returns the length written. + * binwrite ensures the file is closed before returning. + * The open mode would be "wb:ASCII-8BIT". + * If offset is not given, the file is truncated. Otherwise, + * it is not truncated. + * + * IO.binwrite("testfile", "0123456789") #=> "0123456789" + * IO.binwrite("testfile", "0123456789", 20) #=> "This is line one\nThi0123456789two\nThis is line three\nAnd so on...\n" + */ + +static VALUE +rb_io_s_binwrite(int argc, VALUE *argv, VALUE io) +{ + io_s_write(argc, argv, 1); +} + struct copy_stream_struct { VALUE src; VALUE dst; @@ -10315,6 +10439,8 @@ Init_IO(void) rb_define_singleton_method(rb_cIO, "readlines", rb_io_s_readlines, -1); rb_define_singleton_method(rb_cIO, "read", rb_io_s_read, -1); rb_define_singleton_method(rb_cIO, "binread", rb_io_s_binread, -1); + rb_define_singleton_method(rb_cIO, "write", rb_io_s_write, -1); + rb_define_singleton_method(rb_cIO, "binwrite", rb_io_s_binwrite, -1); rb_define_singleton_method(rb_cIO, "select", rb_f_select, -1); rb_define_singleton_method(rb_cIO, "pipe", rb_io_s_pipe, -1); rb_define_singleton_method(rb_cIO, "try_convert", rb_io_s_try_convert, 1); diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index f919227..8aaecd1 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1861,4 +1861,61 @@ End end end + def test_s_write + t = Tempfile.new("foo") + path = t.path + t.close(false) + File.write(path, "foo\nbar\nbaz") + assert_equal("foo\nbar\nbaz", File.read(path)) + File.write(path, "FOO", 0) + assert_equal("FOO\nbar\nbaz", File.read(path)) + File.write(path, "BAR") + assert_equal("BAR", File.read(path)) + File.write(path, "\u{3042}", mode: "w", encoding: "EUC-JP") + assert_equal("\u{3042}".encode("EUC-JP"), File.read(path, encoding: "EUC-JP")) + File.delete t + assert_equal(6, File.write(path,'string',2)) + File.delete t + assert_raise(Errno::EINVAL) { File.write('/tmp/nonexisting','string',-2) } + assert_equal(6, File.write(path, 'string')) + assert_equal(3, File.write(path, 'sub', 1)) + assert_equal("ssubng", File.read(path)) + File.delete t + assert_equal(3, File.write(path, "foo", encoding: "UTF-8")) + File.delete t + assert_equal(3, File.write(path, "foo", 0, encoding: "UTF-8")) + assert_equal("foo", File.read(path)) + assert_equal(1, File.write(path, "f", 1, encoding: "UTF-8")) + assert_equal("ffo", File.read(path)) + File.delete t + assert_equal(1, File.write(path, "f", 1, encoding: "UTF-8")) + assert_equal("\00f", File.read(path)) + assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8")) + assert_equal("ff", File.read(path)) + t.unlink + end + + def test_s_binwrite + t = Tempfile.new("foo") + path = t.path + t.close(false) + File.binwrite(path, "foo\nbar\nbaz") + assert_equal("foo\nbar\nbaz", File.read(path)) + File.binwrite(path, "FOO", 0) + assert_equal("FOO\nbar\nbaz", File.read(path)) + File.binwrite(path, "BAR") + assert_equal("BAR", File.read(path)) + File.binwrite(path, "\u{3042}") + assert_equal("\u{3042}".force_encoding("ASCII-8BIT"), File.binread(path)) + File.delete t + assert_equal(6, File.binwrite(path,'string',2)) + File.delete t + assert_equal(6, File.binwrite(path, 'string')) + assert_equal(3, File.binwrite(path, 'sub', 1)) + assert_equal("ssubng", File.binread(path)) + assert_equal(6, File.size(path)) + assert_raise(Errno::EINVAL) { File.binwrite('/tmp/nonexisting','string',-2) } + assert_nothing_raised(TypeError) { File.binwrite(path, "string", mode: "w", encoding: "EUC-JP") } + t.unlink + end end -- Yusuke Endoh ---------------------------------------- Feature #1081: add File::write() convenience method http://redmine.ruby-lang.org/issues/1081 Author: Suraj Kurapati Status: Assigned Priority: Normal Assignee: Yusuke Endoh Category: core Target version: 1.9.3 =begin Please add a File::write() convenience method to the core Ruby API. Currently, it is easier to read whole files than to write them: # reading a whole file --- less effort text = File::read('foo.txt') # writing a whole file --- more effort File::open('foo.txt', 'wb') {|f| f.write 'ruby!' } This imbalance can be corrected by adding a File::write method, such as the following, to the core Ruby API: class File def self.write path, data, mode = 'wb' open(path, mode) {|f| f.write data } end end Thanks for your consideration. =end -- http://redmine.ruby-lang.org