Temporarily override a methode

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”

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

Under OSX my version worked too - but not under windows.

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

Thanks for this.

Al

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”

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]

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