diff --git a/io.c b/io.c index 4e1945c..fcbf1d6 100644 --- a/io.c +++ b/io.c @@ -805,6 +805,12 @@ struct binwrite_arg { long length; }; +struct write_arg { + VALUE io; + VALUE str; + int nosync; +}; + static VALUE io_binwrite_string(VALUE arg) { @@ -8366,6 +8372,155 @@ 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_write(struct write_arg *arg) +{ + return io_write(arg->io,arg->str,arg->nosync); +} + +/* + * 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) +{ + VALUE opt, offset, string; + 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))) { + if(NIL_P(offset)) { + rb_hash_aset(opt,sym_mode,INT2NUM(O_WRONLY|O_TRUNC|O_CREAT)); + }else{ + rb_hash_aset(opt,sym_mode,INT2NUM(O_WRONLY|O_CREAT)); + } + } + + open_key_args(argc,argv,opt,&arg); + + 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_write, (VALUE)&warg, rb_io_close, arg.io); +} + +/* + * 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) +{ + VALUE path, string, offset, mode; + struct write_arg arg; + + rb_scan_args(argc, argv, "21", &path, &string, &offset); + +#ifdef O_BINARY + if(NIL_P(offset)){ + mode = INT2NUM(O_WRONLY|O_TRUNC|O_CREAT|O_BINARY); + }else{ + mode = INT2NUM(O_RDWR|O_CREAT|O_BINARY); + } +#else + if(NIL_P(offset)) { + mode = INT2NUM(O_WRONLY|O_TRUNC|O_CREAT); + }else{ + mode = INT2NUM(O_RDWR|O_CREAT); + } +#endif + + arg.io = rb_io_open(path,mode,INT2FIX(0666),Qnil); + arg.str = string; + arg.nosync = 0; + + if(NIL_P(arg.io)) return Qnil; + +#ifndef O_BINARY + rb_io_binmode_m(arg.io); +#endif + + 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); + } + } + + return rb_ensure(io_s_write, (VALUE)&arg, rb_io_close, arg.io); +} + struct copy_stream_struct { VALUE src; VALUE dst; @@ -10315,6 +10470,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..0c05a8b 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_raise(TypeError) { File.binwrite(path, "string", mode: "w", encoding: "EUC-JP") } + t.unlink + end end