[ruby-core:36099] [Ruby 1.9 - Feature #1081] add File::write() convenience method

33 views
Skip to first unread message

Motohiro KOSAKI

unread,
May 11, 2011, 7:13:49 AM5/11/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Motohiro KOSAKI.

Assignee changed from Yukihiro Matsumoto to Motohiro KOSAKI


----------------------------------------
Feature #1081: add File::write() convenience method
http://redmine.ruby-lang.org/issues/1081

Author: Suraj Kurapati
Status: Assigned
Priority: Normal
Assignee: Motohiro KOSAKI
Category: core
Target version: 1.9.x


=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

--
http://redmine.ruby-lang.org

Motohiro KOSAKI

unread,
May 12, 2011, 2:38:02 PM5/12/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Motohiro KOSAKI.

Assignee changed from Motohiro KOSAKI to Yusuke Endoh


----------------------------------------
Feature #1081: add File::write() convenience method
http://redmine.ruby-lang.org/issues/1081

Author: Suraj Kurapati
Status: Assigned
Priority: Normal

Assignee: Yusuke Endoh

Roger Pack

unread,
May 17, 2011, 2:37:23 PM5/17/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Roger Pack.

File latest_also_accomodate_lack_of_mode_param.diff added

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

Yusuke Endoh

unread,
May 28, 2011, 1:50:43 AM5/28/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Yusuke Endoh.

Target version changed from 1.9.x 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 <ma...@tsg.ne.jp>


----------------------------------------
Feature #1081: add File::write() convenience method
http://redmine.ruby-lang.org/issues/1081

Author: Suraj Kurapati
Status: Assigned
Priority: Normal
Assignee: Yusuke Endoh
Category: core

Target version: 1.9.3

Shota Fukumori

unread,
May 29, 2011, 10:32:40 AM5/29/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Shota Fukumori.


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

Shota Fukumori

unread,
May 29, 2011, 9:57:29 PM5/29/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Shota Fukumori.

File sorah_implementation.diff added

Hi,

My Implementation has been completed File.write, binwrite.

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

Yusuke ENDOH

unread,
May 30, 2011, 9:29:54 AM5/30/11
to ruby...@ruby-lang.org
Hello,

2011/5/30 Shota Fukumori <so...@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 `<main>'

--
Yusuke Endoh <ma...@tsg.ne.jp>

Shota Fukumori

unread,
May 31, 2011, 2:43:26 AM5/31/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Shota Fukumori.


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

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

Yusuke Endoh

unread,
May 31, 2011, 9:20:47 AM5/31/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Yusuke Endoh.


Hello,

2011/5/31 Shota Fukumori <so...@tubusu.net>:


> 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 <i>offset</i>, writes
+ * <i>string</i>, then returns the length written.
+ * <code>write</code> ensures the file is closed before returning.
+ * If <i>offset</i> 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 <i>offset</i>, write
+ * <i>string</i> then returns the length written.
+ * <code>binwrite</code> ensures the file is closed before returning.
+ * The open mode would be "wb:ASCII-8BIT".
+ * If <i>offset</i> 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 <ma...@tsg.ne.jp>

Shota Fukumori (sora_h)

unread,
May 31, 2011, 6:17:33 PM5/31/11
to ruby...@ruby-lang.org
hi,

On Tue, May 31, 2011 at 10:20 PM, Yusuke Endoh <ma...@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/

Shota Fukumori

unread,
May 31, 2011, 10:21:13 PM5/31/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Shota Fukumori.


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.

Roger Pack

unread,
Jun 1, 2011, 1:24:44 PM6/1/11
to ruby...@ruby-lang.org
> Great!  I tested your patch and noticed no problem.
> Roger and rubyspec folks, could you also check it?

Looks veru good.
The only question I had about it was whether this should be allowed:

File.binwrite(path, "string", mode: "w", encoding: "EUC-JP")

Cheers!
-roger-

Roman Gaufman

unread,
Oct 15, 2011, 11:14:51 PM10/15/11
to ruby...@ruby-lang.org

Issue #1081 has been updated by Roman Gaufman.


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.


----------------------------------------
Feature #1081: add File::write() convenience method
http://redmine.ruby-lang.org/issues/1081

Author: Suraj Kurapati
Status: Closed

Reply all
Reply to author
Forward
0 new messages