# MacRuby implementation of stringio. # # This file is covered by the Ruby license. See COPYING for more details. # # Copyright (C) 2012, The MacRuby Team. All rights reserved. # Copyright (C) 2009-2011, Apple Inc. All rights reserved. class StringIO attr_reader :string, :pos # strio.lineno -> integer # # Returns the current line number in *strio*. The stringio 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 $. variable. # # # strio.lineno = integer -> integer # # Manually sets the current line number to the given value. # $. is updated only on the next read. # attr_accessor :lineno include Enumerable # StringIO.open(string=""[, mode]) {|strio| ...} # # 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. # def self.open(*args) obj = new(*args) if block_given? begin yield obj ensure obj.close obj.instance_variable_set(:@string, nil) obj end else obj end end # StringIO.new(string=""[, mode]) # # Creates new StringIO instance from with _string_ and _mode_. # def initialize(string = String.new, mode = nil) @string = string.to_str @pos = 0 @lineno = 0 define_mode(mode) raise Errno::EACCES if (@writable && string.frozen?) self end def initialize_copy(from) from = from.to_strio self.taint if from.tainted? @string = from.instance_variable_get(:@string).dup # mode @append = from.instance_variable_get(:@append) @readable = from.instance_variable_get(:@readable) @writable = from.instance_variable_get(:@writable) @pos = from.instance_variable_get(:@pos) @lineno = from.instance_variable_get(:@lineno) self end # strio.reopen(other_StrIO) -> strio # strio.reopen(string, mode) -> strio # # Reinitializes *strio* with the given other_StrIO or _string_ # and _mode_ (see StringIO#new). # def reopen(str=nil, mode=nil) if str == nil && mode == nil mode = 'w+' elsif !str.kind_of?(String) && mode == nil raise TypeError unless str.respond_to?(:to_strio) return initialize_copy(str) else raise TypeError unless str.respond_to?(:to_str) @string = str.to_str end define_mode(mode) @pos = 0 @lineno = 0 self end # strio.string = string -> string # # Changes underlying String object, the subject of IO. # def string=(str) @string = str.to_str @pos = 0 @lineno = 0 end # strio.rewind -> 0 # # Positions *strio* to the beginning of input, resetting # +lineno+ to zero. # def rewind @pos = 0 @lineno = 0 end # strio.read([length [, buffer]]) -> string, buffer, or nil # # See IO#read. # def read(length = nil, buffer = String.new) raise IOError, "not opened for reading" unless @readable raise TypeError unless buffer.respond_to?(:to_str) buffer = buffer.to_str if length.nil? return buffer.replace("") if self.eof? buffer.replace(@string[@pos..-1]) @pos = @string.size else raise TypeError unless length.respond_to?(:to_int) length = length.to_int raise ArgumentError if length < 0 if length == 0 buffer.replace("") else if self.eof? buffer.replace("") return nil end buffer.replace(@string[@pos, length]) @pos += buffer.length end buffer.force_encoding('BINARY') end buffer end # strio.sysread(integer[, outbuf]) -> string # # Similar to #read, but raises +EOFError+ at end of string instead of # returning +nil+, as well as IO#sysread does. def sysread(length = nil, buffer = String.new) val = read(length, buffer) ( buffer.clear && raise(IO::EOFError, "end of file reached")) if val == nil val end alias_method :readpartial, :sysread alias_method :read_nonblock, :sysread # strio.readbyte -> fixnum # # See IO#readbyte. # def readbyte raise(IO::EOFError, "end of file reached") if eof? getbyte end # strio.seek(amount, whence=SEEK_SET) -> 0 # # Seeks to a given offset _amount_ in the stream according to # the value of _whence_ (see IO#seek). # def seek(offset, whence = ::IO::SEEK_SET) raise(IOError, "closed stream") if closed? raise TypeError unless offset.respond_to?(:to_int) offset = offset.to_int case whence when ::IO::SEEK_CUR # Seeks to offset plus current position offset += @pos when ::IO::SEEK_END # Seeks to offet plus end of stream (usually offset is a negative value) offset += @string.size when ::IO::SEEK_SET, nil # Seeks to the absolute location given by offset else raise Errno::EINVAL, "invalid whence" end raise Errno::EINVAL if (offset < 0) @pos = offset 0 end # strio.pos = integer -> integer # # Seeks to the given position (in bytes) in *strio*. # def pos=(pos) raise Errno::EINVAL if pos < 0 @pos = pos end # strio.closed? -> true or false # # Returns +true+ if *strio* is completely closed, +false+ otherwise. # def closed? !@readable && !@writable end # strio.close -> nil # # Closes strio. The *strio* is unavailable for any further data # operations; an +IOError+ is raised if such an attempt is made. # def close raise(IOError, "closed stream") if closed? @readable = @writable = nil end # strio.closed_read? -> true or false # # Returns +true+ if *strio* is not readable, +false+ otherwise. # def closed_read? !@readable end # strio.close_read -> nil # # Closes the read end of a StringIO. Will raise an +IOError+ if the # *strio* is not readable. # def close_read raise(IOError, "closing non-duplex IO for reading") unless @readable @readable = nil end # strio.closed_write? -> true or false # # Returns +true+ if *strio* is not writable, +false+ otherwise. # def closed_write? !@writable end # strio.eof -> true or false # strio.eof? -> true or false # # Returns true if *strio* is at end of file. The stringio must be # opened for reading or an +IOError+ will be raised. # def eof? raise(IOError, "not opened for reading") unless @readable @pos >= @string.length end alias_method :eof, :eof? def binmode self end def fcntl raise NotImplementedError, "StringIO#fcntl is not implemented" end def flush self end def fsync 0 end # strio.pid -> nil # def pid nil end # strio.sync -> true # # Returns +true+ always. # def sync true end # strio.sync = boolean -> boolean # def sync=(value) value end def tell @pos end # strio.fileno -> nil # def fileno nil end # strio.isatty -> nil # strio.tty? -> nil def isatty false end alias_method :tty?, :isatty def length @string.length end alias_method :size, :length # strio.getc -> string or nil # # Gets the next character from io. # Returns nil if called at end of file def getc raise(IOError, "not opened for reading") unless @readable return nil if eof? result = @string[@pos] @pos += 1 result end # strio.ungetc(string) -> nil # # Pushes back one character (passed as a parameter) onto *strio* # such that a subsequent buffered read will return it. Pushing back # behind the beginning of the buffer string is not possible. Nothing # will be done if such an attempt is made. # In other case, there is no limitation for multiple pushbacks. # def ungetc(chars) raise(IOError, "not opened for reading") unless @readable return nil if chars == nil chars = chars.chr if chars.kind_of?(Fixnum) raise TypeError unless chars.respond_to?(:to_str) chars = chars.to_str if @pos == 0 @string = chars + @string elsif @pos > 0 @pos -= 1 @string[@pos] = chars end nil end # strio.ungetbyte(fixnum) -> nil # # See IO#ungetbyte # def ungetbyte(bytes) raise(IOError, "not opened for reading") unless @readable return nil if bytes == nil bytes = bytes.chr if bytes.kind_of?(Fixnum) raise TypeError unless bytes.respond_to?(:to_str) bytes = bytes.to_str if @pos == 0 @string = bytes + @string elsif @pos > 0 @pos -= 1 @string[@pos] = bytes end nil end # strio.readchar -> fixnum # # See IO#readchar. # def readchar raise(IO::EOFError, "end of file reached") if eof? getc end # strio.each_char {|char| block } -> strio # # See IO#each_char. # def each_char raise(IOError, "not opened for reading") unless @readable if block_given? @string.each_char{|c| yield(c)} self else @string.each_char end end alias_method :chars, :each_char # strio.getbyte -> fixnum or nil # # See IO#getbyte. def getbyte raise(IOError, "not opened for reading") unless @readable # Because we currently don't support bytes access # the following code isn't used # instead we are dealing with chars result = @string.bytes.to_a[@pos] @pos += 1 unless eof? result # getc end # strio.each_byte {|byte| block } -> strio # # See IO#each_byte. # def each_byte raise(IOError, "not opened for reading") unless @readable return self if (@pos > @string.length) if block_given? @string.each_byte{|b| @pos += 1; yield(b)} self else @string.each_byte end end alias_method :bytes, :each_byte # strio.each(sep=$/) {|line| block } -> strio # strio.each(limit) {|line| block } -> strio # strio.each(sep, limit) {|line| block } -> strio # strio.each_line(sep=$/) {|line| block } -> strio # strio.each_line(limit) {|line| block } -> strio # strio.each_line(sep,limit) {|line| block } -> strio # # See IO#each. # def each(sep=$/, limit=nil) if block_given? raise(IOError, "not opened for reading") unless @readable sep, limit = getline_args(sep, limit) if limit == 0 raise ArgumentError, "invalid limit: 0 for each and family" end while line = getline(sep, limit) yield(line) end self else to_enum(:each, sep, limit) end end alias_method :each_line, :each alias_method :lines, :each # strio.gets(sep=$/) -> string or nil # strio.gets(limit) -> string or nil # strio.gets(sep, limit) -> string or nil # # See IO#gets. # def gets(sep=$/, limit=nil) sep, limit = getline_args(sep, limit) $_ = getline(sep, limit) end # strio.readline(sep=$/) -> string # strio.readline(limit) -> string or nil # strio.readline(sep, limit) -> string or nil # # See IO#readline. def readline(sep=$/, limit=nil) raise(IO::EOFError, 'end of file reached') if eof? sep, limit = getline_args(sep, limit) $_ = getline(sep, limit) end # strio.readlines(sep=$/) -> array # strio.readlines(limit) -> array # strio.readlines(sep,limit) -> array # # See IO#readlines. # def readlines(sep=$/, limit=nil) raise IOError, "not opened for reading" unless @readable ary = [] sep, limit = getline_args(sep, limit) if limit == 0 raise ArgumentError, "invalid limit: 0 for readlines" end while line = getline(sep, limit) ary << line end ary end # strio.write(string) -> integer # strio.syswrite(string) -> integer # # Appends the given string to the underlying buffer string of *strio*. # The stream must be opened for writing. If the argument is not a # string, it will be converted to a string using to_s. # Returns the number of bytes written. See IO#write. # def write(str) raise(IOError, "not opened for writing") unless @writable raise(IOError, "not modifiable string") if @string.frozen? str = str.to_s return 0 if str.empty? if @append || (@pos >= @string.length) # add padding in case it's needed str = str.rjust((@pos + str.length) - @string.length, "\000") if (@pos > @string.length) enc1, enc2 = str.encoding, @string.encoding if enc1 != enc2 str = str.dup.force_encoding(enc2) end @string << str @pos = @string.length else @string[@pos, str.length] = str @pos += str.length @string.taint if str.tainted? end str.length end alias_method :syswrite, :write alias_method :write_nonblock, :write # strio << obj -> strio # # See IO#<<. # def <<(str) self.write(str) self end def close_write raise(IOError, "closing non-duplex IO for writing") unless @writable @writable = nil end # strio.truncate(integer) -> 0 # # Truncates the buffer string to at most _integer_ bytes. The *strio* # must be opened for writing. # def truncate(len) raise(IOError, "closing non-duplex IO for writing") unless @writable raise(TypeError) unless len.respond_to?(:to_int) length = len.to_int raise(Errno::EINVAL, "negative length") if (length < 0) if length < @string.size @string[length .. @string.size] = "" else @string = @string.ljust(length, "\000") end # send back what was passed, not our :to_int version len end # strio.puts(obj, ...) -> nil # # Writes the given objects to strio as with # IO#print. Writes a record separator (typically a # newline) after any that do not already end with a newline sequence. # If called with an array argument, writes each element on a new line. # If called without arguments, outputs a single record separator. # # io.puts("this", "is", "a", "test") # # produces: # # this # is # a # test # def puts(*args) if args.empty? write("\n") else args.each do |arg| if arg == nil line = "nil" else begin arg = arg.to_ary recur = false arg.each do |a| if arg == a recur = true break else puts a end end next unless recur line = '[...]' rescue line = arg.to_s end end write(line) write("\n") if line[-1] != ?\n end end nil end # strio.putc(obj) -> obj # # If obj is Numeric, write the character whose # code is obj, otherwise write the first character of the # string representation of obj to strio. # def putc(obj) raise(IOError, "not opened for writing") unless @writable if obj.is_a?(String) char = obj[0] else raise TypeError unless obj.respond_to?(:to_int) char = obj.to_int % 256 end if @append || @pos == @string.length @string << char @pos = @string.length elsif @pos > @string.length @string = @string.ljust(@pos, "\000") @string << char @pos = @string.length else @string[@pos] = ("" << char) @pos += 1 end obj end # strio.print() => nil # strio.print(obj, ...) => nil # # Writes the given object(s) to strio. The stream must be # opened for writing. If the output record separator ($\\) # is not nil, it will be appended to the output. If no # arguments are given, prints $_. Objects that aren't # strings will be converted by calling their to_s method. # With no argument, prints the contents of the variable $_. # Returns nil. # # io.print("This is ", 100, " percent.\n") # # produces: # # This is 100 percent. # def print(*args) raise IOError, "not opened for writing" unless @writable args << $_ if args.empty? args.map! { |x| (x == nil) ? "nil" : x } write((args << $\).flatten.join) nil end # printf(strio, string [, obj ... ] ) => nil # # Equivalent to: # strio.write(sprintf(string, obj, ...) # def printf(*args) raise IOError, "not opened for writing" unless @writable if args.size > 1 write(args.shift % args) else write(args.first) end nil end def external_encoding @string ? @string.encoding : nil end def internal_encoding nil end def set_encoding(enc) @string = @string.encode(enc) self end protected # meant to be overwritten by developers def to_strio self end def define_mode(mode=nil) if mode == nil # default modes @string.frozen? ? set_mode_from_string("r") : set_mode_from_string("r+") elsif mode.is_a?(Integer) set_mode_from_integer(mode) else mode = mode.to_str set_mode_from_string(mode) end end def set_mode_from_string(mode) @readable = @writable = @append = false case mode when "r", "rb" @readable = true when "r+", "rb+" raise(Errno::EACCES) if @string.frozen? @readable = true @writable = true when "w", "wb" @string.frozen? ? raise(Errno::EACCES) : @string.replace("") @writable = true when "w+", "wb+" @string.frozen? ? raise(Errno::EACCES) : @string.replace("") @readable = true @writable = true when "a", "ab" raise(Errno::EACCES) if @string.frozen? @writable = true @append = true when "a+", "ab+" raise(Errno::EACCES) if @string.frozen? @readable = true @writable = true @append = true end end def set_mode_from_integer(mode) @readable = @writable = @append = false case mode & (IO::RDONLY | IO::WRONLY | IO::RDWR) when IO::RDONLY @readable = true @writable = false when IO::WRONLY @readable = false @writable = true raise(Errno::EACCES) if @string.frozen? when IO::RDWR @readable = true @writable = true raise(Errno::EACCES) if @string.frozen? end @append = true if (mode & IO::APPEND) != 0 raise(Errno::EACCES) if @append && @string.frozen? @string.replace("") if (mode & IO::TRUNC) != 0 end def getline(sep = $/, lim = nil) raise(IOError, "not opened for reading") unless @readable return nil if eof? offset = limit = -1 if lim != nil limit = lim - 1 offset = @pos + limit end if lim != nil && lim == 0 line = "" elsif sep == nil line = @string[@pos .. offset] elsif sep.empty? while @string[@pos] == ?\n @pos += 1 limit -= 1 end if stop = @string.index("\n\n", @pos) stop += 1 line = @string[@pos .. stop] if lim != nil && line[limit, 2] != "\n\n" line = line[0 .. limit] end else line = @string[@pos .. offset] end else if stop = @string.index(sep, @pos) line = @string[@pos .. (stop + sep.size - 1)] else line = @string[@pos .. offset] end end @pos += line.size @lineno += 1 line end def getline_args(sep = $/, lim = nil) if lim == nil && !sep.kind_of?(String) if !sep.respond_to?(:to_str) lim = sep sep = nil end end if lim != nil raise TypeError unless lim.respond_to?(:to_int) lim = lim.to_int end sep = sep.to_str unless (sep == nil) return sep, lim end end