diff options
Diffstat (limited to 'ext/stringio')
-rw-r--r-- | ext/stringio/depend | 3 | ||||
-rw-r--r-- | ext/stringio/extconf.rb | 7 | ||||
-rw-r--r-- | ext/stringio/stringio.c | 531 | ||||
-rw-r--r-- | ext/stringio/stringio.gemspec | 8 |
4 files changed, 315 insertions, 234 deletions
diff --git a/ext/stringio/depend b/ext/stringio/depend index 828fc6e842..ba2b812041 100644 --- a/ext/stringio/depend +++ b/ext/stringio/depend @@ -53,6 +53,7 @@ stringio.o: $(hdrdir)/ruby/internal/attr/noexcept.h stringio.o: $(hdrdir)/ruby/internal/attr/noinline.h stringio.o: $(hdrdir)/ruby/internal/attr/nonnull.h stringio.o: $(hdrdir)/ruby/internal/attr/noreturn.h +stringio.o: $(hdrdir)/ruby/internal/attr/packed_struct.h stringio.o: $(hdrdir)/ruby/internal/attr/pure.h stringio.o: $(hdrdir)/ruby/internal/attr/restrict.h stringio.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h @@ -121,7 +122,6 @@ stringio.o: $(hdrdir)/ruby/internal/intern/enumerator.h stringio.o: $(hdrdir)/ruby/internal/intern/error.h stringio.o: $(hdrdir)/ruby/internal/intern/eval.h stringio.o: $(hdrdir)/ruby/internal/intern/file.h -stringio.o: $(hdrdir)/ruby/internal/intern/gc.h stringio.o: $(hdrdir)/ruby/internal/intern/hash.h stringio.o: $(hdrdir)/ruby/internal/intern/io.h stringio.o: $(hdrdir)/ruby/internal/intern/load.h @@ -152,7 +152,6 @@ stringio.o: $(hdrdir)/ruby/internal/memory.h stringio.o: $(hdrdir)/ruby/internal/method.h stringio.o: $(hdrdir)/ruby/internal/module.h stringio.o: $(hdrdir)/ruby/internal/newobj.h -stringio.o: $(hdrdir)/ruby/internal/rgengc.h stringio.o: $(hdrdir)/ruby/internal/scan_args.h stringio.o: $(hdrdir)/ruby/internal/special_consts.h stringio.o: $(hdrdir)/ruby/internal/static_assert.h diff --git a/ext/stringio/extconf.rb b/ext/stringio/extconf.rb index a933159766..553732f79c 100644 --- a/ext/stringio/extconf.rb +++ b/ext/stringio/extconf.rb @@ -1,4 +1,7 @@ # frozen_string_literal: false require 'mkmf' -have_func("rb_io_extract_modeenc", "ruby/io.h") -create_makefile('stringio') +if RUBY_ENGINE == 'ruby' + create_makefile('stringio') +else + File.write('Makefile', dummy_makefile("").join) +end diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 9ba2cd92a1..74e2b95c99 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -12,7 +12,8 @@ **********************************************************************/ -#define STRINGIO_VERSION "3.0.3" +static const char *const +STRINGIO_VERSION = "3.1.1"; #include "ruby.h" #include "ruby/io.h" @@ -32,86 +33,6 @@ # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) #endif -#ifndef HAVE_RB_IO_EXTRACT_MODEENC -#define rb_io_extract_modeenc strio_extract_modeenc -static void -strio_extract_modeenc(VALUE *vmode_p, VALUE *vperm_p, VALUE opthash, - int *oflags_p, int *fmode_p, struct rb_io_enc_t *convconfig_p) -{ - VALUE mode = *vmode_p; - VALUE intmode; - int fmode; - int has_enc = 0, has_vmode = 0; - - convconfig_p->enc = convconfig_p->enc2 = 0; - - vmode_handle: - if (NIL_P(mode)) { - fmode = FMODE_READABLE; - } - else if (!NIL_P(intmode = rb_check_to_integer(mode, "to_int"))) { - int flags = NUM2INT(intmode); - fmode = rb_io_oflags_fmode(flags); - } - else { - const char *m = StringValueCStr(mode), *n, *e; - fmode = rb_io_modestr_fmode(m); - n = strchr(m, ':'); - if (n) { - long len; - char encname[ENCODING_MAXNAMELEN+1]; - has_enc = 1; - if (fmode & FMODE_SETENC_BY_BOM) { - n = strchr(n, '|'); - } - e = strchr(++n, ':'); - len = e ? e - n : (long)strlen(n); - if (len > 0 && len <= ENCODING_MAXNAMELEN) { - rb_encoding *enc; - if (e) { - memcpy(encname, n, len); - encname[len] = '\0'; - n = encname; - } - enc = rb_enc_find(n); - if (e) - convconfig_p->enc2 = enc; - else - convconfig_p->enc = enc; - } - if (e && (len = strlen(++e)) > 0 && len <= ENCODING_MAXNAMELEN) { - convconfig_p->enc = rb_enc_find(e); - } - } - } - - if (!NIL_P(opthash)) { - rb_encoding *extenc = 0, *intenc = 0; - VALUE v; - if (!has_vmode) { - ID id_mode; - CONST_ID(id_mode, "mode"); - v = rb_hash_aref(opthash, ID2SYM(id_mode)); - if (!NIL_P(v)) { - if (!NIL_P(mode)) { - rb_raise(rb_eArgError, "mode specified twice"); - } - has_vmode = 1; - mode = v; - goto vmode_handle; - } - } - - if (rb_io_extract_encoding_option(opthash, &extenc, &intenc, &fmode)) { - if (has_enc) { - rb_raise(rb_eArgError, "encoding specified twice"); - } - } - } - *fmode_p = fmode; -} -#endif - struct StringIO { VALUE string; rb_encoding *enc; @@ -171,7 +92,7 @@ static const rb_data_type_t strio_data_type = { strio_free, strio_memsize, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; #define check_strio(self) ((struct StringIO*)rb_check_typeddata((self), &strio_data_type)) @@ -257,9 +178,20 @@ strio_s_allocate(VALUE klass) } /* - * call-seq: StringIO.new(string=""[, mode]) + * call-seq: + * StringIO.new(string = '', mode = 'r+') -> new_stringio * - * Creates new StringIO instance from with _string_ and _mode_. + * Note that +mode+ defaults to <tt>'r'</tt> if +string+ is frozen. + * + * Returns a new \StringIO instance formed from +string+ and +mode+; + * see {Access Modes}[rdoc-ref:File@Access+Modes]: + * + * strio = StringIO.new # => #<StringIO> + * strio.close + * + * The instance should be closed when no longer needed. + * + * Related: StringIO.open (accepts block; closes automatically). */ static VALUE strio_initialize(int argc, VALUE *argv, VALUE self) @@ -345,7 +277,7 @@ strio_init(int argc, VALUE *argv, struct StringIO *ptr, VALUE self) { VALUE string, vmode, opt; int oflags; - struct rb_io_enc_t convconfig; + rb_io_enc_t convconfig; argc = rb_scan_args(argc, argv, "02:", &string, &vmode, &opt); rb_io_extract_modeenc(&vmode, 0, opt, &oflags, &ptr->flags, &convconfig); @@ -368,7 +300,7 @@ strio_init(int argc, VALUE *argv, struct StringIO *ptr, VALUE self) if (ptr->flags & FMODE_TRUNC) { rb_str_resize(string, 0); } - ptr->string = string; + RB_OBJ_WRITE(self, &ptr->string, string); if (argc == 1) { ptr->enc = rb_enc_get(string); } @@ -386,17 +318,32 @@ static VALUE strio_finalize(VALUE self) { struct StringIO *ptr = StringIO(self); - ptr->string = Qnil; + RB_OBJ_WRITE(self, &ptr->string, Qnil); ptr->flags &= ~FMODE_READWRITE; return self; } /* - * call-seq: StringIO.open(string=""[, mode]) {|strio| ...} + * call-seq: + * StringIO.open(string = '', mode = 'r+') {|strio| ... } + * + * Note that +mode+ defaults to <tt>'r'</tt> if +string+ is frozen. + * + * Creates a new \StringIO instance formed from +string+ and +mode+; + * see {Access Modes}[rdoc-ref:File@Access+Modes]. + * + * With no block, returns the new instance: + * + * strio = StringIO.open # => #<StringIO> + * + * With a block, calls the block with the new instance + * and returns the block's value; + * closes the instance on block exit. + * + * StringIO.open {|strio| p strio } + * # => #<StringIO> * - * Equivalent to StringIO.new except that when it is called with a block, it - * yields with the new instance and closes it, and returns the result which - * returned from the block. + * Related: StringIO.new. */ static VALUE strio_s_open(int argc, VALUE *argv, VALUE klass) @@ -482,9 +429,23 @@ strio_unimpl(int argc, VALUE *argv, VALUE self) } /* - * call-seq: strio.string -> string + * call-seq: + * string -> string + * + * Returns underlying string: + * + * StringIO.open('foo') do |strio| + * p strio.string + * strio.string = 'bar' + * p strio.string + * end * - * Returns underlying String object, the subject of IO. + * Output: + * + * "foo" + * "bar" + * + * Related: StringIO#string= (assigns the underlying string). */ static VALUE strio_get_string(VALUE self) @@ -494,9 +455,23 @@ strio_get_string(VALUE self) /* * call-seq: - * strio.string = string -> string + * string = other_string -> other_string + * + * Assigns the underlying string as +other_string+, and sets position to zero; + * returns +other_string+: * - * Changes underlying String object, the subject of IO. + * StringIO.open('foo') do |strio| + * p strio.string + * strio.string = 'bar' + * p strio.string + * end + * + * Output: + * + * "foo" + * "bar" + * + * Related: StringIO#string (returns the underlying string). */ static VALUE strio_set_string(VALUE self, VALUE string) @@ -509,15 +484,19 @@ strio_set_string(VALUE self, VALUE string) ptr->flags = OBJ_FROZEN(string) ? FMODE_READABLE : FMODE_READWRITE; ptr->pos = 0; ptr->lineno = 0; - return ptr->string = string; + RB_OBJ_WRITE(self, &ptr->string, string); + return string; } /* * call-seq: - * strio.close -> nil + * close -> nil * - * Closes a StringIO. The stream is unavailable for any further data - * operations; an +IOError+ is raised if such an attempt is made. + * Closes +self+ for both reading and writing. + * + * Raises IOError if reading or writing is attempted. + * + * Related: StringIO#close_read, StringIO#close_write. */ static VALUE strio_close(VALUE self) @@ -529,10 +508,13 @@ strio_close(VALUE self) /* * call-seq: - * strio.close_read -> nil + * close_read -> nil + * + * Closes +self+ for reading; closed-write setting remains unchanged. * - * Closes the read end of a StringIO. Will raise an +IOError+ if the - * receiver is not readable. + * Raises IOError if reading is attempted. + * + * Related: StringIO#close, StringIO#close_write. */ static VALUE strio_close_read(VALUE self) @@ -547,10 +529,13 @@ strio_close_read(VALUE self) /* * call-seq: - * strio.close_write -> nil + * close_write -> nil + * + * Closes +self+ for writing; closed-read setting remains unchanged. + * + * Raises IOError if writing is attempted. * - * Closes the write end of a StringIO. Will raise an +IOError+ if the - * receiver is not writeable. + * Related: StringIO#close, StringIO#close_read. */ static VALUE strio_close_write(VALUE self) @@ -565,9 +550,10 @@ strio_close_write(VALUE self) /* * call-seq: - * strio.closed? -> true or false + * closed? -> true or false * - * Returns +true+ if the stream is completely closed, +false+ otherwise. + * Returns +true+ if +self+ is closed for both reading and writing, + * +false+ otherwise. */ static VALUE strio_closed(VALUE self) @@ -579,9 +565,9 @@ strio_closed(VALUE self) /* * call-seq: - * strio.closed_read? -> true or false + * closed_read? -> true or false * - * Returns +true+ if the stream is not readable, +false+ otherwise. + * Returns +true+ if +self+ is closed for reading, +false+ otherwise. */ static VALUE strio_closed_read(VALUE self) @@ -593,9 +579,9 @@ strio_closed_read(VALUE self) /* * call-seq: - * strio.closed_write? -> true or false + * closed_write? -> true or false * - * Returns +true+ if the stream is not writable, +false+ otherwise. + * Returns +true+ if +self+ is closed for writing, +false+ otherwise. */ static VALUE strio_closed_write(VALUE self) @@ -615,11 +601,12 @@ strio_to_read(VALUE self) /* * call-seq: - * strio.eof -> true or false - * strio.eof? -> true or false + * eof? -> true or false * - * Returns true if the stream is at the end of the data (underlying string). - * The stream must be opened for reading or an +IOError+ will be raised. + * Returns +true+ if positioned at end-of-stream, +false+ otherwise; + * see {Position}[rdoc-ref:IO@Position]. + * + * Raises IOError if the stream is not opened for reading. */ static VALUE strio_eof(VALUE self) @@ -632,15 +619,19 @@ strio_eof(VALUE self) static VALUE strio_copy(VALUE copy, VALUE orig) { - struct StringIO *ptr; + struct StringIO *ptr, *old_ptr; + VALUE old_string = Qundef; orig = rb_convert_type(orig, T_DATA, "StringIO", "to_strio"); if (copy == orig) return copy; ptr = StringIO(orig); - if (check_strio(copy)) { - strio_free(DATA_PTR(copy)); + old_ptr = check_strio(copy); + if (old_ptr) { + old_string = old_ptr->string; + strio_free(old_ptr); } DATA_PTR(copy) = ptr; + RB_OBJ_WRITTEN(copy, old_string, ptr->string); RBASIC(copy)->flags &= ~STRIO_READWRITE; RBASIC(copy)->flags |= RBASIC(orig)->flags & STRIO_READWRITE; ++ptr->count; @@ -649,13 +640,10 @@ strio_copy(VALUE copy, VALUE orig) /* * call-seq: - * strio.lineno -> integer + * lineno -> current_line_number * - * Returns the current line number. The stream must be - * opened for reading. +lineno+ counts the number of times +gets+ is - * called, rather than the number of newlines encountered. The two - * values will differ if +gets+ is called with a separator other than - * newline. See also the <code>$.</code> variable. + * Returns the current line number in +self+; + * see {Line Number}[rdoc-ref:IO@Line+Number]. */ static VALUE strio_get_lineno(VALUE self) @@ -665,10 +653,10 @@ strio_get_lineno(VALUE self) /* * call-seq: - * strio.lineno = integer -> integer + * lineno = new_line_number -> new_line_number * - * Manually sets the current line number to the given value. - * <code>$.</code> is updated only on the next read. + * Sets the current line number in +self+ to the given +new_line_number+; + * see {Line Number}[rdoc-ref:IO@Line+Number]. */ static VALUE strio_set_lineno(VALUE self, VALUE lineno) @@ -679,9 +667,10 @@ strio_set_lineno(VALUE self, VALUE lineno) /* * call-seq: - * strio.binmode -> stringio + * binmode -> self * - * Puts stream into binary mode. See IO#binmode. + * Sets the data mode in +self+ to binary mode; + * see {Data Mode}[rdoc-ref:File@Data+Mode]. * */ static VALUE @@ -705,11 +694,27 @@ strio_binmode(VALUE self) /* * call-seq: - * strio.reopen(other_StrIO) -> strio - * strio.reopen(string, mode) -> strio + * reopen(other, mode = 'r+') -> self + * + * Reinitializes the stream with the given +other+ (string or StringIO) and +mode+; + * see IO.new: + * + * StringIO.open('foo') do |strio| + * p strio.string + * strio.reopen('bar') + * p strio.string + * other_strio = StringIO.new('baz') + * strio.reopen(other_strio) + * p strio.string + * other_strio.close + * end + * + * Output: + * + * "foo" + * "bar" + * "baz" * - * Reinitializes the stream with the given <i>other_StrIO</i> or _string_ - * and _mode_ (see StringIO#new). */ static VALUE strio_reopen(int argc, VALUE *argv, VALUE self) @@ -723,10 +728,10 @@ strio_reopen(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.pos -> integer - * strio.tell -> integer + * pos -> stream_position * - * Returns the current offset (in bytes). + * Returns the current position (in bytes); + * see {Position}[rdoc-ref:IO@Position]. */ static VALUE strio_get_pos(VALUE self) @@ -736,9 +741,10 @@ strio_get_pos(VALUE self) /* * call-seq: - * strio.pos = integer -> integer + * pos = new_position -> new_position * - * Seeks to the given position (in bytes). + * Sets the current position (in bytes); + * see {Position}[rdoc-ref:IO@Position]. */ static VALUE strio_set_pos(VALUE self, VALUE pos) @@ -754,10 +760,11 @@ strio_set_pos(VALUE self, VALUE pos) /* * call-seq: - * strio.rewind -> 0 + * rewind -> 0 * - * Positions the stream to the beginning of input, resetting - * +lineno+ to zero. + * Sets the current position and line number to zero; + * see {Position}[rdoc-ref:IO@Position] + * and {Line Number}[rdoc-ref:IO@Line+Number]. */ static VALUE strio_rewind(VALUE self) @@ -770,10 +777,11 @@ strio_rewind(VALUE self) /* * call-seq: - * strio.seek(amount, whence=SEEK_SET) -> 0 + * seek(offset, whence = SEEK_SET) -> 0 * - * Seeks to a given offset _amount_ in the stream according to - * the value of _whence_ (see IO#seek). + * Sets the current position to the given integer +offset+ (in bytes), + * with respect to a given constant +whence+; + * see {Position}[rdoc-ref:IO@Position]. */ static VALUE strio_seek(int argc, VALUE *argv, VALUE self) @@ -809,9 +817,9 @@ strio_seek(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.sync -> true + * sync -> true * - * Returns +true+ always. + * Returns +true+; implemented only for compatibility with other stream classes. */ static VALUE strio_get_sync(VALUE self) @@ -826,10 +834,12 @@ strio_get_sync(VALUE self) /* * call-seq: - * strio.each_byte {|byte| block } -> strio - * strio.each_byte -> anEnumerator + * each_byte {|byte| ... } -> self * - * See IO#each_byte. + * With a block given, calls the block with each remaining byte in the stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. + * + * With no block given, returns an enumerator. */ static VALUE strio_each_byte(VALUE self) @@ -847,9 +857,10 @@ strio_each_byte(VALUE self) /* * call-seq: - * strio.getc -> string or nil + * getc -> character or nil * - * See IO#getc. + * Reads and returns the next character from the stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_getc(VALUE self) @@ -872,9 +883,10 @@ strio_getc(VALUE self) /* * call-seq: - * strio.getbyte -> fixnum or nil + * getbyte -> byte or nil * - * See IO#getbyte. + * Reads and returns the next 8-bit byte from the stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_getbyte(VALUE self) @@ -910,12 +922,10 @@ strio_extend(struct StringIO *ptr, long pos, long len) /* * call-seq: - * strio.ungetc(string) -> nil + * ungetc(character) -> nil * - * Pushes back one character (passed as a parameter) - * such that a subsequent buffered read will return it. There is no - * limitation for multiple pushbacks including pushing back behind the - * beginning of the buffer string. + * Pushes back ("unshifts") a character or integer onto the stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_ungetc(VALUE self, VALUE c) @@ -950,9 +960,10 @@ strio_ungetc(VALUE self, VALUE c) /* * call-seq: - * strio.ungetbyte(fixnum) -> nil + * ungetbyte(byte) -> nil * - * See IO#ungetbyte + * Pushes back ("unshifts") an 8-bit byte onto the stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_ungetbyte(VALUE self, VALUE c) @@ -1012,9 +1023,10 @@ strio_unget_bytes(struct StringIO *ptr, const char *cp, long cl) /* * call-seq: - * strio.readchar -> string + * readchar -> string * - * See IO#readchar. + * Like +getc+, but raises an exception if already at end-of-stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_readchar(VALUE self) @@ -1026,9 +1038,10 @@ strio_readchar(VALUE self) /* * call-seq: - * strio.readbyte -> fixnum + * readbyte -> byte * - * See IO#readbyte. + * Like +getbyte+, but raises an exception if already at end-of-stream; + * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_readbyte(VALUE self) @@ -1040,10 +1053,12 @@ strio_readbyte(VALUE self) /* * call-seq: - * strio.each_char {|char| block } -> strio - * strio.each_char -> anEnumerator + * each_char {|c| ... } -> self + * + * With a block given, calls the block with each remaining character in the stream; + * see {Character IO}[rdoc-ref:IO@Character+IO]. * - * See IO#each_char. + * With no block given, returns an enumerator. */ static VALUE strio_each_char(VALUE self) @@ -1060,10 +1075,12 @@ strio_each_char(VALUE self) /* * call-seq: - * strio.each_codepoint {|c| block } -> strio - * strio.each_codepoint -> anEnumerator + * each_codepoint {|codepoint| ... } -> self + * + * With a block given, calls the block with each remaining codepoint in the stream; + * see {Codepoint IO}[rdoc-ref:IO@Codepoint+IO]. * - * See IO#each_codepoint. + * With no block given, returns an enumerator. */ static VALUE strio_each_codepoint(VALUE self) @@ -1126,38 +1143,57 @@ struct getline_arg { }; static struct getline_arg * -prepare_getline_args(struct getline_arg *arg, int argc, VALUE *argv) +prepare_getline_args(struct StringIO *ptr, struct getline_arg *arg, int argc, VALUE *argv) { - VALUE str, lim, opts; + VALUE rs, lim, opts; long limit = -1; int respect_chomp; - argc = rb_scan_args(argc, argv, "02:", &str, &lim, &opts); - respect_chomp = argc == 0 || !NIL_P(str); + argc = rb_scan_args(argc, argv, "02:", &rs, &lim, &opts); + respect_chomp = argc == 0 || !NIL_P(rs); switch (argc) { case 0: - str = rb_rs; + rs = rb_rs; break; case 1: - if (!NIL_P(str) && !RB_TYPE_P(str, T_STRING)) { - VALUE tmp = rb_check_string_type(str); + if (!NIL_P(rs) && !RB_TYPE_P(rs, T_STRING)) { + VALUE tmp = rb_check_string_type(rs); if (NIL_P(tmp)) { - limit = NUM2LONG(str); - str = rb_rs; + limit = NUM2LONG(rs); + rs = rb_rs; } else { - str = tmp; + rs = tmp; } } break; case 2: - if (!NIL_P(str)) StringValue(str); + if (!NIL_P(rs)) StringValue(rs); if (!NIL_P(lim)) limit = NUM2LONG(lim); break; } - arg->rs = str; + if (!NIL_P(rs)) { + rb_encoding *enc_rs, *enc_io; + enc_rs = rb_enc_get(rs); + enc_io = get_enc(ptr); + if (enc_rs != enc_io && + (rb_enc_str_coderange(rs) != ENC_CODERANGE_7BIT || + (RSTRING_LEN(rs) > 0 && !rb_enc_asciicompat(enc_io)))) { + if (rs == rb_rs) { + rs = rb_enc_str_new(0, 0, enc_io); + rb_str_buf_cat_ascii(rs, "\n"); + rs = rs; + } + else { + rb_raise(rb_eArgError, "encoding mismatch: %s IO with %s RS", + rb_enc_name(enc_io), + rb_enc_name(enc_rs)); + } + } + } + arg->rs = rs; arg->limit = limit; arg->chomp = 0; if (!NIL_P(opts)) { @@ -1245,8 +1281,9 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) str = strio_substr(ptr, ptr->pos, e - s - w, enc); } else { - if (n < e - s) { - if (e - s < 1024) { + if (n < e - s + arg->chomp) { + /* unless chomping, RS at the end does not matter */ + if (e - s < 1024 || n == e - s) { for (p = s; p + n <= e; ++p) { if (MEMCMP(p, RSTRING_PTR(str), char, n) == 0) { e = p + n; @@ -1273,35 +1310,38 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) /* * call-seq: - * strio.gets(sep=$/, chomp: false) -> string or nil - * strio.gets(limit, chomp: false) -> string or nil - * strio.gets(sep, limit, chomp: false) -> string or nil + * gets(sep = $/, chomp: false) -> string or nil + * gets(limit, chomp: false) -> string or nil + * gets(sep, limit, chomp: false) -> string or nil * - * See IO#gets. + * Reads and returns a line from the stream; + * assigns the return value to <tt>$_</tt>; + * see {Line IO}[rdoc-ref:IO@Line+IO]. */ static VALUE strio_gets(int argc, VALUE *argv, VALUE self) { + struct StringIO *ptr = readable(self); struct getline_arg arg; VALUE str; - if (prepare_getline_args(&arg, argc, argv)->limit == 0) { - struct StringIO *ptr = readable(self); + if (prepare_getline_args(ptr, &arg, argc, argv)->limit == 0) { return rb_enc_str_new(0, 0, get_enc(ptr)); } - str = strio_getline(&arg, readable(self)); + str = strio_getline(&arg, ptr); rb_lastline_set(str); return str; } /* * call-seq: - * strio.readline(sep=$/, chomp: false) -> string - * strio.readline(limit, chomp: false) -> string or nil - * strio.readline(sep, limit, chomp: false) -> string or nil + * readline(sep = $/, chomp: false) -> string + * readline(limit, chomp: false) -> string + * readline(sep, limit, chomp: false) -> string * - * See IO#readline. + * Reads a line as with IO#gets, but raises EOFError if already at end-of-file; + * see {Line IO}[rdoc-ref:IO@Line+IO]. */ static VALUE strio_readline(int argc, VALUE *argv, VALUE self) @@ -1313,32 +1353,29 @@ strio_readline(int argc, VALUE *argv, VALUE self) /* * call-seq: - * strio.each(sep=$/, chomp: false) {|line| block } -> strio - * strio.each(limit, chomp: false) {|line| block } -> strio - * strio.each(sep, limit, chomp: false) {|line| block } -> strio - * strio.each(...) -> anEnumerator - * - * strio.each_line(sep=$/, chomp: false) {|line| block } -> strio - * strio.each_line(limit, chomp: false) {|line| block } -> strio - * strio.each_line(sep, limit, chomp: false) {|line| block } -> strio - * strio.each_line(...) -> anEnumerator + * each_line(sep = $/, chomp: false) {|line| ... } -> self + * each_line(limit, chomp: false) {|line| ... } -> self + * each_line(sep, limit, chomp: false) {|line| ... } -> self * - * See IO#each. + * Calls the block with each remaining line read from the stream; + * does nothing if already at end-of-file; + * returns +self+. + * See {Line IO}[rdoc-ref:IO@Line+IO]. */ static VALUE strio_each(int argc, VALUE *argv, VALUE self) { VALUE line; + struct StringIO *ptr = readable(self); struct getline_arg arg; - StringIO(self); RETURN_ENUMERATOR(self, argc, argv); - if (prepare_getline_args(&arg, argc, argv)->limit == 0) { + if (prepare_getline_args(ptr, &arg, argc, argv)->limit == 0) { rb_raise(rb_eArgError, "invalid limit: 0 for each_line"); } - while (!NIL_P(line = strio_getline(&arg, readable(self)))) { + while (!NIL_P(line = strio_getline(&arg, ptr))) { rb_yield(line); } return self; @@ -1356,15 +1393,15 @@ static VALUE strio_readlines(int argc, VALUE *argv, VALUE self) { VALUE ary, line; + struct StringIO *ptr = readable(self); struct getline_arg arg; - StringIO(self); - ary = rb_ary_new(); - if (prepare_getline_args(&arg, argc, argv)->limit == 0) { + if (prepare_getline_args(ptr, &arg, argc, argv)->limit == 0) { rb_raise(rb_eArgError, "invalid limit: 0 for readlines"); } - while (!NIL_P(line = strio_getline(&arg, readable(self)))) { + ary = rb_ary_new(); + while (!NIL_P(line = strio_getline(&arg, ptr))) { rb_ary_push(ary, line); } return ary; @@ -1567,6 +1604,55 @@ strio_read(int argc, VALUE *argv, VALUE self) } /* + * call-seq: + * pread(maxlen, offset) -> string + * pread(maxlen, offset, out_string) -> string + * + * See IO#pread. + */ +static VALUE +strio_pread(int argc, VALUE *argv, VALUE self) +{ + VALUE rb_len, rb_offset, rb_buf; + rb_scan_args(argc, argv, "21", &rb_len, &rb_offset, &rb_buf); + long len = NUM2LONG(rb_len); + long offset = NUM2LONG(rb_offset); + + if (len < 0) { + rb_raise(rb_eArgError, "negative string size (or size too big): %" PRIsVALUE, rb_len); + } + + if (len == 0) { + if (NIL_P(rb_buf)) { + return rb_str_new("", 0); + } + return rb_buf; + } + + if (offset < 0) { + rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); + } + + struct StringIO *ptr = readable(self); + + if (offset >= RSTRING_LEN(ptr->string)) { + rb_eof_error(); + } + + if (NIL_P(rb_buf)) { + return strio_substr(ptr, offset, len, rb_ascii8bit_encoding()); + } + + long rest = RSTRING_LEN(ptr->string) - offset; + if (len > rest) len = rest; + rb_str_resize(rb_buf, len); + rb_enc_associate(rb_buf, rb_ascii8bit_encoding()); + MEMCPY(RSTRING_PTR(rb_buf), RSTRING_PTR(ptr->string) + offset, char, len); + return rb_buf; +} + + +/* * call-seq: * strio.sysread(integer[, outbuf]) -> string * strio.readpartial(integer[, outbuf]) -> string @@ -1726,7 +1812,7 @@ strio_set_encoding(int argc, VALUE *argv, VALUE self) else { enc = rb_find_encoding(ext_enc); if (!enc) { - struct rb_io_enc_t convconfig; + rb_io_enc_t convconfig; int oflags, fmode; VALUE vmode = rb_str_append(rb_str_new_cstr("r:"), ext_enc); rb_io_extract_modeenc(&vmode, 0, Qnil, &oflags, &fmode, &convconfig); @@ -1751,24 +1837,16 @@ strio_set_encoding_by_bom(VALUE self) } /* - * Pseudo I/O on String object, with interface corresponding to IO. + * \IO streams for strings, with access similar to + * {IO}[rdoc-ref:IO]; + * see {IO}[rdoc-ref:IO]. * - * Commonly used to simulate <code>$stdio</code> or <code>$stderr</code> + * === About the Examples * - * === Examples + * Examples on this page assume that \StringIO has been required: * * require 'stringio' * - * # Writing stream emulation - * io = StringIO.new - * io.puts "Hello World" - * io.string #=> "Hello World\n" - * - * # Reading stream emulation - * io = StringIO.new "first\nsecond\nlast\n" - * io.getc #=> "f" - * io.gets #=> "irst\n" - * io.read #=> "second\nlast\n" */ void Init_stringio(void) @@ -1834,6 +1912,7 @@ Init_stringio(void) rb_define_method(StringIO, "gets", strio_gets, -1); rb_define_method(StringIO, "readlines", strio_readlines, -1); rb_define_method(StringIO, "read", strio_read, -1); + rb_define_method(StringIO, "pread", strio_pread, -1); rb_define_method(StringIO, "write", strio_write_m, -1); rb_define_method(StringIO, "putc", strio_putc, 1); diff --git a/ext/stringio/stringio.gemspec b/ext/stringio/stringio.gemspec index 1015d261f5..8c950f8ff9 100644 --- a/ext/stringio/stringio.gemspec +++ b/ext/stringio/stringio.gemspec @@ -4,7 +4,7 @@ source_version = ["", "ext/stringio/"].find do |dir| begin break File.open(File.join(__dir__, "#{dir}stringio.c")) {|f| - f.gets("\n#define STRINGIO_VERSION ") + f.gets("\nSTRINGIO_VERSION ") f.gets[/\s*"(.+)"/, 1] } rescue Errno::ENOENT @@ -14,7 +14,6 @@ Gem::Specification.new do |s| s.name = "stringio" s.version = source_version - s.required_rubygems_version = Gem::Requirement.new(">= 2.6") s.require_paths = ["lib"] s.authors = ["Nobu Nakada", "Charles Oliver Nutter"] s.description = "Pseudo `IO` class from/to `String`." @@ -22,7 +21,8 @@ Gem::Specification.new do |s| s.files = ["README.md"] jruby = true if Gem::Platform.new('java') =~ s.platform or RUBY_ENGINE == 'jruby' if jruby - s.files += ["lib/stringio.rb", "lib/stringio.jar"] + s.require_paths = "lib/java" + s.files += ["lib/java/stringio.rb", "lib/java/stringio.jar"] s.platform = "java" else s.extensions = ["ext/stringio/extconf.rb"] @@ -30,7 +30,7 @@ Gem::Specification.new do |s| end s.homepage = "https://github.com/ruby/stringio" s.licenses = ["Ruby", "BSD-2-Clause"] - s.required_ruby_version = ">= 2.5" + s.required_ruby_version = ">= 2.7" s.summary = "Pseudo IO on String" # s.cert_chain = %w[certs/nobu.pem] |