Feature #1081
closedadd File::write() convenience method
Added by sunaku (Suraj Kurapati) over 15 years ago. Updated almost 13 years ago.
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 |
Updated by ko1 (Koichi Sasada) over 15 years ago
- Assignee set to matz (Yukihiro Matsumoto)
=begin
=end
Updated by matz (Yukihiro Matsumoto) over 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 redmine@ruby-lang.org 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
Updated by murphy (Kornelius Kalnbach) over 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
Updated by sunaku (Suraj Kurapati) over 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
Updated by rogerdpack (Roger Pack) over 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
Updated by sunaku (Suraj Kurapati) almost 15 years ago
=begin
To summarize Roger's comment, we prefer this API:
File::write(path, string, mode='wb', opt={})
Thanks for your consideration.
=end
Updated by axgle (xiong ai) almost 15 years ago
=begin
File::write(path, string, mode='wb', opt={})
+1
=end
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
Updated by sunaku (Suraj Kurapati) over 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
Updated by mame (Yusuke Endoh) over 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 mame@tsg.ne.jp
=end
Updated by mame (Yusuke Endoh) over 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 mame@tsg.ne.jp
=end
Updated by matz (Yukihiro Matsumoto) over 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 redmine@ruby-lang.org writes:
|I have not commit it yet. Matz, may I commit my patch?
Go ahead.
matz.
=end
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
Hi,
2010/3/4 Yukihiro Matsumoto matz@ruby-lang.org:
In message "Re: [ruby-core:28450] [Feature #1081] add File::write() convenience method"
on Thu, 4 Mar 2010 02:48:14 +0900, Yusuke Endoh redmine@ruby-lang.org writes:|I have not commit it yet. Matz, may I commit my patch?
Go ahead.
Thank you! Done.
--
Yusuke ENDOH mame@tsg.ne.jp
=end
Updated by mame (Yusuke Endoh) over 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
Updated by runpaint (Run Paint Run Run) over 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
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
Hi,
2010/3/5 Run Paint Run Run runrun@runpaint.org:
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 mame@tsg.ne.jp
=end
Updated by runpaint (Run Paint Run Run) over 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
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
Hi,
2010/3/6 Run Paint Run Run runrun@runpaint.org:
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') #=> 6File.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 mame@tsg.ne.jp
=end
Updated by runpaint (Run Paint Run Run) over 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
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
2010/3/6 Run Paint Run Run runrun@runpaint.org:
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 mame@tsg.ne.jp
=end
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
Hi,
2010/3/6 Yusuke ENDOH mame@tsg.ne.jp:
2010/3/6 Run Paint Run Run runrun@runpaint.org:
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 mame@tsg.ne.jp
=end
Updated by mame (Yusuke Endoh) over 14 years ago
- Status changed from Closed to Open
- Target version changed from 1.9.2 to 2.0.0
=begin
=end
Updated by runpaint (Run Paint Run Run) over 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
Updated by runpaint (Run Paint Run Run) over 14 years ago
- File 0001-io.c-io_s_write-io_s_binwrite-Re-add-IO.write-binwri.patch 0001-io.c-io_s_write-io_s_binwrite-Re-add-IO.write-binwri.patch added
=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
Updated by nobu (Nobuyoshi Nakada) over 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
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
Hi,
2010/3/6 Run Paint Run Run runrun@runpaint.org:
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 mame@tsg.ne.jp
=end
Updated by sunaku (Suraj Kurapati) over 14 years ago
Updated by mame (Yusuke Endoh) over 14 years ago
=begin
Hi,
2010/3/15 Suraj Kurapati redmine@ruby-lang.org:
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 mame@tsg.ne.jp
=end
Updated by rogerdpack (Roger Pack) over 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
Updated by nobu (Nobuyoshi Nakada) over 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
Updated by rogerdpack (Roger Pack) over 14 years ago
- File latest.diff latest.diff added
=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
Updated by shyouhei (Shyouhei Urabe) almost 14 years ago
- Status changed from Open to Assigned
=begin
=end
Updated by rogerdpack (Roger Pack) almost 14 years ago
=begin
Any chance of getting this committed at all? (File#write)?
Thanks!
=end
Updated by Eregon (Benoit Daloze) almost 14 years ago
=begin
On 1 November 2010 15:37, Roger Pack redmine@ruby-lang.org 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
Updated by rogerdpack (Roger Pack) over 13 years ago
- File add_file_write_prelude.diff add_file_write_prelude.diff added
- % Done changed from 100 to 0
=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) over 13 years ago
- Assignee changed from matz (Yukihiro Matsumoto) to kosaki (Motohiro KOSAKI)
Updated by kosaki (Motohiro KOSAKI) over 13 years ago
- Assignee changed from kosaki (Motohiro KOSAKI) to mame (Yusuke Endoh)
Updated by rogerdpack (Roger Pack) over 13 years ago
- File latest_also_accomodate_lack_of_mode_param.diff latest_also_accomodate_lack_of_mode_param.diff added
Uploading new tests and a fix that accomodate for the previously failing example.
Updated by mame (Yusuke Endoh) about 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 mame@tsg.ne.jp
Updated by sorah (Sorah Fukumori) about 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) about 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) about 13 years ago
Hello,
2011/5/30 Shota Fukumori sorah@tubusu.net:
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 mame@tsg.ne.jp
Updated by sorah (Sorah Fukumori) about 13 years ago
Patch has updated, Now File.binwrite accepts Hash for specifying options:
Updated by mame (Yusuke Endoh) about 13 years ago
Hello,
2011/5/31 Shota Fukumori sorah@tubusu.net:
Patch has updated, Now File.binwrite accepts Hash for specifying options:
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 mame@tsg.ne.jp
Updated by sorah (Sorah Fukumori) about 13 years ago
hi,
On Tue, May 31, 2011 at 10:20 PM, Yusuke Endoh mame@tsg.ne.jp 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) about 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.
Updated by sorah (Sorah Fukumori) about 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) almost 13 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.