lib/stringio.rb in rubysl-stringio-2.0.0 vs lib/stringio.rb in rubysl-stringio-2.1
- old
+ new
@@ -1,741 +1,1482 @@
-require "rubysl/stringio"
+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, :encoding
+
+ def initialize(string)
+ @string = string
+ @encoding = string.encoding
+ @pos = @lineno = 0
+ end
+ end
+
+ def self.open(*args)
+ io = new(*args)
+ return io unless block_given?
+
+ begin
+ yield io
+ ensure
+ io.close
+ io.__data__.string = nil
+ self
+ end
+ end
+
+ attr_reader :__data__
+
+ 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
+ mode = StringValue(mode)
+ mode_from_string(mode)
+ end
+ else
+ mode_from_string(string.frozen? ? "r" : "r+")
+ end
+
+ self
+ end
+
+ def initialize_copy(from)
+ from = Rubinius::Type.coerce_to(from, StringIO, :to_strio)
+
+ taint if from.tainted?
+
+ @append = from.instance_variable_get(:@append)
+ @readable = from.instance_variable_get(:@readable)
+ @writable = from.instance_variable_get(:@writable)
+ @__data__ = from.instance_variable_get(:@__data__)
+
+ self
+ end
+
+ def check_readable
+ raise IOError, "not opened for reading" unless @readable
+ end
+
+ private :check_readable
+
+ def check_writable
+ raise IOError, "not opened for writing" unless @writable
+ 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)
+ self
+ 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__
+ string = d.string
+
+ while d.pos < string.length
+ byte = string.getbyte d.pos
+ d.pos += 1
+ yield byte
+ end
+
+ self
+ end
+
+ alias_method :bytes, :each_byte
+
+ def each_char
+ return to_enum :each_char unless block_given?
+ while s = getc
+ yield s
+ end
+
+ self
+ end
+
+ alias_method :chars, :each_char
+
+ def each_codepoint(&block)
+ return to_enum :each_codepoint unless block_given?
+ check_readable
+
+ 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
+
+ alias_method :each_line, :each
+ alias_method :lines, :each
+
+ def <<(str)
+ write(str)
+ self
+ end
+
+ def binmode
+ self
+ end
+
+ 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.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
+ 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
+
+ str.bytesize
+ end
+ alias_method :syswrite, :write
+ alias_method :write_nonblock, :write
+
+ def close
+ raise IOError, "closed stream" if closed?
+ @readable = @writable = nil
+ end
+
+ def closed?
+ !@readable && !@writable
+ end
+
+ def close_read
+ check_readable
+ @readable = nil
+ end
+
+ def closed_read?
+ !@readable
+ end
+
+ def close_write
+ check_writable
+ @writable = nil
+ end
+
+ def closed_write?
+ !@writable
+ end
+
+ def eof?
+ d = @__data__
+ d.pos >= d.string.bytesize
+ end
+ alias_method :eof, :eof?
+
+ def fcntl
+ raise NotImplementedError, "StringIO#fcntl is not implemented"
+ end
+
+ def fileno
+ nil
+ end
+
+ def flush
+ self
+ end
+
+ def fsync
+ 0
+ end
+
+ def getc
+ check_readable
+ d = @__data__
+
+ return nil if eof?
+
+ char = d.string.find_character(d.pos)
+ d.pos += char.bytesize
+ char
+ end
+
+ def getbyte
+ check_readable
+ d = @__data__
+
+ return nil if eof?
+
+ byte = d.string.getbyte(d.pos)
+ d.pos += 1
+ byte
+ end
+
+ def gets(sep=$/, limit=Undefined)
+ check_readable
+
+ $_ = getline(false, sep, limit)
+ end
+
+ def isatty
+ false
+ end
+ alias_method :tty?, :isatty
+
+ def lineno
+ @__data__.lineno
+ end
+
+ def lineno=(line)
+ @__data__.lineno = line
+ end
+
+ def pid
+ nil
+ end
+
+ def pos
+ @__data__.pos
+ end
+
+ def pos=(pos)
+ raise Errno::EINVAL if pos < 0
+ @__data__.pos = pos
+ end
+
+ def print(*args)
+ check_writable
+ args << $_ if args.empty?
+ write((args << $\).flatten.join)
+ nil
+ end
+
+ def printf(*args)
+ check_writable
+
+ if args.size > 1
+ write(args.shift % args)
+ else
+ write(args.first)
+ end
+
+ nil
+ end
+
+ def putc(obj)
+ check_writable
+
+ if obj.is_a?(String)
+ char = obj[0]
+ else
+ 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.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
+ m = Rubinius::Mirror.reflect string
+ m.splice pos, char.bytesize, char
+ d.pos += char.bytesize
+ end
+
+ obj
+ end
+
+ def puts(*args)
+ if args.empty?
+ write(DEFAULT_RECORD_SEPARATOR)
+ else
+ args.each do |arg|
+ if arg.nil?
+ line = ""
+ elsif Thread.guarding? arg
+ line = "[...]"
+ else
+ begin
+ arg = Rubinius::Type.coerce_to(arg, Array, :to_ary)
+ Thread.recursion_guard arg do
+ arg.each { |a| puts a }
+ end
+ next
+ rescue
+ line = arg.to_s
+ end
+ end
+
+ write(line)
+ write(DEFAULT_RECORD_SEPARATOR) unless line[-1] == ?\n
+ end
+ end
+
+ nil
+ end
+
+ def read(length=nil, buffer=nil)
+ check_readable
+ d = @__data__
+ pos = d.pos
+ string = d.string
+
+ if length
+ length = Rubinius::Type.coerce_to length, Integer, :to_int
+ raise ArgumentError if length < 0
+
+ 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
+ if eof?
+ buffer.clear if buffer
+ return "".force_encoding(Encoding::ASCII_8BIT)
+ end
+
+ str = string.byteslice(pos..-1)
+ buffer.replace str if buffer
+ end
+
+ d.pos += str.length
+ return str
+ end
+
+ def readchar
+ raise IO::EOFError, "end of file reached" if eof?
+ getc
+ end
+
+ def readbyte
+ readchar.getbyte(0)
+ end
+
+ def readline(sep=$/, limit=Undefined)
+ check_readable
+ raise IO::EOFError, "end of file reached" if eof?
+
+ $_ = getline(true, sep, limit)
+ end
+
+ def readlines(sep=$/, limit=Undefined)
+ check_readable
+
+ ary = []
+ while line = getline(true, sep, limit)
+ ary << line
+ end
+
+ ary
+ end
+
+ def reopen(string=nil, mode=Undefined)
+ if string and not string.kind_of? String and mode.equal? Undefined
+ stringio = Rubinius::Type.coerce_to(string, StringIO, :to_strio)
+
+ taint if stringio.tainted?
+ initialize_copy stringio
+ else
+ mode = nil if mode.equal? Undefined
+ string = "" unless string
+
+ initialize string, mode
+ end
+
+ self
+ end
+
+ def rewind
+ d = @__data__
+ d.pos = d.lineno = 0
+ end
+
+ def seek(to, whence = IO::SEEK_SET)
+ raise IOError, "closed stream" if self.closed?
+ to = Rubinius::Type.coerce_to to, Integer, :to_int
+
+ case whence
+ when IO::SEEK_CUR
+ to += @__data__.pos
+ when IO::SEEK_END
+ to += @__data__.string.bytesize
+ when IO::SEEK_SET, nil
+ else
+ raise Errno::EINVAL, "invalid whence"
+ end
+
+ raise Errno::EINVAL if to < 0
+
+ @__data__.pos = to
+
+ return 0
+ end
+
+ def size
+ @__data__.string.bytesize
+ end
+ alias_method :length, :size
+
+ def string
+ @__data__.string
+ end
+
+ def string=(string)
+ d = @__data__
+ d.string = StringValue(string)
+ d.pos = 0
+ d.lineno = 0
+ end
+
+ def sync
+ true
+ end
+
+ def sync=(val)
+ val
+ end
+
+ def sysread(length=nil, buffer="")
+ str = read(length, buffer)
+
+ 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.bytesize
+ string[len..string.bytesize] = ""
+ else
+ string << "\000" * (len - string.bytesize)
+ end
+ return length
+ end
+
+ def ungetc(char)
+ check_readable
+
+ d = @__data__
+ pos = d.pos
+ string = d.string
+
+ 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.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)
+ @__data__ = Data.new("")
+ end
+
+ protected
+
+ def mode_from_string(mode)
+ @append = truncate = false
+
+ if mode[0] == ?r
+ @readable = true
+ @writable = mode[-1] == ?+ ? true : false
+ end
+
+ if mode[0] == ?w
+ @writable = truncate = true
+ @readable = mode[-1] == ?+ ? true : false
+ end
+
+ if mode[0] == ?a
+ @append = @writable = true
+ @readable = mode[-1] == ?+ ? true : false
+ end
+
+ d = @__data__
+ raise Errno::EACCES, "Permission denied" if @writable && d.string.frozen?
+ d.string.replace("") if truncate
+ end
+
+ def mode_from_integer(mode)
+ @readable = @writable = @append = false
+ d = @__data__
+
+ if mode == 0 or mode & IO::RDWR != 0
+ @readable = true
+ end
+
+ if mode & (IO::WRONLY | IO::RDWR) != 0
+ raise Errno::EACCES, "Permission denied" if d.string.frozen?
+ @writable = true
+ end
+
+ @append = true if (mode & IO::APPEND) != 0
+ d.string.replace("") if (mode & IO::TRUNC) != 0
+ end
+
+ def getline(arg_error, sep, limit)
+ if limit != Undefined
+ limit = Rubinius::Type.coerce_to limit, Fixnum, :to_int if limit
+ 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?
+ 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.find_string("\n\n", pos)
+ stop += 2
+ line = string.byteslice(pos, stop - pos)
+ while string.getbyte(stop) == 10
+ stop += 1
+ end
+ d.pos = stop
+ else
+ line = string.byteslice(pos, string.bytesize - pos)
+ d.pos = string.bytesize
+ end
+ else
+ 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
+ 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
+
+ return line
+ end
+end