require 'pp'
## This module helps to debug by:
## 1. defining log functions which write data into any kind of stream
## rather than to STDOUT, and are seperated into fatal, error and normal
## messages.
## 2. by defining assertion functions which raise an AssertionError
## if the assertion is false.
## 3. a couple of other nice things such as trace or bm (benchmark)
##
## use this with:
## include Debug
## Debug.setup(...)
module Debug
class AssertionError < StandardError; end
## setup the debugger. optionally takes a hash like:
## Debug.setup(:name => 'foo', :level => 2)
## parameters are:
## _name_: an identifier which is written before anything
## _stream_: a writable stream like STDERR or File.open('foo', 'a')
## _level_: the verbosity level.
## 0: log nothing
## 1: log fatal errors
## 2: log all errors
## 3: log everything
def self.setup(hash)
@@file = hash[:file]
@@stream = hash[:stream]
@@level = hash[:level] || 3
@@name = "#{hash[:name] || 'debug'}: "
end
## a getter that, if stream is nil, opens @@file as the new stream
## or uses STDERR if there is no @@file.
## Doing it like this will delay opening the file until it
## is actually needed.
def stream
if @@stream
@@stream
elsif @@file
@@stream = File.open(@@file, 'a') rescue STDERR
@@stream.sync = true
@@stream
else
@@stream = STDERR
end
end
## Write something to the output stream.
def self.write(str)
stream.write(@@name + str.to_s)
return str
end
## Write something to the output stream with a newline at the end.
def self.puts(str)
stream.write(@@name)
stream.puts(str)
return str
end
## Passes if value is neither nil nor false.
## The second argument is an optional message.
## All other assert methods are based on this.
def assert_true(value, message = nil)
message ||= "#{value.inspect} is not true!"
Kernel.raise(AssertionError, message, caller(0)) unless value
end
## Takes a good guess about what you want to do.
## There are two options:
## 1 or 2 arguments, of which the second must be a String
## => use assert_true(value, rest.first)
##
## otherwise
## => use assert_match(value, *rest)
def assert(value, *rest)
if rest.size == 0 or rest.size == 1 and rest.first.is_a? String
assert_true(value, rest.first)
else
assert_match(value, *rest)
end
end
## Passes if "testX === value" is true for any argument.
## If the last argument is a string, it will be used as the message.
def assert_match(value, test0, *test)
## test0 and *test are only seperated to force >=2 args
## so put them back together here.
test.unshift( test0 )
## get or generate the message
if test.last.is_a? String
message = test.pop
else
message = "Expected #{value.inspect} to match with "
message << if test.size == 1
"#{test0.inspect}!"
else
"either #{test.map{|x| x.inspect}.join(" or ")}!"
end
end
assert_true test.any? { |testX| testX === value }, message
end
## Passes if "value1 == value2"
def assert_equal(value1, value2, message=nil)
message ||= "#{value1.inspect} expected, got: #{value2.inspect}!"
assert_true value1 == value2, message
end
## Passes if "value1 != value2"
def assert_not_equal(arg1, arg2, message=nil)
message ||= "Expected something other than #{arg1.inspect}!"
assert_true arg1 != arg2, message
end
## Put this at positions which require attention.
def forbid(message = nil)
message ||= "Incomplete Code!"
assert_true false, message
end
alias flunk forbid
alias assert_neq assert_not_equal
## Trace back the whole way from this function,
## ommitting the first _n_ function calls
def trace( n = 1 ) __log__( caller( n ), 3 ) end
## if you don't want your program to stop,
## but still want to retrieve the error information
def lograise(e=nil)
e ||= $!
log_err("#{e.class}: #{e.message}")
log_err(e.backtrace)
end
## Perform a benchmark on the given block. Optionally
## takes a description which will be logged too.
def bm(descr="benchmark", &block)
if @@level == 0
yield
return
end
# Benchmark
t1 = Time.now
yield
dur = Time.now-t1
# substract the durtation of a "bm(..) do end"
dur -= Debug.bm_dummy(descr) do end
# Format the duration
dur *= 1000
dur = dur > 0 ? dur : 0
dur = '%0.3f' % dur
logerr("#{descr}: #{dur}ms")
end
## for better benchmark results
def self.bm_dummy(descr="benchmark", &block)
t1 = Time.now
yield
return (Time.now-t1)
end
## Log _obj_ by using "IO#write" if _level_ is smaller or equal
## to the current verbose level. There will be some shortcuts:
## logwrite(x) => __logwrite__(x, 3)
## logwriteerr(x) => __logwrite__(x, 2)
## logwritefatal(x) => __logwrite__(x, 1)
def self.__logwrite__(obj, level)
if level <= @@level
Debug.write(obj)
end
obj
end
## Log obj by using "IO#puts" if level is smaller or equal
## to the current verbose level. There will be some shortcuts:
## log(x) => __log__(x, 3)
## logerr(x) => __log__(x, 2)
## logfatal(x) => __log__(x, 1)
def self.__log__(obj, level)
if level <= @@level
obj = obj.nil? ? "checkpoint at #{Time.now}" : obj
Debug.puts(obj)
end
obj
end
## Log obj by using "pp" (pretty-print) if level is smaller or equal
## to the current verbose level. There will be some shortcuts:
## logpp(x) => __logpp__(x, 3)
## logpperr(x) => __logpp__(x, 2)
## logppfatal(x) => __logpp__(x, 1)
def self.__logpp__(obj, level)
if level <= @@level
Debug.write(obj.pretty_inspect)
end
obj
end
## generate lots of shortcut functions for __log(pp|write)__
for method in ['', 'pp', 'write']
for key, level in { '' => 3, 'err' => 2, 'fatal' => 1 }
eval <<-DONE
def log#{method}#{key}( obj = nil )
Debug.__log#{method}__( obj, #{level} )
end
unless key.empty?
alias log#{method}_#{key} log#{method}#{key}
end
DONE
end
end
end