Skip to content

Instantly share code, notes, and snippets.

@eventualbuddha
Created March 23, 2010 22:08
Show Gist options
  • Save eventualbuddha/341744 to your computer and use it in GitHub Desktop.
Save eventualbuddha/341744 to your computer and use it in GitHub Desktop.
Trace method calls on objects
# ruby -rdebug_proxy -e "DebugProxy.new('foo', :string).gsub!(/o/, 'a')"
#
# DebugProxy (string) -------------------------
# | |
# | Method called on proxy object: |
# | gsub!(/o/, "a") |
# | |
# | Stack trace of method call: |
# | -e:1 |
# | |
# | Value of proxy object before method call: |
# | "foo" |
# | |
# | Value of proxy object after method call: |
# | "faa" |
# | |
# | Proxy was created at: |
# | -e:1 |
# | |
# ---------------------------------------------
class DebugProxy
class Printer
STDOUT = $stdout
attr_reader :stream, :width, :maxlen, :title, :topbottom, :lpadding, :rpadding
def self.print(stream, title, *lines)
maxlen = 0
lines = lines.flatten
lines.each { |line| maxlen = [maxlen, line.size].max }
new(stream, maxlen) do |printer|
printer.header title
lines.each { |line| printer.line line }
printer.footer
end
end
def initialize(stream, maxlen, topbottom="-", lpadding="| ", rpadding=" |")
@stream, @maxlen, @width, @topbottom, @lpadding, @rpadding = stream, maxlen, maxlen + lpadding.size + rpadding.size, topbottom, lpadding, rpadding
yield self if block_given?
end
def header(title)
stream.puts
stream.puts pad("#{title} ", width, topbottom)
end
def footer
stream.puts pad('', width, topbottom)
end
def line(line)
stream.puts lpadding + pad(crop(line, maxlen), maxlen) + rpadding
end
def self.undersized?(line, width)
line.size < width
end
def undersized?(*args)
self.class.undersized?(*args)
end
def self.oversized?(line, width)
line.size > width
end
def oversized?(*args)
self.class.oversized?(*args)
end
def self.pad(line, width, tail=' ')
return line if line.size >= width
line = line + tail * ((width - line.size) / tail.size)
line + tail[0, width - line.size]
end
def pad(*args)
self.class.pad(*args)
end
def self.crop(line, width, tail='...')
return line if line.size <= width
line[0, width-tail.size]+tail
end
def crop(*args)
self.class.crop(*args)
end
end
instance_methods.each { |m| undef_method m unless m =~ %r{^__|caller} }
private
def initialize(target, name=nil, &inspector)
@name = name
@target = target
@inspector = inspector || proc { |target| target.inspect }
@birthplace = caller[1]
end
def method_missing(name, *args, &block)
before = @inspector.call(@target)
begin
return @target.__send__(name, *args, &block)
ensure
after = @inspector.call(@target)
Printer.print(Printer::STDOUT, "DebugProxy#{" (#{@name})" if @name}",
"",
"Method called on proxy object:",
" #{name}(#{args.map {|a| a.inspect}.join(', ')}#{"#{", " unless args.empty?}&#{block.inspect}" if block})",
"",
"Stack trace of method call:",
caller.map {|line| " #{line}"},
"",
"Value of proxy object before method call:",
before.gsub(/^/, ' '),
"",
"Value of proxy object after method call:",
after.gsub(/^/, ' '),
"",
"Proxy was created at:",
" #{@birthplace}",
""
)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment