Project

General

Profile

Actions

Feature #1081

closed

add File::write() convenience method

Added by sunaku (Suraj Kurapati) about 15 years ago. Updated over 12 years ago.

Status:
Closed
Target version:
[ruby-core:21701]

Description

=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


Files

0001-io.c-io_s_write-io_s_binwrite-Re-add-IO.write-binwri.patch (2.86 KB) 0001-io.c-io_s_write-io_s_binwrite-Re-add-IO.write-binwri.patch runpaint (Run Paint Run Run), 03/07/2010 02:32 AM
new.diff (9.02 KB) new.diff rogerdpack (Roger Pack), 04/29/2010 03:42 AM
latest.diff (7.49 KB) latest.diff rogerdpack (Roger Pack), 04/30/2010 02:32 AM
add_file_write_prelude.diff (4.66 KB) add_file_write_prelude.diff rogerdpack (Roger Pack), 03/19/2011 04:41 AM
latest_also_accomodate_lack_of_mode_param.diff (5.59 KB) latest_also_accomodate_lack_of_mode_param.diff rogerdpack (Roger Pack), 05/18/2011 03:37 AM
sorah_implementation.diff (7.46 KB) sorah_implementation.diff sorah (Sorah Fukumori), 05/30/2011 10:57 AM

Related issues 1 (0 open1 closed)

Related to Ruby master - Bug #4846: Permission denied - /tmp/nonexistingClosedznz (Kazuhiro NISHIYAMA)06/07/2011Actions
Actions #1

Updated by ko1 (Koichi Sasada) about 15 years ago

  • Assignee set to matz (Yukihiro Matsumoto)

=begin

=end

Actions #2

Updated by matz (Yukihiro Matsumoto) about 15 years ago

=begin
Hi,

In message "Re: [ruby-core:21701] [Feature #1081] add File::write() convenience method"
on Sun, 1 Feb 2009 05:46:57 +0900, Suraj Kurapati writes:

|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!' }

open() has following API

open(path, mode="r", mode=0, opthash=nil)

write() has

IO#write(string)

What do you think proper API for combined File.open?

File::write(path, string, mode, opt)

or

File::write(string, path, mode, opt)

or whatever? The latter would conflict with some in-house definition
of File::write.

						matz.

=end

Actions #3

Updated by murphy (Kornelius Kalnbach) about 15 years ago

=begin
Yukihiro Matsumoto wrote:

What do you think proper API for combined File.open?

File::write(path, string, mode, opt)

or

File::write(string, path, mode, opt)
we badly need keyword arguments.

[murphy]

=end

Actions #4

Updated by sunaku (Suraj Kurapati) about 15 years ago

=begin
Hi,

Sorry for my late response.

Matz wrote:

What do you think proper API for combined File.open?

File::write(path, string, mode, opt)

I prefer this order of parameters because it feels natural:

File::open('hello.txt', 'r').read()
File::read('hello.txt')

File::open('hello.txt', 'w').write('hello')
File::write('hello.txt', 'hello')

In addition, the Facets library also perfers this order:

http://facets.rubyforge.org/doc/api/core/classes/File.html#M000241

or

File::write(string, path, mode, opt)

The latter would conflict with some in-house definition of File::write.

Then, to minimize disruption, let us avoid this latter order of parameters.

Thanks for your consideration.
=end

Actions #5

Updated by rogerdpack (Roger Pack) almost 15 years ago

=begin

What do you think proper API for combined File.open?

File::write(path, string, mode, opt)

I also prefer this first way.
File.write('hello.txt', 'some stuff')

and default to mode 'wb' for all platforms
Thanks for looking into this. It would be quite useful.

=end

Actions #6

Updated by sunaku (Suraj Kurapati) over 14 years ago

=begin
To summarize Roger's comment, we prefer this API:

File::write(path, string, mode='wb', opt={})

Thanks for your consideration.
=end

Actions #7

Updated by axgle (xiong ai) over 14 years ago

=begin
File::write(path, string, mode='wb', opt={})
+1
=end

Actions #8

Updated by rogerdpack (Roger Pack) over 14 years ago

=begin
Hmm.
On second thought perhaps on windows it should be

File::write(path, string, mode='w', opt={})

And there should be

File::binwrite(path, string, mode='wb', opt={})

for consistency with 1.9's existing File::read and File::binread

Thoughts?
-r

=end

Actions #9

Updated by sunaku (Suraj Kurapati) about 14 years ago

=begin
That seems reasonable Roger. I agree:

File::write(path, string, mode='w', opt={})
File::binwrite(path, string, mode='wb', opt={})
=end

Actions #10

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

Please add a File::write() convenience method to the core Ruby API.

I have written a patch. The API is similar to File.read except string:

File.write(path, string[, offset]) #=> length written
File.write(path, string[, offset], open_args) #=> length written

If offset is given, the file is not truncated. Otherwise, truncated.

$ ./ruby -e 'File.write("foo", "foo\nbar\nbaz\n")' && cat foo
foo
bar
baz

$ ./ruby -e 'File.write("foo", "BAR", 4)' && cat foo
foo
BAR
baz

$ ./ruby -e 'File.write("foo", "FOO\n")' && cat foo
FOO

$ ./ruby -e 'File.write("foo", "あいうえお", mode: "w", encoding: "EUC-JP")' && nkf -Ew foo
あいうえお

I'll commit the patch if anyone says objection.

diff --git a/io.c b/io.c
index 8026ea3..08d4988 100644
--- a/io.c
+++ b/io.c
@@ -7701,7 +7701,7 @@ struct foreach_arg {
};

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;

@@ -7709,9 +7709,9 @@ open_key_args(int argc, VALUE *argv, struct foreach_arg *arg)
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);
    @@ -7736,9 +7736,23 @@ open_key_args(int argc, VALUE *argv, struct foreach_arg arg)
    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)
{
@@ -7826,6 +7840,16 @@ io_s_read(struct foreach_arg *arg)
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;
@@ -7833,7 +7857,7 @@ struct seek_arg {
};

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);
@@ -7844,7 +7868,7 @@ seek_before_read(struct seek_arg *arg)

  • 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.

@@ -7886,7 +7910,7 @@ rb_io_s_read(int argc, VALUE *argv, VALUE io)
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);
    @@ -7900,9 +7924,9 @@ rb_io_s_read(int argc, VALUE *argv, VALUE io)
  • 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"
      

@@ -7928,6 +7952,117 @@ rb_io_s_binread(int argc, VALUE *argv, VALUE io)
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;
@@ -9731,6 +9866,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 5fc3b13..6ea128a 100644
    --- a/test/ruby/test_io.rb
    +++ b/test/ruby/test_io.rb
    @@ -1512,4 +1512,34 @@ End
    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"))
  • 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"))
  • t.unlink
  • end
    end

--
Yusuke Endoh
=end

Actions #11

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

Yusuke Endoh wrote:

I have written a patch. The API is similar to File.read except string:

File.write(path, string[, offset]) #=> length written
File.write(path, string[, offset], open_args) #=> length written

snip

I'll commit the patch if anyone says objection.

Sorry, I have misunderstood matz had already approved the feature in
[ruby-core:21732]. But I realized he just discussed API design.

I have not commit it yet. Matz, may I commit my patch?

--
Yusuke Endoh
=end

Actions #12

Updated by matz (Yukihiro Matsumoto) about 14 years ago

=begin
Hi,

In message "Re: [ruby-core:28450] [Feature #1081] add File::write() convenience method"
on Thu, 4 Mar 2010 02:48:14 +0900, Yusuke Endoh writes:

|I have not commit it yet. Matz, may I commit my patch?

Go ahead.

						matz.

=end

Actions #13

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

