Forum: Ruby temporarily override a methode

Posted by Markus n/a (albundy)
on 2013-01-02 14:44
I'd like to do something like this (see below).

Is that possible?

class Tee
  @@already_redirected = false
  def self.tee(logfile, append = true)
    raise "output already redirected" if @@already_redirected
    @@already_redirected = true
    lf = File.open(logfile, append ? 'a' : 'w+')
    #TODO: store current methods
    #redefine methods
    redirect(lf)
    begin
      yield
    ensure
      lf.close
      #TODO: restore methods
    end
  end

  private
  def self.redirect(file)
    [$stdout, $stderr].each do |io|
      old_write = io.method(:write)
      class << io
        self
      end.module_eval do
        define_method(:write) do |text|
          old_write.call(text)
          file.write(text)
        end
      end
    end
  end
end

puts "start"
Tee::tee("logfile.log", true) {
  puts "Hello"
  printf "Hello again"
  system("ls -l")
}
puts "end"
Posted by Markus n/a (albundy)
on 2013-01-02 16:40
found a way

class Tee
  def self.tee(logfile, append = true, stdout = true, stderr = true)
    file = File.open(logfile, append ? 'a' : 'w+')
    stdout_write = nil
    stderr_write = nil
    stdout_write = decorate($stdout, :write) {|data| file.write(data)} 
if stdout
    stderr_write = decorate($stderr, :write) {|data| file.write(data)} 
if stderr
    begin
      yield if block_given?
    ensure
      restore($stdout, :write, stdout_write) if not stdout_write.nil?
      restore($stderr, :write, stderr_write) if not stderr_write.nil?
      file.close
    end
  end

  private
  def self.restore(obj, methodname, method)
    class << obj
      self
    end.module_eval do
      define_method(methodname, method)
    end
  end
  def self.decorate(obj, methodname)
    method = obj.method(methodname)
    class << obj
      self
    end.module_eval do
      define_method(methodname) do |data|
        method.call(data)
        yield data
      end
    end
    return method
  end
end
Posted by Brian Candler (candlerb)
on 2013-01-03 14:37
However, what you have written doesn't handle your desired case of 
redirecting system("ls"). This is because commands run by system() 
inherit the ruby process's stdout/stderr file descriptors, and write 
directly to them (not touching any Ruby objects)

Here's a possible solution, setting stdout and stderr to point to new 
pipes which the ruby process consumes in the background.

class Tee
  def self.tee(logfile, append=true)
    file = File.open(logfile, append ? 'a' : 'w')
    stdout_old = $stdout.dup
    stderr_old = $stderr.dup
    #stdout_old.close_on_exec = true  # only in ruby 1.9
    #stderr_old.close_on_exec = true  # only in ruby 1.9
    stdout_r, stdout_w = IO.pipe
    stderr_r, stderr_w = IO.pipe
    t1 = Thread.new do
      while data = stdout_r.readpartial(1024) rescue nil
        stdout_old.write(data)
        file.write(data)
      end
      stdout_r.close
    end
    t2 = Thread.new do
      while data = stderr_r.readpartial(1024) rescue nil
        stderr_old.write(data)
        file.write(data)
      end
      stderr_r.close
    end
    begin
      $stdout.reopen(stdout_w)
      $stderr.reopen(stderr_w)
      yield
    ensure
      $stdout.reopen(stdout_old)
      $stderr.reopen(stderr_old)
      [stdout_w,stderr_w].each { |f| f.close }
      t1.join
      t2.join
      [stdout_old,stderr_old,file].each { |f| f.close }
    end
  end
end

puts "start"
Tee::tee("logfile.log", true) {
  puts "Hello"
  printf "Hello again"
  system("ls -l")
}
puts "end"
Posted by Markus n/a (albundy)
on 2013-01-03 22:41
Under OSX my version worked too - but not under windows.

Your version works with both - so I will use this.

Thanks for this.

Al
Posted by Brian Candler (candlerb)
on 2013-01-04 13:52
Markus n/a wrote in post #1090973:
> Under OSX my version worked too - but not under windows.

I tried yours under OSX (10.7.5) and it didn't work, and I can't see how 
it possibly could - unless you are using a version of ruby with a 
radically different version of the system() command than mine.

I tried both these rubies:

(system)
ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin11.0]

(homebrew)
ruby 1.9.3p362 (2012-12-25 revision 38607) [x86_64-darwin11.4.2]
Posted by Markus n/a (albundy)
on 2013-01-04 16:15
You are right - I'm working with OSX 10.6.8 and develop my scripts in 
eclipse with jruby.
With JRuby 1.6.8 my solution works.
With the native Ruby 1.8.7 it doesn't.

Your solution works with ruby, jruby, on windows and on osx.

Al
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.