mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
ebccd57013
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>
167 lines
4.9 KiB
Ruby
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
|