2010/3/4 Yukihiro Matsumoto :

In message "Re: [ruby-core:28450] [Feature #1081] add File::write() convenience method"
   on Thu, 4 Mar 2010 02:48:14 +0900, Yusuke Endoh writes:

|I have not commit it yet.  Matz, may I commit my patch?

Go ahead.

Thank you! Done.

--
Yusuke ENDOH

=end

Actions #14

Updated by mame (Yusuke Endoh) about 14 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

=begin
This issue was solved with changeset r26816.
Suraj, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.

=end

Actions #15

Updated by runpaint (Run Paint Run Run) about 14 years ago

=begin
Is it intended that the length of the leading nulls are not included
in the return value? I assumed the second line in the example below
would return 3.

File.delete('/tmp/glark') #=> 1
File.write('/tmp/glark','s',2) #=> 1
File.read('/tmp/glark') #=> "\u0000\u0000s"

=end

Actions #16

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

2010/3/5 Run Paint Run Run :

Is it intended that the length of the leading nulls are not included
in the return value? I assumed the second line in the example below
would return 3.

File.delete('/tmp/glark') #=> 1
File.write('/tmp/glark','s',2) #=> 1
File.read('/tmp/glark') #=> "\u0000\u0000s"

Intended. It currently returns the length actually written. The
\u0000s are padded by seek, not write.

Consider the situation where there is /tmp/glark whose size is more
than 2:

File.read('/tmp/glark') #=> "foo"
File.write('/tmp/glark', "s", 2) #=> 1
File.read('/tmp/glark') #=> "fos"

In this case, it is natural (for me) to return 1.

But I'm happy to change the spec if you guys want.

--
Yusuke ENDOH

=end

Actions #17

Updated by runpaint (Run Paint Run Run) about 14 years ago

=begin

Is it intended that the length of the leading nulls are not included
in the return value? I assumed the second line in the example below
would return 3.

 >> File.delete('/tmp/glark') #=> 1
 >> File.write('/tmp/glark','s',2) #=> 1
 >> File.read('/tmp/glark') #=> "\u0000\u0000s"

Intended.  It currently returns the length actually written.  The
\u0000s are padded by seek, not write.

OK, just wanted to check. As for #binwrite, I'm also assuming that, at
least on Linux, it's intentionally ignoring the offset, treating it
instead as merely a cue to append.

File.delete('/tmp/offset') #=> 1
File.write('/tmp/offset','ruby') #=> 4
File.write('/tmp/offset','ruby',2) #=> 4
File.size('/tmp/offset') #=> 6

File.delete('/tmp/offset') #=> 1
File.binwrite('/tmp/offset','ruby') #=> 4
File.binwrite('/tmp/offset','ruby',2) #=> 4
File.size('/tmp/offset') #=> 8

=end

Actions #18

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

2010/3/6 Run Paint Run Run :

As for #binwrite, I'm also assuming that, at
least on Linux, it's intentionally ignoring the offset, treating it
instead as merely a cue to append.

File.delete('/tmp/offset') #=> 1
File.write('/tmp/offset','ruby') #=> 4
File.write('/tmp/offset','ruby',2) #=> 4
File.size('/tmp/offset') #=> 6

File.delete('/tmp/offset') #=> 1
File.binwrite('/tmp/offset','ruby') #=> 4
File.binwrite('/tmp/offset','ruby',2) #=> 4
File.size('/tmp/offset') #=> 8

No, it is never intended :-(
Fixed. Thanks.

I had used assert instead of assert_equal in the test...

--
Yusuke ENDOH

=end

Actions #19

Updated by runpaint (Run Paint Run Run) about 14 years ago

=begin
Teamwork. :-)

The only other inconsistency I found is how offsets are handled for
non-existent paths.

 >> File.delete('/tmp/offset') #=> 1
 >> File.binwrite('/tmp/offset','string',2)
 Errno::ENOENT: No such file or directory - /tmp/offset
