1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00
pry--pry/lib/pry/commands/find_method.rb
Kyrylo Silin ebccd57013 Convert all commands to classes
John "banister" Mair describes the following key features of commands
as classes:

  1. It enables people to extend them by either subclassing or
     monkeypatching.
  2. It enables them to provide their own API, so that for example, the
     Pry::Command::Edit class could have class methods for people to
     configure it.

Please, note that I didn't touch easter eggs commands. I also prettified
some strings (your source code reading experience should vastly improve!).

Signed-off-by: Kyrylo Silin <kyrylosilin@gmail.com>
2012-12-27 13:31:37 +02:00

167 lines
4.9 KiB
Ruby

class Pry
class Command::FindMethod < Pry::ClassCommand
extend Pry::Helpers::BaseHelpers
match 'find-method'
group 'Context'
options :requires_gem => 'ruby18_source_location' if mri_18?
options :shellwords => false
description 'Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]'
banner <<-BANNER
Usage: find-method [-n | -c] METHOD [NAMESPACE]
Recursively search for a method within a Class/Module or the current namespace.
Use the `-n` switch (the default) to search for methods whose name matches the given regex.
Use the `-c` switch to search for methods that contain the given code.
e.g find-method re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc.
e.g find-method -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace.
BANNER
def setup
require 'ruby18_source_location' if mri_18?
end
def options(opti)
opti.on :n, :name, "Search for a method by name"
opti.on :c, :content, "Search for a method based on content in Regex form"
end
def process
return if args.size < 1
pattern = ::Regexp.new args[0]
if args[1]
klass = target.eval(args[1])
if !klass.is_a?(Module)
klass = klass.class
end
else
klass = (target_self.is_a?(Module)) ? target_self : target_self.class
end
matches = if opts.content?
content_search(pattern, klass)
else
name_search(pattern, klass)
end
if matches.empty?
output.puts text.bold("No Methods Matched")
else
print_matches(matches, pattern)
end
end
private
# pretty-print a list of matching methods.
#
# @param Array[Method]
def print_matches(matches, pattern)
grouped = matches.group_by(&:owner)
order = grouped.keys.sort_by{ |x| x.name || x.to_s }
order.each do |klass|
output.puts text.bold(klass.name)
grouped[klass].each do |method|
header = method.name_with_owner
extra = if opts.content?
header += ": "
colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}"))
else
""
end
output.puts header + extra
end
end
end
# Run the given block against every constant in the provided namespace.
#
# @param Module The namespace in which to start the search.
# @param Hash[Module,Boolean] The namespaces we've already visited (private)
# @yieldparam klazz Each class/module in the namespace.
#
def recurse_namespace(klass, done={}, &block)
return if !(Module === klass) || done[klass]
done[klass] = true
yield klass
klass.constants.each do |name|
next if klass.autoload?(name)
begin
const = klass.const_get(name)
rescue RescuableException
# constant loading is an inexact science at the best of times,
# this often happens when a constant was .autoload? but someone
# tried to load it. It's now not .autoload? but will still raise
# a NameError when you access it.
else
recurse_namespace(const, done, &block)
end
end
end
# Gather all the methods in a namespace that pass the given block.
#
# @param Module The namespace in which to search.
# @yieldparam Method The method to test
# @yieldreturn Boolean
# @return Array[Method]
#
def search_all_methods(namespace)
done = Hash.new{ |h,k| h[k] = {} }
matches = []
recurse_namespace(namespace) do |klass|
(Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).each do |method|
next if done[method.owner][method.name]
done[method.owner][method.name] = true
matches << method if yield method
end
end
matches
end
# Search for all methods with a name that matches the given regex
# within a namespace.
#
# @param Regex The regex to search for
# @param Module The namespace to search
# @return Array[Method]
#
def name_search(regex, namespace)
search_all_methods(namespace) do |meth|
meth.name =~ regex
end
end
# Search for all methods who's implementation matches the given regex
# within a namespace.
#
# @param Regex The regex to search for
# @param Module The namespace to search
# @return Array[Method]
#
def content_search(regex, namespace)
search_all_methods(namespace) do |meth|
begin
meth.source =~ regex
rescue RescuableException
false
end
end
end
end
Pry::Commands.add_command(Pry::Command::FindMethod)
end