#
# MethodFinder
#
# Author       :  David Tran
# Date         :  2006-05-20
# Last updated :  2006-05-22
#
# Resources :
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/32844
# http://www.nobugs.org/developer/ruby/method_finder.html
# http://redhanded.hobix.com/inspect/stickItInYourIrbrcMethodfinder.html
# http://atrustheotaku.livejournal.com/339449.html
#

class Object
  def what?(*args, &block)
    MethodFinder.new(self, *args, &block)
  end

  def _clone_
    self.clone
  rescue
    self
  end
end

class MethodFinder
  @@black_list = %w[ what? _clone_ exec exit exit! fork sleep syscall system ]

  def initialize(obj, *args, &block)
    @obj = obj
    @args = args
    @block = block
  end

  def self.write(*args)
  end

  # Find all methods on obj which, when called with *args, &block return result
  def self.find(obj, result, *args, &block)
    stdout, stderr = $stdout, $stderr
    $stdout = $stderr = self
    methods = obj.methods.select do |name|
      arity = obj.method(name).arity
      !@@black_list.include?(name)                                       and
      (arity == args.size) || (arity < 0 && (arity+1).abs <= args.size)  and
      begin obj._clone_.send(name, *args, &block) == result; rescue Object; end
    end
    $stdout, $stderr = stdout, stderr
    methods
  end

  # As find method but also Pretty-prints the results
  def self.show(obj, result, *args, &block)
    args_string = args.empty? ? '' : "(" + args.map {|a| a.inspect}.join(", ") + ")"
    block_string = block.nil? ? '' : " {...}"
    find(obj, result, *args, &block).each do |name|
      puts "#{obj.inspect}.#{name}#{args_string}#{block_string} == #{result.inspect}"
    end
  end

  def ==(result)
    # MethodFinder.find(@obj, result, *@args, &@block)
    MethodFinder.show(@obj, result, *@args, &@block)
  end

end

__END__

Some examples:

-1.what? == 1
-1.-@ == 1
-1.abs == 1
-1.denominator == 1
=> ["-@", "abs", "denominator"]

'abc'.what? == 3
"abc".length == 3
"abc".size == 3
=> ["length", "size"]

'a'.what? == 'A'
"a".upcase == "A"
"a".upcase! == "A"
"a".capitalize == "A"
"a".capitalize! == "A"
"a".swapcase == "A"
"a".swapcase! == "A"
=> ["upcase", "upcase!", "capitalize", "capitalize!", "swapcase", "swapcase!"]

[1,2].what? {|s,e| s+e} == 3
[1, 2].instance_eval {...} == 3
[1, 2].inject {...} == 3
=> ["instance_eval", "inject"]

[1,2].what?(0) {|s,e| s+e} == 3
[1, 2].inject(0) {...} == 3
=> ["inject"]

MethodFinder.show(%w[xxx z yy], %w[z yy xxx]) {|e| e.size}
["xxx", "z", "yy"].sort_by {...} == ["z", "yy", "xxx"]
=> ["sort_by"]

#####
updated: 2006-05-22
bug: arity checking
arities = [args.size, -args.size - 1, -1]
arities.include?(obj.method(name).arity)
is not good.
Example:
def m(a, b=1);end
it has arity == -2, so if args.size == 2, [2, -2 - 1, -1].inlcude? check will fail.
==> new checking rule:
((arity >= 0 && arity == args.size) || (arity < 0 && (arity+1).abs <= args.size))
this can simplify to:
(arity == args.size) || (arity < 0 && (arity+1).abs <= args.size)