core/argf.rb
module Rubinius
# :internal:
#
# The virtual concatenation file of the files given on command line (or
# from $stdin if no files were given.)
#
# The only instance hereof is the object referred to by ARGF.
#
# @see ARGF
#
class ARGFClass
include Enumerable
# :internal:
#
# Create a stateless ARGF.
#
# The actual setup is done on the fly:
#
# @see #advance!
#
def initialize
@lineno = 0
@advance = true
@init = false
@use_stdin_only = false
@encoding_args = nil
end
private :initialize
#
# Set stream into binary mode.
#
# Stream is set into binary mode, i.e. 8-bit ASCII.
# Once set, the binary mode cannot be undone. Returns
# self.
#
def binmode
@binmode = true
@external = Encoding::ASCII_8BIT
self
end
def binmode?
@binmode
end
#
# Close stream.
#
def close
advance!
@stream.close
@advance = true unless @use_stdin_only
@lineno = 0
@binmode = false
@external = nil
self
end
#
# True if the stream is closed.
#
def closed?
advance!
@stream.closed?
end
def default_value
"".encode(encoding)
end
#
# Linewise iteration.
#
# Yields one line from stream at a time, as given by
# #gets. An Enumerator is returned if no block is
# provided. Returns nil if no content, self otherwise.
#
# @see #gets.
#
def each_line(sep=$/)
return to_enum :each_line, sep unless block_given?
return nil unless advance!
while line = gets(sep)
yield line
end
self
end
alias_method :lines, :each_line
alias_method :each, :each_line
#
# Bytewise iteration.
#
# Yields one byte at a time from stream, an Integer
# as given by #getc. An Enumerator is returned if no
# block is provided. Returns self.
#
# @see #getc
#
def each_byte
return to_enum :each_byte unless block_given?
while ch = getbyte()
yield ch
end
self
end
alias_method :bytes, :each_byte
#
# Character-wise iteration.
#
# Yields one character at a time from stream. An
# Enumerator is returned if no block is provided.
# Returns self.
#
# The characters yielded are gotten from #getc.
#
# @see #getc
#
def each_char
return to_enum :each_char unless block_given?
while c = getc()
yield c.chr
end
self
end
alias_method :chars, :each_char
def each_codepoint
return to_enum :each_codepoint unless block_given?
while c = getc
yield c.ord
end
self
end
alias_method :codepoints, :each_codepoint
def encoding
@external || Encoding.default_external
end
#
# Query whether stream is at end-of-file.
#
# True if there is a stream and it is in EOF
# status.
#
def eof?
@stream and @stream.eof?
end
alias_method :eof, :eof?
#
# File descriptor number for stream.
#
# Returns a file descriptor number for the stream being
# read out of.
#
# @todo Check correctness, does this imply there may be
# multiple FDs and if so, is this correct? --rue
#
def fileno
raise ArgumentError, "No stream" unless advance!
@stream.fileno
end
alias_method :to_i, :fileno
#
# File path currently in use.
#
# Path to file from which read currently is
# occurring, or an indication that the stream
# is STDIN.
#
def filename
advance!
@filename
end
alias_method :path, :filename
#
# Current stream object.
#
# This may change during the course of execution,
# but is the current one!
#
def file
advance!
@stream
end
def getbyte
while true
return nil unless advance!
if val = @stream.getbyte
return val
end
return nil if @use_stdin_only
@stream.close unless @stream.closed?
@advance = true
end
end
#
# Return one character from stream.
#
# If a character cannot be returned and we are
# reading from a file, the stream is closed.
#
def getc
while true
return nil unless advance!
if val = @stream.getc
return val
end
return nil if @use_stdin_only
@stream.close unless @stream.closed?
@advance = true
end
end
#
# Return next line of text from stream.
#
# If a line cannot be returned and we are
# reading from a file, the stream is closed.
#
# The mechanism does track the line numbers,
# and updates $. accordingly.
#
def gets(sep=$/)
while true
return nil unless advance!
line = @stream.gets(sep)
unless line
return nil if @use_stdin_only
@stream.close unless @stream.closed?
@advance = true
next
end
@lineno += 1
$. = @lineno
return line
end
end
#
# Return current line number.
#
# Line numbers are maintained when using the linewise
# access methods.
#
# @see #gets
# @see #each_line
#
attr_reader :lineno
#
# Set current line number.
#
# Also sets $. accordingly.
#
# @todo Should this be public? --rue
#
def lineno=(val)
$. = @lineno = val
end
#
# Return stream position for seeking etc.
#
# @see IO#pos.
#
def pos
raise ArgumentError, "no stream" unless advance!
@stream.tell
end
alias_method :tell, :pos
#
# Set stream position to a previously obtained position.
#
# @see IO#pos=
#
def pos=(position)
raise ArgumentError, "no stream" unless advance!
@stream.pos = position
end
#
# Read a byte from stream.
#
# Similar to #getc, but raises an EOFError if
# EOF has been reached.
#
# @see #getc
#
def readbyte
advance!
if val = getc()
return val
end
raise EOFError, "ARGF at end"
end
alias_method :readchar, :readbyte
#
# Read number of bytes or all, optionally into buffer.
#
# If number of bytes is not given or is nil, tries to read
# all of the stream, which is then closed. If the number is
# specified, then at most that many bytes will be read.
#
# A buffer responding to #<< may be provided as the second
# argument. The data read is pushed into it. If no buffer
# is provided, as by default, a String with the data is
# returned instead.
#
def read(bytes=nil, output=nil)
# The user might try to pass in nil, so we have to check here
output ||= default_value
output.clear
if bytes
bytes_left = bytes
until bytes_left == 0
return output unless advance!
if res = @stream.read(bytes_left)
output << res
bytes_left -= res.size
else
break if @use_stdin_only
@stream.close unless @stream.closed?
@advance = true
end
end
return output
end
while advance!
output << @stream.read
break if @use_stdin_only
@stream.close unless @stream.closed?
@advance = true
end
output
end
def read_nonblock(maxlen, output = nil, exception: true)
output ||= default_value
unless advance!
output.clear
raise EOFError, "ARGF at end"
end
begin
out = @stream.read_nonblock(maxlen, output, exception: exception)
return out if out == :wait_readable
rescue EOFError => e
raise e if @use_stdin_only
@stream.close
@advance = true
advance! or raise e
end
return output
end
#
# Read next line of text.
#
# As #gets, but an EOFError is raised if the stream
# is at EOF.
#
# @see #gets
#
def readline(sep=$/)
raise EOFError, "ARGF at end" unless advance!
if line = gets(sep)
return line
end
raise EOFError, "ARGF at end"
end
#
# Read all lines from stream.
#
# Reads all lines into an Array using #gets and
# returns the Array.
#
# @see #gets
#
def readlines(sep=$/)
return [] unless advance!
lines = []
while line = gets(sep)
lines << line
end
lines
end
alias_method :to_a, :readlines
def readpartial(maxlen, output=nil)
output ||= default_value
unless advance!
output.clear
raise EOFError, "ARGF at end"
end
begin
@stream.readpartial(maxlen, output)
rescue EOFError => e
raise e if @use_stdin_only
@stream.close
@advance = true
advance! or raise e
end
return output
end
#
# Rewind the stream to its beginning.
#
# Line number is updated accordingly.
#
# @todo Is this correct, only current stream is rewound? --rue
#
def rewind
raise ArgumentError, "no stream to rewind" unless advance!
@lineno -= @stream.lineno
@stream.rewind
end
#
# Seek into a previous position in the stream.
#
# @see IO#seek.
#
def seek(*args)
raise ArgumentError, "no stream" unless advance!
@stream.seek(*args)
end
def set_encoding(*args)
@encoding_args = args
if @stream and !@stream.closed?
@stream.set_encoding *args
end
end
#
# Close file stream and return self.
#
# STDIN is not closed if being used, otherwise the
# stream gets closed. Returns self.
#
def skip
return self if @use_stdin_only
@stream.close unless @stream.closed?
@advance = true
self
end
def stream(file)
stream = file == "-" ? STDIN : File.open(file, "r", :external_encoding => encoding)
if @encoding_args
stream.set_encoding *@encoding_args
elsif encoding
stream.set_encoding encoding
end
stream
end
#
# Return IO object for current stream.
#
# @see IO#to_io
#
def to_io
advance!
@stream.to_io
end
#
# Returns "ARGF" as the string representation of this object.
#
def to_s
"ARGF"
end
# Internals
#
# Main processing.
#
# If not initialised yet, sets the object up on either
# first of provided file names or STDIN.
#
# Does nothing further or later if using STDIN, but if
# there are further file names in ARGV, tries to open
# the next one as the current stream.
#
def advance!
return true unless @advance
unless @init
if ARGV.empty?
@advance = false
@stream = STDIN
@filename = "-"
@use_stdin_only = true
return true
end
@init = true
end
File.unlink(@backup_filename) if @backup_filename && $-i == ""
return false if @use_stdin_only || ARGV.empty?
@advance = false
file = ARGV.shift
@stream = stream(file)
@filename = file
if $-i && @stream != STDIN
backup_extension = $-i == "" ? ".bak" : $-i
@backup_filename = "#{@filename}#{backup_extension}"
File.rename(@filename, @backup_filename)
@stream = File.open(@backup_filename, "r")
$stdout = File.open(@filename, "w")
end
return true
end
private :advance!
end
end