From 0f02c2aec38602ff4d76ffc8e50b1a7c8aef02f0 Mon Sep 17 00:00:00 2001 From: Laurent Julliard Date: Sat, 19 Jul 2008 22:36:48 +0200 Subject: [PATCH] First implementation of the ARGF class * ARGF is implemented as the unique instance of the ARGFClass as seen with evan as brixen * Everything works in argf.rb except a) the in place edit mode and b) a couple of limitations in the spec due to the fact that IO#reopen doesn't work on STDIn as opposed to MRI * Fixed a bug in the setter of hooked global variables (validated by brixen as well) --- kernel/core/argf.rb | 362 ++++++++++++++++++++++++++++++++++++++++++++++++- kernel/core/global.rb | 4 +- kernel/core/io.rb | 9 +- kernel/loader.rb | 4 + 4 files changed, 369 insertions(+), 10 deletions(-) diff --git a/kernel/core/argf.rb b/kernel/core/argf.rb index 4aee667..cc7e24f 100644 --- a/kernel/core/argf.rb +++ b/kernel/core/argf.rb @@ -1,8 +1,362 @@ -# depends on: class.rb +# depends on: class.rb object.rb enumerable.rb range.rb global.rb array.rb -## +# # The virtual concatenation file of the files given on command line (or from -# $stdin if no files were given). +# $stdin if no files were given or filename is '-'). +# +# Written by Laurent Julliard, Nuxos Group +# + +class ARGFClass + + include Enumerable + + attr_accessor :gets_lineno + + DEFAULT_RS = $/ + ORIG_STDOUT = STDOUT + + def initialize + @gets_lineno = 0 + @next_p = 0 + @init_p = 0 + @current_file = nil + @binmode = false + end + + # to_s : no need to define this class method + # the Class class already returns the class name as a string + + def fileno + raise ArgumentError, "no stream" unless _next_argv() + @current_file.fileno + end + alias_method :to_i, :fileno + + def to_io + _next_argv() + @current_file.to_io + end + + def each_line(&block) + return nil unless _next_argv() + while str = _getline() + yield str + end + self + end + alias_method :each, :each_line + + def each_byte + while (byte = getc()) + yield(byte) + end + self + end + + def read(length=nil, str=nil) + len = length || 0 + str.replace '' if str # reset the buffer string if given + + begin + reetry = false + return str unless _next_argv() + + tmp = @current_file.read(length ? len : nil) + + if str.nil? + str = tmp + elsif tmp + str << tmp + end + + if tmp.nil? || length.nil? + if @next_p != -1 + _close(@current_file) + @next_p = 1 + reetry = true + end + elsif length + if str.length < len + len -= str.length + reetry = true + end + end + + end while reetry + str + end + + def readlines + return nil unless _next_argv() + ary = [] + while (line = _getline()) + ary << line + end + ary + end + alias_method :to_a, :readlines + + def gets + return nil unless _next_argv() + line = _getline() + $_ = line + line + end + + def readline + raise EOFError unless _next_argv() + line = _getline() + raise EOFError if line.nil? + line + end + + def getc + begin + reetry = false + return nil unless _next_argv() + + byte = @current_file.getc + + if (byte.nil? && @next_p != -1) + _close(@current_file) + @next_p = 1 + reetry = true + end + end while reetry + byte + end + + def readchar + c = getc() + raise EOFError if c.nil? + c + end + + def tell + raise ArgumentError, "no stream to tell" unless _next_argv() + @current_file.tell + end + alias_method :pos, :tell + + def seek(int, whence=IO::SEEK_SET) + raise ArgumentError, "no stream to seek" unless _next_argv() + @current_file.seek(int, whence) + end + + def rewind + raise ArgumentError, "no stream to rewind" unless _next_argv() + @current_file.rewind + end + + def pos=(position) + raise ArgumentError, "no stream to set position" unless _next_argv() + @current_file.pos = position + end + + def eof + if @current_file + return true if @init_p == 0 || @current_file.eof + end + return false + end + alias_method :eof?, :eof + + def binmode + #TODO: remove the line below when binmode is implemented + raise RuntimeError, "binmode not yet implemented" unless @current_file.respond_to? :binmode + @binmode = true; + _next_argv(); + @current_file.binmode + self + end + + def filename + _next_argv() + @filename + end + alias_method :path, :filename + + def file + _next_argv() + @current_file + end + + def skip + if @next_p != -1 + _close(@current_file) + @next_p = 1 + end + self + end + + def close + _next_argv() + _close(@current_file) + @next_p = 1 if @next_p != -1; + @gets_lineno = 0; + self + end + + def closed? + _next_argv() + @current_file.closed? + end + + def lineno + $. + end + + def lineno=(val) + @gets_lineno = val + $. = val + nil + end + + def self.after_loaded + # declare the ARGF constant in the Object space + Object.const_set('ARGF', ARGFClass.new) + + # $FILENAME is a read-only global variable + set = proc { |val, key| raise NameError, "#{key} is a read-only variable" } + get = proc { ARGF.filename } + Globals.set_hook(:$FILENAME, get, set) + + # $< is the ARGF object itself + get = proc { ARGF } + Globals.set_hook(:$<, get, nil) + + end + + ### + protected + ### + + def _next_argv + + # TODO: Activate this peice of code + # when there is a way to known whether the current + # stdout is in binmode. There is no such method in MRI + # today + #stdout_binmode = true if STDOUT.binmode? + + if @init_p == 0 + if ARGV.size > 0 + @next_p = 1; + else + @next_p = -1; + end + @init_p = 1; + @gets_lineno = 0; + end + + if @next_p == 1 + @next_p = 0 + + begin + reetry = false + if ARGV.size > 0 + @filename = fn = ARGV.shift + if fn == '-' + @current_file = STDIN + if $-i + warn("Can't do inplace edit for stdio; skipping"); + reetry = true; next + end + else + fr = File.open(fn) + if $-i + STDOUT.close if STDOUT != ORIG_STDOUT + st = fr.stat + + unless $-i.empty? + + bkup_fn = fn+$-i + begin + File.rename(fn, bkup_fn) + rescue SystemCallError + # file cannot be renamed + warn("Can't rename #{fn} to #{bkup_fn}: #{$!.message}, skipping file") + fclose(fr); + reetry = true; next + end + + else + + begin + File.unlink(fn) + rescue + warn("Can't remove #{fn}: #{$!.message}, skipping file") + fclose(fr); + reetry = true; next + end + + end # unless $-i empty (no backup) + + fw = File.open(fn, 'w') + st2 = fw.stat + fw.chmod(st.mode) + + if (st.uid != st2.uid || st.gid != st2.gid) + fw.chown(st.uid, st.gid) + end + + STDOUT.reopen(fw) + + #TODO: activate when binmode implemented + #stdout.binmode if stdout_binmode + + end # if $-i (in place edit mode) + + @current_file = fr + end + #TODO: activate when binmode implemented in IO + #@current_file.binmode if @binmode + + else + @next_p = 1 + return false + end + end while reetry # end do + + elsif (@next_p == -1) + @current_file = STDIN + @filename = '-' + if $-i + warn("Can't do inplace edit for stdio"); + STDOUT.reopen(orig_stdout) + end + end + return true + end + + def _getline(rs=$/) + + begin + reetry = false + return nil unless _next_argv() + + line = @current_file.gets(rs) + + if line.nil? && @next_p != -1 + _close(@current_file) + @next_p = 1 + reetry = true + end + end while reetry + + if line + @gets_lineno += 1 + $. = @gets_lineno + end + + line + end + + # Mimic the low level rb_io_close that doesn't raise + # an IOerror when that IO is already closed. + def _close(io) + io.close unless io.closed? + end -class ARGF end diff --git a/kernel/core/global.rb b/kernel/core/global.rb index 4b287ec..c641afb 100644 --- a/kernel/core/global.rb +++ b/kernel/core/global.rb @@ -24,10 +24,8 @@ class GlobalVariables :$CONSOLE => STDOUT, :$DEBUG => false, :$SAFE => 0, - :$FILENAME => '-', # current IO file - :$. => 0, # TODO: Last line number of IO read. + :$. => 0, # Last line number of IO read :$_ => nil, # HACK: bunk for now. - :$< => nil, # ARGF (set it in argf.rb :$? => nil, # Process status. nil until set :$= => false, # ignore case, whatever that is :$-i => nil # in place edit mode is disabled by default diff --git a/kernel/core/io.rb b/kernel/core/io.rb index 70aba08..1f1fc6b 100644 --- a/kernel/core/io.rb +++ b/kernel/core/io.rb @@ -544,7 +544,6 @@ class IO line.taint unless line.nil? $_ = line - $. = @lineno line end @@ -828,8 +827,9 @@ class IO def rewind seek 0 - @lineno = 0 @eof = false + ARGF.gets_lineno -= @lineno + @lineno = 0 return 0 end @@ -923,7 +923,10 @@ class IO def self.after_loaded() remove_method :orig_reopen - # Nothing to do right now + + # Treat $. (last line number read) as a hooked global variable like MRI + set = proc { |val| ARGF.gets_lineno = val; val } + Globals.set_hook(:$., nil, set) end end diff --git a/kernel/loader.rb b/kernel/loader.rb index 3ac040d..65c0f61 100644 --- a/kernel/loader.rb +++ b/kernel/loader.rb @@ -26,6 +26,8 @@ begin String.after_loaded Ar.after_loaded + + ARGFClass.after_loaded ENV = EnvironmentVariables.new @@ -192,6 +194,8 @@ begin else require more end + elsif arg.prefix? "-i" + $-i = arg[2..-1] # in place edit mode elsif arg == "-" $0 = "-" Compile.execute STDIN.read -- 1.5.4.5