lib/motion-csv/stringio.rb
# 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 <code>$.</code> variable.
#
#
# strio.lineno = integer -> integer
#
# Manually sets the current line number to the given value.
# <code>$.</code> 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 <i>other_StrIO</i> 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 <code>to_s</code>.
# 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 <em>strio</em> as with
# <code>IO#print</code>. 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")
#
# <em>produces:</em>
#
# 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 <i>obj</i> is <code>Numeric</code>, write the character whose
# code is <i>obj</i>, otherwise write the first character of the
# string representation of <i>obj</i> to <em>strio</em>.
#
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 <em>strio</em>. The stream must be
# opened for writing. If the output record separator (<code>$\\</code>)
# is not <code>nil</code>, it will be appended to the output. If no
# arguments are given, prints <code>$_</code>. Objects that aren't
# strings will be converted by calling their <code>to_s</code> method.
# With no argument, prints the contents of the variable <code>$_</code>.
# Returns <code>nil</code>.
#
# io.print("This is ", 100, " percent.\n")
#
# <em>produces:</em>
#
# 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