Index: io.c =================================================================== --- io.c (revision 27536) +++ io.c (working copy) @@ -7779,7 +7779,7 @@ }; static void -open_key_args(int argc, VALUE *argv, struct foreach_arg *arg) +open_key_args_with_opt(int argc, VALUE *argv, struct foreach_arg *arg, int mandatory_argc, int default_mode, int default_perm) { VALUE opt, v; @@ -7787,9 +7787,9 @@ arg->io = 0; arg->argc = argc - 1; arg->argv = argv + 1; - if (argc == 1) { + if (argc == mandatory_argc) { no_key: - arg->io = rb_io_open(argv[0], INT2NUM(O_RDONLY), INT2FIX(0666), Qnil); + arg->io = rb_io_open(argv[0], INT2NUM(default_mode), INT2FIX(default_perm), Qnil); return; } opt = pop_last_hash(&arg->argc, arg->argv); @@ -7814,9 +7814,23 @@ rb_ary_clear(args); /* prevent from GC */ return; } + if (default_mode != O_RDONLY && NIL_P(rb_hash_aref(opt, sym_mode))) { + opt = rb_hash_dup(opt); + rb_hash_aset(opt, sym_mode, INT2NUM(default_mode)); + } + if (default_perm != 0666 && NIL_P(rb_hash_aref(opt, sym_perm))) { + opt = rb_hash_dup(opt); + rb_hash_aset(opt, sym_perm, INT2FIX(default_perm)); + } arg->io = rb_io_open(argv[0], Qnil, Qnil, opt); } +static void +open_key_args(int argc, VALUE *argv, struct foreach_arg *arg) +{ + open_key_args_with_opt(argc, argv, arg, 1, O_RDONLY, 0666); +} + static VALUE io_s_foreach(struct foreach_arg *arg) { @@ -7904,6 +7918,16 @@ return io_read(arg->argc, arg->argv, arg->io); } +struct write_arg { + VALUE io, str; +}; + +static VALUE +io_s_write(struct write_arg *arg) +{ + return io_write(arg->io, arg->str, 0); +} + struct seek_arg { VALUE io; VALUE offset; @@ -7911,7 +7935,7 @@ }; static VALUE -seek_before_read(struct seek_arg *arg) +seek_before_access(struct seek_arg *arg) { rb_io_binmode(arg->io); return rb_io_seek(arg->io, arg->offset, arg->mode); @@ -7922,7 +7946,7 @@ * IO.read(name, [length [, offset]] ) => string * IO.read(name, [length [, offset]], open_args) => string * - * Opens the file, optionally seeks to the given offset, then returns + * Opens the file, optionally seeks to the given offset, then returns * length bytes (defaulting to the rest of the file). * read ensures the file is closed before returning. * @@ -7964,7 +7988,7 @@ sarg.io = arg.io; sarg.offset = offset; sarg.mode = SEEK_SET; - rb_protect((VALUE (*)(VALUE))seek_before_read, (VALUE)&sarg, &state); + rb_protect((VALUE (*)(VALUE))seek_before_access, (VALUE)&sarg, &state); if (state) { rb_io_close(arg.io); rb_jump_tag(state); @@ -7978,9 +8002,9 @@ * call-seq: * IO.binread(name, [length [, offset]] ) => string * - * Opens the file, optionally seeks to the given offset, then returns + * Opens the file, optionally seeks to the given offset, then returns * length bytes (defaulting to the rest of the file). - * read ensures the file is closed before returning. + * binread ensures the file is closed before returning. * The open mode would be "rb:ASCII-8BIT". * * IO.binread("testfile") #=> "This is line one\nThis is line two\nThis is line three\nAnd so on...\n" @@ -8006,6 +8030,117 @@ return rb_ensure(io_s_read, (VALUE)&arg, 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) +{ + VALUE offset; + struct foreach_arg arg; + struct write_arg warg; + int mode = O_WRONLY | O_CREAT, mandatory_argc; + + rb_scan_args(argc, argv, "22", NULL, &warg.str, &offset, NULL); + if (!NIL_P(offset) && FIXNUM_P(offset)) { + mandatory_argc = 3; + } + else { + mode |= O_TRUNC; + mandatory_argc = 2; + } + open_key_args_with_opt(argc, argv, &arg, mandatory_argc, mode, 0666); + if (NIL_P(arg.io)) return Qnil; + if (!NIL_P(offset) && FIXNUM_P(offset)) { + struct seek_arg sarg; + int state = 0; + sarg.io = arg.io; + sarg.offset = offset; + sarg.mode = SEEK_SET; + rb_protect((VALUE (*)(VALUE))seek_before_access, (VALUE)&sarg, &state); + if (state) { + rb_io_close(arg.io); + rb_jump_tag(state); + } + if (arg.argc == 2) arg.argc = 1; + } + warg.io = arg.io; + 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 offset; + const char *mode; + struct write_arg warg; + + rb_scan_args(argc, argv, "21", NULL, &warg.str, &offset); + if (!NIL_P(offset)) { + NUM2OFFT(offset); + mode = "ab:ASCII-8BIT"; + } + else { + mode = "wb:ASCII-8BIT"; + } + FilePathValue(argv[0]); + warg.io = rb_io_open(argv[0], rb_str_new_cstr(mode), Qnil, Qnil); + if (NIL_P(warg.io)) return Qnil; + if (!NIL_P(offset)) { + rb_io_seek(warg.io, offset, SEEK_SET); + } + return rb_ensure(io_s_write, (VALUE)&warg, rb_io_close, warg.io); +} + struct copy_stream_struct { VALUE src; VALUE dst; @@ -9809,6 +9944,8 @@ 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); Index: test/ruby/test_io.rb =================================================================== --- test/ruby/test_io.rb (revision 27536) +++ test/ruby/test_io.rb (working copy) @@ -1582,4 +1582,48 @@ t.close assert_raise(IOError) {t.binmode} end + + def test_s_write + t = Tempfile.new("foo") + path = t.path + t.close(false) + File.write(path, "foo\nbar\nbaz") + assert("foo\nbar\nbaz", File.read(path)) + File.write(path, "FOO", 0) + assert("FOO\nbar\nbaz", File.read(path)) + File.write(path, "BAR") + assert("BAR", File.read(path)) + File.write(path, "\u{3042}", mode: "w", encoding: "EUC-JP") + assert("\u{3042}".encode("EUC-JP"), File.read(path, encoding: "EUC-JP")) + File.delete t + assert(6, File.write(path,'string',2)) + File.delete t + assert_raise(Errno::EINVAL) { File.write('/tmp/offset','string',-2) } + File.write(path, 'string') + File.write(path, 'string', 1) + assert("sstring", 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("foo\nbar\nbaz", File.read(path)) + File.binwrite(path, "FOO", 0) + assert("FOO\nbar\nbaz", File.read(path)) + File.binwrite(path, "BAR") + assert("BAR", File.read(path)) + File.binwrite(path, "\u{3042}") + assert("\u{3042}", File.read(path, encoding: "EUC-JP")) + File.delete t + assert(6, File.binwrite(path,'string',2)) + File.delete t + File.write(path, 'string') + File.write(path, 'string', 1) + assert("sstring", File.binread(path)) + assert_raise(Errno::EINVAL) { File.binwrite('/tmp/offset','string',-2) } + t.unlink + end end