This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class FindMethod < Pry::ClassCommand
|
||||
extend Pry::Helpers::BaseHelpers
|
||||
|
||||
match 'find-method'
|
||||
group 'Context'
|
||||
description 'Recursively search for a method within a Class/Module or ' \
|
||||
'the current namespace.'
|
||||
command_options shellwords: false
|
||||
|
||||
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.
|
||||
|
||||
# Find all methods whose name match /re/ inside
|
||||
# the Pry namespace. Matches Pry#repl, etc.
|
||||
find-method re Pry
|
||||
|
||||
# Find all methods that contain the code:
|
||||
# output.puts inside the Pry namespace.
|
||||
find-method -c 'output.puts' Pry
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on :n, :name, "Search for a method by name"
|
||||
opt.on :c, :content, "Search for a method based on content in Regex form"
|
||||
end
|
||||
|
||||
def process
|
||||
return if args.empty?
|
||||
|
||||
klass = search_class
|
||||
|
||||
matches = opts.content? ? content_search(klass) : name_search(klass)
|
||||
show_search_results(matches)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @return [Regexp] The pattern to search for.
|
||||
def pattern
|
||||
@pattern ||= ::Regexp.new args[0]
|
||||
end
|
||||
|
||||
# Output the result of the search.
|
||||
#
|
||||
# @param [Array] matches
|
||||
def show_search_results(matches)
|
||||
if matches.empty?
|
||||
output.puts bold("No Methods Matched")
|
||||
else
|
||||
print_matches(matches)
|
||||
end
|
||||
end
|
||||
|
||||
# The class to search for methods.
|
||||
# We only search classes, so if the search object is an
|
||||
# instance, return its class. If no search object is given
|
||||
# search `target_self`.
|
||||
def search_class
|
||||
klass = if args[1]
|
||||
target.eval(args[1])
|
||||
else
|
||||
target_self
|
||||
end
|
||||
|
||||
klass.is_a?(Module) ? klass : klass.class
|
||||
end
|
||||
|
||||
# pretty-print a list of matching methods.
|
||||
#
|
||||
# @param [Array<Method>] matches
|
||||
def print_matches(matches)
|
||||
grouped = matches.group_by(&:owner)
|
||||
order = grouped.keys.sort_by { |x| x.name || x.to_s }
|
||||
|
||||
order.each do |klass|
|
||||
print_matches_for_class(klass, grouped)
|
||||
end
|
||||
end
|
||||
|
||||
# Print matched methods for a class
|
||||
def print_matches_for_class(klass, grouped)
|
||||
output.puts bold(klass.name)
|
||||
grouped[klass].each do |method|
|
||||
header = method.name_with_owner
|
||||
output.puts header + additional_info(header, method)
|
||||
end
|
||||
end
|
||||
|
||||
# Return the matched lines of method source if `-c` is given or ""
|
||||
# if `-c` was not given
|
||||
def additional_info(header, method)
|
||||
if opts.content?
|
||||
': ' + colorize_code(matched_method_lines(header, method))
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def matched_method_lines(header, method)
|
||||
method.source.split(/\n/).select { |x| x =~ pattern }.join(
|
||||
"\n#{' ' * header.length}"
|
||||
)
|
||||
end
|
||||
|
||||
# Run the given block against every constant in the provided namespace.
|
||||
#
|
||||
# @param [Module] klass The namespace in which to start the search.
|
||||
# @param [Hash<Module,Boolean>] done The namespaces we've already visited (private)
|
||||
# @yieldparam klass Each class/module in the namespace.
|
||||
#
|
||||
def recurse_namespace(klass, done = {}, &block)
|
||||
return if !klass.is_a?(Module) || done[klass]
|
||||
|
||||
done[klass] = true
|
||||
|
||||
yield klass
|
||||
|
||||
klass.constants.each do |name|
|
||||
next if klass.autoload?(name)
|
||||
|
||||
begin
|
||||
const = Pry::Method.singleton_class_of(klass.const_get(name))
|
||||
rescue RescuableException # rubocop:disable Lint/HandleExceptions
|
||||
# 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] namespace The namespace in which to search.
|
||||
# @yieldparam [Method] 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|
|
||||
methods = Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)
|
||||
methods.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 [Module] namespace The namespace to search
|
||||
# @return [Array<Method>]
|
||||
#
|
||||
def name_search(namespace)
|
||||
search_all_methods(namespace) do |meth|
|
||||
meth.name =~ pattern
|
||||
end
|
||||
end
|
||||
|
||||
# Search for all methods who's implementation matches the given regex
|
||||
# within a namespace.
|
||||
#
|
||||
# @param [Module] namespace The namespace to search
|
||||
# @return [Array<Method>]
|
||||
#
|
||||
def content_search(namespace)
|
||||
search_all_methods(namespace) do |meth|
|
||||
begin
|
||||
meth.source =~ pattern
|
||||
rescue RescuableException
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::FindMethod)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user