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