In order to handle a -q (quiet) option in my script, I did this:
class NoWrite
def write(*args)
end
end
$stdout = NoWrite.new if $options[:quiet]
It works fine.
However I'm wondering if there isn't a metaprogramming way of making the write method moot.
remove_method and undef_method can't be used because the write method has to be defined, I just want to make it do nothing.
Also, $stderr can't be impacted.
Alternatively, you can redirect stdout to /dev/null
def stfu
begin
orig_stdout = $stdout.clone
$stdout.reopen File.new('/dev/null', 'w')
retval = yield
rescue Exception => e
$stdout.reopen orig_stdout
raise e
ensure
$stdout.reopen orig_stdout
end
retval
end
require 'some_noisy_gem'
stfu do
some_function_that_generates_a_lot_of_cruft_on_stdout
end
Original snippet is at: https://jacob.hoffman-andrews.com/README/index.php/2011/04/14/ruby-function-stfu-temporarily-redirect-noisy-stdout-writes-to-devnull/
You can use instance_eval for this:
$stdout.instance_eval{ def write(*args); end } if $options[:quiet]
Or you can make an anonymous class:
$stdout = Class.new{ def self.write(*args); end } if $options[:quiet]
Related
I have ruby application and I want to implement a DSL to define Factories. Factories are classes that instantiates some object, execute some logic in the process, perform some validations and execute some callbacks depending on the results (succeeded or failed):
f = Factory.new
f.create(foo: :bar) do |on|
on.success { puts 'hey from success action callback' }
on.failure { puts 'hey from failure action callback' }
end
Ok, this is not that hard to do, but I also want to stop the create method right after the method fails or succeeds, something like:
def create(options = {})
# some logic
failed! # this method stops execution and yields the callback object
puts "you'll never see this"
end
What I came up with is this: https://gist.github.com/esdras/631a04769f24856c6d7f
See a partial version below:
require 'fiber'
class Factory
class Callbacks
# omitted some code here, this class is basically a container for
# success and failure callbacks
end
def failed!
#callbacks.failed!
resume_context
end
def succeeded!
#callbacks.succeeded!
resume_context
end
def resume_context ; Fiber.yield ; end
def self.handle(name, &method_body)
define_method "__original_#{name}__", &method_body
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { send("__original_#{name}__", *args) }.resume
#callbacks
end
end
handle :create do |options = {}|
puts options.inspect
puts "in create"
succeeded!
puts 'after succeeded, never reached here'
end
end
As you can see the class method handle defines two methods: __original_create__ and create which wraps __original_create__ in a Fiber to make it possible to stop execution immediately and execute the callbacks. My question is: Is there a better way to do this? Without creating that __original_create__ method or even without using Fibers?
I already tried this:
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { method_body.call *args }.resume
# above method_body is evaluated in the context of the class.
#callbacks
end
end
but method_body is evaluated in the context of the class, not the instance:
I also tried to instance_eval the method_body like this:
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { instance_eval &method_body }.resume
# above we lost the parameters defined by the handle method
#callbacks
end
end
but I lost the reference to the parameters defined by:
handle :create do |param1, param2|
# method body
end
The only way I found is defining a method with the block passed to the handle method and after defining a wrapper method that calls the original method, like I did above with __original_create__. I'm not ok with defining an extra method, there got to be another way to do this. :(
Any insights would be appreciated.
I'm not sure what you need the Fiber for, so I will leave it, but you need is instance_exec
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { instance_exec *args, &method_body }.resume
# above we lost the parameters defined by the handle method
#callbacks
end
end
One way to do it is to use throw and catch.
def create(options = {})
case catch(:result) do
throw :result, :success if ...
throw :result, :error if ...
end
when :success then ...
when :error then ...
end
end
Ruby has the File class that can be initialized using the normal new() method, or using open() and passing a block. How would I write a class that behaved like this?
File.open("myfile.txt","r") do |f|
...
end
This is a simple example of passing a block to new/open method
class Foo
def initialize(args, &block)
if block_given?
p block.call(args) # or do_something
else
#do_something else
end
end
def self.open(args, &block)
if block_given?
a = new(args, &block) # or do_something
else
#do_something else
end
ensure
a.close
end
def close
puts "closing"
end
end
Foo.new("foo") { |x| "This is #{x} in new" }
# >> "This is foo in new"
Foo.open("foo") { |x| "This is #{x} in open" }
# >> "This is foo in open"
# >> closing
The general outline of File.open is something like this:
def open(foo, bar)
f = do_opening_stuff(foo, bar)
begin
yield f
ensure
do_closing_stuff(f)
end
end
It is yield that invokes the block passed by the caller. Putting do_closing_stuff within an ensure guarantees that the file gets closed even if the block raises an exception.
More nitty-gritty on the various ways of calling blocks here: http://innig.net/software/ruby/closures-in-ruby
You can create a class method which creates an instance, yields it, and then then performs cleanup after the yield.
class MyResource
def self.open(thing, otherthing)
r = self.new(thing, otherthing)
yield r
r.close
end
def initialize(thing, otherthing)
#thing = thing
#otherthing = otherthing
end
def do_stuff
puts "Doing stuff with #{#thing} and #{#otherthing}"
end
def close
end
end
Now, you can either use it with a constructor:
r = MyResource.new(1, 2)
r.do_stuff
r.close
or using a block, which automatically closes the object:
MyResource.open(1, 2) do |r|
r.do_stuff
end
I've got some code that I cobbled together from hints found that worked. But something is going wrong, and I am baffled. Nothing is sent to the screen, and file is empty.
Here's the program:
#!/usr/bin/env ruby -w
require "stringio"
class Tee
def initialize
date_str = `date '+%Y%m%d_%H%M%S'`.chomp
#log = File.new("tee_output_example_#{date_str}.log","w")
end
["$stdout", "$stderr"].each do |std|
io = eval(std)
old_write = io.method(:write)
class << io
self
end.module_eval do
define_method(:write) do |text|
unless text =~ /^[\r\n]+$/ # Because puts calls twice.
File.open(#log, "a") do |f|
# f.puts [std[1..-1].upcase, caller[2], text].join(" ")
f.puts text
end
end
old_write.call(text)
end
end
end
end
logger = Tee.new()
logger.puts "text on stdout"
logger.puts "Something else"
$stdout = STDOUT
$stderr = STDERR
$stdout.puts "plain puts to $stdout"
$stderr.puts "plain puts to $stderr"
I managed to solve it simply with this command:
STDOUT.reopen IO.popen "tee stdout.log", "a"
STDERR.reopen IO.popen "tee stderr.log", "a"
Your expectations aren't very clear to me, but this seems to be a reasonable start (with several directions you could take it depending on your ultimate goal).
#!/usr/bin/env ruby -w
class Tee
attr_accessor :log_file
def initialize()
self.log_file = File.open "tee_output_example_#{date_str}.log", "w"
at_exit { log_file.close }
end
def date_str
Time.now.strftime "%Y%m%d_%H%M%S"
end
def puts(*strings)
log_file.puts(*strings)
$stdout.puts(*strings)
end
end
# will be sent to both $stdout and the logfile
logger = Tee.new
logger.puts "text on stdout"
logger.puts "Something else"
# will only be sent to $stdout or $stderr
$stdout.puts "plain puts to $stdout"
$stderr.puts "plain puts to $stderr"
I could give some tips on debugging techniques, but instead, I'll suggest an alternate solution which might meet your needs:
class Tee
def initialize(a,b); #a,#b = a,b; end
def method_missing(m,*args,&b)
#a.send(m,*args,&b)
#b.send(m,*args,&b)
end
end
This class is more generally useful than what you were trying to write; it takes 2 objects, and passes all method calls (including arguments and block) on to BOTH of them. So you could do something like:
tee = Tee.new(File.open("log","w"), $stdout)
tee.puts "Hello world AND log file!"
I am trying to write fails and errors that occur in the test to a log file, so that they don't appear on-screen, but it appears as though errors and assert failures write to STDOUT instead of STDERR. I have been unable to find information on how to redirect this output after hours of googling, and would greatly appreciate help.
Why should the errors not be on stdout?
Up to now I have no prepared solution to suppress the output for specific errors.
If you accept the unchanged output, I have a solution to store errors and failures in a file. It will be no problem to create a 2nd file for Notifications...
gem 'test-unit'
require 'test/unit'
module Test
module Unit
class TestSuite
alias :old_run :run
def run(result, &progress_block)
old_run(result, &progress_block)
File.open('test.log', 'w'){|f|
result.faults.each{|err|
case err
when Test::Unit::Error, Test::Unit::Failure
f << err.long_display
f << "\n===========\n"
#not in log file
when Test::Unit::Pending, Test::Unit::Notification, Test::Unit::Omission
end
}
}
end
end
end
end
class MyTest < Test::Unit::TestCase
def test_1()
assert_equal( 3, 1+1) #failure
end
def test_2()
1 / 0 #force an error
end
def test_3()
notify 'sss'
end
def test_4()
pend "MeineKlasse.new"
end
def test_5
omit 'aaa' if RUBY_VERSION == '1.9.2'
end
def test_5
assert_in_delta( 0.1, 0.00001, 1.0/10)
end
end
Have you tried redirecting the output to StringIO, to write it to a logfile later?
original_stdout = $stdout
original_stderr = $stderr
fake_stdout = StringIO.new
fake_stderr = StringIO.new
$stdout = fake_stdout
$stderr = fake_stderr
Then after you've ran the tests:
$stdout = original_stdout
$stderr = original_stderr
#stdout = fake_stdout.string
#stderr = fake_stderr.string
I'm not really sure this will work though...
I hope I understood yor question correct.
Do you mean something like this:
gem 'test-unit'
require 'test/unit'
class StdOutLogger < IO
def initialize(*)
super
#file = File.open('log.txt', 'w')
#stdout = true
end
#write to stdout and file
def write(s)
$stdout << s
#file << s
end
end
STDOUT = StdOutLogger.new(1)
class MyTest < Test::Unit::TestCase
def test_1()
assert_equal( 2, 1+1)
assert_equal( 2, 4/2)
assert_equal( 1, 3/2)
assert_equal( 1.5, 3/2.0)
end
end
But I would recommend to copy stdout on operation system level
ruby mytest.rb > log.txt
Here is a version to skip between stdout and file output. The output switch is only a interim solution - perhaps it can be made better.
class StdOutLogger < IO
def initialize(*)
super
#file = File.open('log.txt', 'w')
#stdout = true
end
def write(s)
case s
when /\AError:/
#stdout = false #change to file
when /\A(Pending):/
#stdout = true #change to file
end
if #stdout
$stdout << s
else
#file << s
end
end
end
STDOUT = StdOutLogger.new(1)
I've seen some code that makes a class method such that you can write
class_method :instance_method,
to alias instance_method and call it from in a wrapper method every time it is called. Is there a way to be able to call class_method and have it apply to all the following definition calls (like how private works)?
I don't quite understand your question. In the future, please provide a specification of what it is exactly that you are trying to do, preferably in the form of an executable testsuite, so that we can check for ourselves whether our answers really answer your question.
Are you perhaps talking about something like this?
module MethodHook
private
def class_method(m=nil)
return if #__recursing__ # prevent infinite recursion
return #__class_method__ = true unless m
#__recursing__ = true
old_m = instance_method(m)
define_method(m) do |*args, &block|
puts "before #{m}(#{args.join(', ')})" # wrap wrap wrap
old_m.bind(self).(*args, &block)
puts "after #{m}" # more wrapping
end
#__recursing__ = nil
end
def method_added(m)
class_method(m) if #__class_method__
super
end
end
Use like this:
class Foo
extend MethodHook
def unwrapped
puts __method__
end
class_method
def wrapped
puts __method__
end
end
f = Foo.new
f.unwrapped
# unwrapped
f.wrapped
# before wrapped()
# wrapped
# after wrapped
class Foo
class_method(:unwrapped)
end
f.unwrapped
# before unwrapped()
# wrapped
# after unwrapped