Index: gem_prelude.rb =================================================================== --- gem_prelude.rb (revision 28430) +++ gem_prelude.rb (working copy) @@ -197,9 +197,15 @@ requirement, version = version_requirements[0].split requirement.strip! + # accomodate for gem 'gem_name', '2.3.8' + if !version + version = requirement + requirement = '=' + end + if loaded_version = GemVersions[gem_name] then case requirement - when ">", ">=" then + when ">", ">=", '=' then return false if (loaded_version <=> Gem.integers_for(version)) >= 0 when "~>" then Index: io.c =================================================================== --- io.c (revision 28430) +++ io.c (working copy) @@ -7789,7 +7789,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; @@ -7797,9 +7797,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); @@ -7824,9 +7824,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) { @@ -7917,6 +7931,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; @@ -8020,6 +8044,110 @@ return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io); } +static VALUE rb_io_s_write_helper(int argc, VALUE *argv, VALUE io, int use_binary); + +/* + * 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) +{ + return rb_io_s_write_helper(argc, argv, io, 0); +} + +static VALUE +rb_io_s_write_helper(int argc, VALUE *argv, VALUE io, int use_binary) +{ + VALUE offset; + struct foreach_arg arg; + struct write_arg warg; + int mode = O_WRONLY | O_CREAT, mandatory_argc; +#ifdef O_BINARY + if(use_binary) + mode |= O_BINARY; +#endif + + 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) +{ + return rb_io_s_write_helper(argc, argv, io, 1); +} + struct copy_stream_struct { VALUE src; VALUE dst; @@ -9859,6 +9987,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 28430) +++ test/ruby/test_io.rb (working copy) @@ -1609,4 +1609,49 @@ 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_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)) + 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) } + t.unlink + end end