from (irb):6:in `binwrite'
from (irb):6
from /usr/local/bin/irb:12:in `<main>'
 >> File.write('/tmp/offset','string',2) #=> 6
 >> File.read('/tmp/offset') #=> "\u0000\u0000string"

  >> File.delete('/tmp/offset') #=> 1
  >> File.binwrite('/tmp/offset','string',-2)
  Errno::ENOENT: No such file or directory - /tmp/offset
from (irb):14:in `binwrite'
from (irb):14
from /usr/local/bin/irb:12:in `<main>'
  >> File.write('/tmp/offset','string',-2)
  Errno::EINVAL: Invalid argument - /tmp/offset
from (irb):16:in `write'
from (irb):16
from /usr/local/bin/irb:12:in `<main>'

=end

Actions #20

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
2010/3/6 Run Paint Run Run :

The only other inconsistency I found is how offsets are handled for
non-existent paths.

Grr, I hate IO.

In addition, I noticed we cannot specify permission with the current
File#binwrite.

I'll consider for some time.

--
Yusuke ENDOH

=end

Actions #21

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

2010/3/6 Yusuke ENDOH :

2010/3/6 Run Paint Run Run :

The only other inconsistency I found is how offsets are handled for
non-existent paths.

Grr, I hate IO.

In addition, I noticed we cannot specify permission with the current
File#binwrite.

I'll consider for some time.

I give up. I have already reverted and deleted the two methods.

I wrote the patch because I thought closing the ticket encourages 1.9.2
release.
But it seems to take some time to make the feature stable, and I became
afraid that it may delay the release. It is never the effect I expected.

