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