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