I think refactoring io.c is needed to implement the feature.
open argument is easy to use, but difficult for me to implement :-(

--
Yusuke ENDOH

=end

Actions #22

Updated by mame (Yusuke Endoh) about 14 years ago

  • Status changed from Closed to Open
  • Target version changed from 1.9.2 to 2.0.0

=begin

=end

Actions #23

Updated by runpaint (Run Paint Run Run) about 14 years ago

=begin

I give up.  I have already reverted and deleted the two methods.

I wrote the patch because I thought closing the ticket encourages 1.9.2
release.
But it seems to take some time to make the feature stable, and I became
afraid that it may delay the release.  It is never the effect I expected.

I think refactoring io.c is needed to implement the feature.
open argument is easy to use, but difficult for me to implement :-(

Why don't we just start simple? Add IO.write and IO.binwrite that take
exactly two arguments--a path and a string--and truncate the file and
write to it the string. That covers the common use case which inspired
this ticket, and allows the optional arguments to be added later
without breaking compatibility.

=end

Actions #24

Updated by runpaint (Run Paint Run Run) about 14 years ago

=begin
For example, with the usual caveat that I don't speak C, the attached patch passes the following RubySpec: http://goo.gl/RcAW (the two methods are treated as aliases because I haven't looked at testing binmode under Linux). I suspect that if it was rewritten by somebody who knew what they were doing it would satisfy the original reporter and simplify the common idiom of "File.open('/tmp/glark','w'){|f| f << 'string'}". If more functionality is desired, IO.open can be used or another ticket can be opened.
=end

Actions #25

Updated by nobu (Nobuyoshi Nakada) about 14 years ago

=begin
Hi,

At Sun, 7 Mar 2010 02:32:56 +0900,
Run Paint Run Run wrote in [ruby-core:28536]:

File 0001-io.c-io_s_write-io_s_binwrite-Re-add-IO.write-binwri.patch added

It's broken, and will cause segfault or similar.

+static VALUE
+rb_io_s_write(int argc, VALUE path, VALUE str, VALUE io)

+static VALUE
+rb_io_s_binwrite(int argc, VALUE path, VALUE str, VALUE io)

  • rb_define_singleton_method(rb_cIO, "write", rb_io_s_write, 2);
  • rb_define_singleton_method(rb_cIO, "binwrite", rb_io_s_binwrite, 2);

--
Nobu Nakada

=end

Actions #26

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

2010/3/6 Run Paint Run Run :

I give up. I have already reverted and deleted the two methods.

I wrote the patch because I thought closing the ticket encourages 1.9.2
release.
But it seems to take some time to make the feature stable, and I became
afraid that it may delay the release. ?It is never the effect I expected.

I think refactoring io.c is needed to implement the feature.
open argument is easy to use, but difficult for me to implement :-(

Why don't we just start simple?

Because there are already complex File.read/binread.
It is confusing to make File.write/binwrite inconsistent with them.

That covers the common use case which inspired
this ticket, and allows the optional arguments to be added later
without breaking compatibility.

There is no reason to rush into 1.9.2.

--
Yusuke ENDOH

=end

Actions #27

Updated by sunaku (Suraj Kurapati) about 14 years ago

=begin
Hi,

Is this issue solved? Or was the patch1 removed from Ruby?

Sorry for asking these (perhaps self-evident) questions;
I really don't understand the discussion succeeding 1.

Thanks for your consideration.

=end

Actions #28

Updated by mame (Yusuke Endoh) about 14 years ago

=begin
Hi,

2010/3/15 Suraj Kurapati :

Is this issue solved? ?Or was the patch[1] removed from Ruby?

Removed. [ruby-core:28530]

I thought it is difficult (for me) to make the feature stable by the
deadline.
I will not disagree with a patch that anyone writes, if it passes Run
Paint's corner cases. [ruby-core:28517] [ruby-core:28528]

--
Yusuke ENDOH

=end

Actions #29

Updated by rogerdpack (Roger Pack) almost 14 years ago

=begin
I was unable to reproduce the odd behavior observed previously by Run Paint Run.

Here is the original diff plus some test cases that cover the corner cases (they don't appear to fail--maybe something else has been fixed since then, allowing things to work right now?)
Maybe somebody can point out a failing test case to me?

Thanks.

./ruby test/ruby/test_io.rb
Loaded suite test/ruby/test_io
Started
...........................................................................................
Finished in 0.687023 seconds.

91 tests, 358 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 26858
=end

Actions #30

Updated by nobu (Nobuyoshi Nakada) almost 14 years ago

=begin
Hi,

At Thu, 29 Apr 2010 03:42:57 +0900,
Roger Pack wrote in [ruby-core:29861]:

Here is the original diff plus some test cases that cover the
corner cases (they don't appear to fail--maybe something else
has been fixed since then, allowing things to work right
now?)
Maybe somebody can point out a failing test case to me?

It's sorry that your tests have no meanings at all, except for
assert_raise. You should use assert_equal instead of mere
assert.

--
Nobu Nakada

=end

Actions #31

Updated by rogerdpack (Roger Pack) almost 14 years ago

=begin
Sorry for the poor tests.
Fixing the tests revealed that there were some bugs in binwrite.

The attached (new) patch fixes the tests and code. The tests pass on windows and linux (and I think are accurate).
Feel free to refactor it as desired.

(as a note, windows currently fails some other tests in test_io.rb http://gist.github.com/383867).

Thanks.
-rp
=end

Actions #32

Updated by shyouhei (Shyouhei Urabe) over 13 years ago

  • Status changed from Open to Assigned

=begin

=end

Actions #33

Updated by rogerdpack (Roger Pack) over 13 years ago

=begin
Any chance of getting this committed at all? (File#write)?
Thanks!
=end

Actions #34

Updated by Eregon (Benoit Daloze) over 13 years ago

=begin
On 1 November 2010 15:37, Roger Pack wrote:

Issue #1081 has been updated by Roger Pack.

Any chance of getting this committed at all? (File#write)?
Thanks!

I want to show my wish to back up this, it is definitely worth it.

Is there any issue left ?

=end

Actions #35

Updated by rogerdpack (Roger Pack) about 13 years ago

=begin
Ok here is a new patch (the old one no longer merged cleanly, plus had minor bugs). Feel free to reformat it or convert to C.

The format is:

File::write(path, string, offset, opt={}) or
File::write(path, string, opt={})

Which I think is reasonable, and matches the previous commits (the ones that were rolled out because of bugs). Apparently offset is common in other api's so I guess is good to have in there.
Thanks for consideration of this.
-r
=end

Updated by kosaki (Motohiro KOSAKI) almost 13 years ago

  • Assignee changed from matz (Yukihiro Matsumoto) to kosaki (Motohiro KOSAKI)

Updated by kosaki (Motohiro KOSAKI) almost 13 years ago

  • Assignee changed from kosaki (Motohiro KOSAKI) to mame (Yusuke Endoh)

Updated by rogerdpack (Roger Pack) almost 13 years ago

Uploading new tests and a fix that accomodate for the previously failing example.

Updated by mame (Yusuke Endoh) almost 13 years ago

  • Target version changed from 2.0.0 to 1.9.3

Hello,

The status of this feature request is considered as "accepted".
If anyone writes a patch (in C) for this feature by the implementation deadline (the end of June) and if there is no objection to the patch, it will be imported in 1.9.3.
If not, this feature will be postponed to 1.9.4 or later.

Sora seems to be interested in this feature. Good luck.

--
Yusuke Endoh

Updated by sorah (Sorah Fukumori) almost 13 years ago

Hi, I'm implementing this feature in C.

I've been completed File.write, I'll complete implementation of File.binwrite in C.

--sora_h

Updated by sorah (Sorah Fukumori) almost 13 years ago

Hi,

My Implementation has been completed File.write, binwrite.

Patch is attached.
RDocs in patch and test is copied from previous patches.

Updated by mame (Yusuke Endoh) almost 13 years ago

Hello,

2011/5/30 Shota Fukumori :

My Implementation has been completed File.write, binwrite.

Good work.

But File.binwrite does not accept an optional hash while write does.

$ rm -f /tmp/x && ./ruby -e 'File.write("/tmp/x", "foo", perm: 0700)'
"foo"

$ rm -f /tmp/x && ./ruby -e 'File.binwrite("/tmp/x", "foo", perm: 0700)'
-e:1:in binwrite': can't convert Hash into Integer (TypeError) from -e:1:in '

--
Yusuke Endoh

Updated by sorah (Sorah Fukumori) almost 13 years ago

Patch has updated, Now File.binwrite accepts Hash for specifying options:

https://gist.github.com/69c544ec245f3a07aabd

Updated by mame (Yusuke Endoh) almost 13 years ago

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

Updated by sorah (Sorah Fukumori) almost 13 years ago

hi,

On Tue, May 31, 2011 at 10:20 PM, Yusuke Endoh wrote:

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:

ok. thanks refactoring.

--
Shota Fukumori a.k.a. @sora_h - http://codnote.net/

Updated by sorah (Sorah Fukumori) almost 13 years ago

I want to commit ASAP because we're going to freeze specification,
so if there aren't any problems, I'll commit patch at http://redmine.ruby-lang.org/issues/1081#note-44 [ruby-core:36630].

I remembered that matz approved this new method with this specification, if not please notify me.

Actions #47

Updated by sorah (Sorah Fukumori) almost 13 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r31902.
Suraj, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • io.c: Add File.write, File.binwrite. [Feature #1081] [ruby-core:21701]

  • test/ruby/test_io.rb: Test for File.write, File.binwrite.

  • NEWS: News for above.

Updated by hackeron (Roman Gaufman) over 12 years ago

It seems the docs here are wrong http://www.ruby-doc.org/core-1.9.3/IO.html#method-c-write -- they mention 4 arguments when only 3 are supported and the :mode isn't explained - please update the docs.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0