lib/rubysl/stringio/stringio.rb in rubysl-stringio-1.0.1 vs lib/rubysl/stringio/stringio.rb in rubysl-stringio-2.0.0

- old
+ new

@@ -1,19 +1,29 @@ +class IO + module Writable + end + module Readable + end +end + class StringIO include Enumerable + include IO::Writable + include IO::Readable DEFAULT_RECORD_SEPARATOR = "\n" unless defined?(::DEFAULT_RECORD_SEPARATOR) # This is why we need undefined in Ruby Undefined = Object.new class Data - attr_accessor :string, :pos, :lineno + attr_accessor :string, :pos, :lineno, :encoding def initialize(string) @string = string + @encoding = string.encoding @pos = @lineno = 0 end end def self.open(*args) @@ -29,13 +39,19 @@ end end attr_reader :__data__ - def initialize(string="", mode=nil) - string = Rubinius::Type.coerce_to string, String, :to_str - @__data__ = Data.new string + def initialize(string=nil, mode=nil) + if string.nil? + @__data__ = Data.new "" + set_encoding(nil) + mode = IO::RDWR + else + string = Rubinius::Type.coerce_to string, String, :to_str + @__data__ = Data.new string + end if mode if mode.is_a?(Integer) mode_from_integer(mode) else @@ -73,10 +89,24 @@ raise IOError, "unable to modify data" if @__data__.string.frozen? end private :check_writable + def set_encoding(external, internal=nil, options=nil) + encoding = external || Encoding.default_external + @__data__.encoding = encoding + @__data__.string.force_encoding(encoding) + end + + def external_encoding + @__data__.encoding + end + + def internal_encoding + nil + end + def each_byte return to_enum :each_byte unless block_given? check_readable d = @__data__ @@ -93,42 +123,47 @@ alias_method :bytes, :each_byte def each_char return to_enum :each_char unless block_given? - if $KCODE == "UTF8" - lookup = 7.downto(4) - while c = read(1) do - n = c[0] - leftmost_zero_bit = lookup.find{|i| n[i].zero? } - case leftmost_zero_bit - when 7 # ASCII - yield c - when 6 # UTF 8 complementary characters - next # Encoding error, ignore - else - more = read(6-leftmost_zero_bit) - break unless more - yield c+more - end - end - else - while s = read(1) - yield s - end + while s = getc + yield s end self end alias_method :chars, :each_char - def each(sep = $/) - return to_enum :each, sep unless block_given? + def each_codepoint(&block) + return to_enum :each_codepoint unless block_given? check_readable - while line = getline(sep) + d = @__data__ + string = d.string + + while d.pos < string.bytesize + char = string.chr_at d.pos + + unless char + raise ArgumentError, "invalid byte sequence in #{d.encoding}" + end + + d.pos += char.bytesize + yield char.ord + end + + self + end + + alias_method :codepoints, :each_codepoint + + def each(sep=$/, limit=Undefined) + return to_enum :each, sep, limit unless block_given? + check_readable + + while line = getline(true, sep, limit) yield line end self end @@ -147,33 +182,39 @@ def write(str) check_writable str = String(str) - return 0 if str.empty? d = @__data__ pos = d.pos string = d.string - if @append || pos == string.length - string << str - d.pos = string.length - elsif pos > string.size - string[string.size..pos] = "\000" * (pos - string.size) - string << str - d.pos = string.size + if @append || pos == string.bytesize + string.byte_append str + d.pos = string.bytesize + elsif pos > string.bytesize + m = Rubinius::Mirror.reflect string + m.splice string.bytesize, 0, "\000" * (pos - string.bytesize) + string.byte_append str + d.pos = string.bytesize else - string[pos, str.length] = str - d.pos += str.length + stop = string.bytesize - pos + if str.bytesize < stop + stop = str.bytesize + end + m = Rubinius::Mirror.reflect string + m.splice pos, stop, str + d.pos += str.bytesize string.taint if str.tainted? end - return str.length + str.bytesize end alias_method :syswrite, :write + alias_method :write_nonblock, :write def close raise IOError, "closed stream" if closed? @readable = @writable = nil end @@ -200,11 +241,11 @@ !@writable end def eof? d = @__data__ - d.pos >= d.string.size + d.pos >= d.string.bytesize end alias_method :eof, :eof? def fcntl raise NotImplementedError, "StringIO#fcntl is not implemented" @@ -224,12 +265,14 @@ def getc check_readable d = @__data__ - char = d.string[d.pos] - d.pos += 1 unless eof? + return nil if eof? + + char = d.string.find_character(d.pos) + d.pos += char.bytesize char end def getbyte check_readable @@ -240,38 +283,29 @@ byte = d.string.getbyte(d.pos) d.pos += 1 byte end - def gets(sep = $/) + def gets(sep=$/, limit=Undefined) check_readable - $_ = getline(sep) + $_ = getline(false, sep, limit) end def isatty false end alias_method :tty?, :isatty - def length - @string.length - end - alias_method :size, :length - def lineno @__data__.lineno end def lineno=(line) @__data__.lineno = line end - def path - nil - end - def pid nil end def pos @@ -284,11 +318,10 @@ end def print(*args) check_writable args << $_ if args.empty? - args.map! { |x| x.nil? ? "nil" : x } write((args << $\).flatten.join) nil end def printf(*args) @@ -307,27 +340,30 @@ check_writable if obj.is_a?(String) char = obj[0] else - char = Rubinius::Type.coerce_to obj, Integer, :to_int + c = Rubinius::Type.coerce_to obj, Integer, :to_int + char = (c & 0xff).chr end d = @__data__ pos = d.pos string = d.string - if @append || pos == string.length - string << char - d.pos = string.length - elsif pos > string.length - string[string.length..pos] = "\000" * (pos - string.length) - string << char - d.pos = string.length + if @append || pos == string.bytesize + string.byte_append char + d.pos = string.bytesize + elsif pos > string.bytesize + m = Rubinius::Mirror.reflect string + m.splice string.bytesize, 0, "\000" * (pos - string.bytesize) + string.byte_append char + d.pos = string.bytesize else - string[pos] = char - d.pos += 1 + m = Rubinius::Mirror.reflect string + m.splice pos, char.bytesize, char + d.pos += char.bytesize end obj end @@ -335,11 +371,11 @@ if args.empty? write(DEFAULT_RECORD_SEPARATOR) else args.each do |arg| if arg.nil? - line = "nil" + line = "" elsif Thread.guarding? arg line = "[...]" else begin arg = Rubinius::Type.coerce_to(arg, Array, :to_ary) @@ -358,52 +394,70 @@ end nil end - def read(length = nil, buffer = "") + def read(length=nil, buffer=nil) check_readable d = @__data__ pos = d.pos string = d.string - buffer = StringValue(buffer) - if length - return nil if eof? length = Rubinius::Type.coerce_to length, Integer, :to_int raise ArgumentError if length < 0 - buffer.replace(string[pos, length]) - d.pos += buffer.length + + buffer = StringValue(buffer) if buffer + + if eof? + buffer.clear if buffer + if length == 0 + return "".force_encoding(Encoding::ASCII_8BIT) + else + return nil + end + end + + str = string.byteslice(pos, length) + str.force_encoding Encoding::ASCII_8BIT + + str = buffer.replace(str) if buffer else - return "" if eof? - buffer.replace(string[pos..-1]) - d.pos = string.size + if eof? + buffer.clear if buffer + return "".force_encoding(Encoding::ASCII_8BIT) + end + + str = string.byteslice(pos..-1) + buffer.replace str if buffer end - return buffer + d.pos += str.length + return str end def readchar raise IO::EOFError, "end of file reached" if eof? getc end - alias_method :readbyte, :readchar + def readbyte + readchar.getbyte(0) + end - def readline(sep=$/) - raise IO::EOFError, "end of file reached" if eof? + def readline(sep=$/, limit=Undefined) check_readable + raise IO::EOFError, "end of file reached" if eof? - $_ = getline(sep) + $_ = getline(true, sep, limit) end - def readlines(sep=$/) + def readlines(sep=$/, limit=Undefined) check_readable ary = [] - while line = getline(sep) + while line = getline(true, sep, limit) ary << line end ary end @@ -435,11 +489,11 @@ case whence when IO::SEEK_CUR to += @__data__.pos when IO::SEEK_END - to += @__data__.string.size + to += @__data__.string.bytesize when IO::SEEK_SET, nil else raise Errno::EINVAL, "invalid whence" end @@ -449,11 +503,11 @@ return 0 end def size - @__data__.string.size + @__data__.string.bytesize end alias_method :length, :size def string @__data__.string @@ -474,28 +528,36 @@ val end def sysread(length=nil, buffer="") str = read(length, buffer) - raise IO::EOFError, "end of file reached" if str.nil? + + if str.nil? + buffer.clear + raise IO::EOFError, "end of file reached" + end + str end + alias_method :readpartial, :sysread + alias_method :read_nonblock, :sysread + def tell @__data__.pos end def truncate(length) check_writable len = Rubinius::Type.coerce_to length, Integer, :to_int raise Errno::EINVAL, "negative length" if len < 0 string = @__data__.string - if len < string.size - string[len..string.size] = "" + if len < string.bytesize + string[len..string.bytesize] = "" else - string << "\000" * (len - string.size) + string << "\000" * (len - string.bytesize) end return length end def ungetc(char) @@ -503,24 +565,67 @@ d = @__data__ pos = d.pos string = d.string - char = Rubinius::Type.coerce_to char, Integer, :to_int + if char.kind_of? Integer + char = Rubinius::Type.coerce_to char, String, :chr + else + char = Rubinius::Type.coerce_to char, String, :to_str + end - if pos > string.size - string[string.size..pos] = "\000" * (pos - string.size) + if pos > string.bytesize + string[string.bytesize..pos] = "\000" * (pos - string.bytesize) d.pos -= 1 string[d.pos] = char elsif pos > 0 d.pos -= 1 string[d.pos] = char end nil end + def ungetbyte(bytes) + check_readable + + return unless bytes + + if bytes.kind_of? Fixnum + bytes = "" << bytes + else + bytes = StringValue(bytes) + return if bytes.bytesize == 0 + end + + d = @__data__ + pos = d.pos + string = d.string + + enc = string.encoding + + if d.pos == 0 + d.string = "#{bytes}#{string}" + else + size = bytes.bytesize + a = string.byteslice(0, pos - size) if size < pos + b = string.byteslice(pos..-1) + d.string = "#{a}#{bytes}#{b}" + d.pos = pos > size ? pos - size : 0 + end + + d.string.force_encoding enc + nil + end + + def encode_with(coder) + end + + def init_with(coder) + @__data__ = Data.new("") + end + def to_yaml_properties [] end def yaml_initialize(type, val) @@ -567,41 +672,66 @@ @append = true if (mode & IO::APPEND) != 0 d.string.replace("") if (mode & IO::TRUNC) != 0 end - def getline(sep = $/) - sep = StringValue(sep) unless sep.nil? + def getline(arg_error, sep, limit) + if limit != Undefined + limit = Rubinius::Type.coerce_to limit, Fixnum, :to_int + sep = Rubinius::Type.coerce_to sep, String, :to_str if sep + else + limit = nil + unless sep == $/ or sep.nil? + osep = sep + sep = Rubinius::Type.check_convert_type sep, String, :to_str + limit = Rubinius::Type.coerce_to osep, Fixnum, :to_int unless sep + end + end + + raise ArgumentError if arg_error and limit == 0 + return nil if eof? d = @__data__ pos = d.pos string = d.string if sep.nil? - line = string[pos..-1] - d.pos = string.size + if limit + line = string.byteslice(pos, limit) + else + line = string.byteslice(pos, string.bytesize - pos) + end + d.pos += line.bytesize elsif sep.empty? - if stop = string.index("\n\n", pos) + if stop = string.find_string("\n\n", pos) stop += 2 - line = string[pos...stop] - while string[stop] == ?\n + line = string.byteslice(pos, stop - pos) + while string.getbyte(stop) == 10 stop += 1 end d.pos = stop else - line = string[pos..-1] - d.pos = string.size + line = string.byteslice(pos, string.bytesize - pos) + d.pos = string.bytesize end else - if stop = string.index(sep, pos) - stop += sep.length - line = string[pos...stop] + if stop = string.find_string(sep, pos) + if limit && stop - pos >= limit + stop = pos + limit + else + stop += sep.bytesize + end + line = string.byteslice(pos, stop - pos) d.pos = stop else - line = string[pos..-1] - d.pos = string.size + if limit + line = string.byteslice(pos, limit) + else + line = string.byteslice(pos, string.bytesize - pos) + end + d.pos += line.bytesize end end d.lineno += 1