From c6676b7e04b8c53d8385ed2b660d5ebb73e66340 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Apr 2012 01:05:44 -0700 Subject: [PATCH 1/2] Refactor, optimize and slightly correct find-method. This increases the speed for name_search considerably (~50%), and makes the code a bit more readable. It also Fixes # 527, by deduping methods by the pair of [owner, name] not just name. TODO: Test --- lib/pry/default_commands/find_method.rb | 168 +++++++++++++++--------- 1 file changed, 103 insertions(+), 65 deletions(-) diff --git a/lib/pry/default_commands/find_method.rb b/lib/pry/default_commands/find_method.rb index 2196f2f5..ffab31b6 100644 --- a/lib/pry/default_commands/find_method.rb +++ b/lib/pry/default_commands/find_method.rb @@ -32,94 +32,132 @@ class Pry else klass = (target_self.is_a?(Module)) ? target_self : target_self.class end - if opts.name? - to_put = name_search(pattern, klass) - elsif opts.content? - to_put = content_search(pattern, klass) - else - to_put = name_search(pattern, klass) - end - 1 - if to_put.flatten == [] + + matches = if opts.content? + content_search(pattern, klass) + else + name_search(pattern, klass) + end + + if matches.empty? puts text.bold("No Methods Matched") else - puts text.bold("Methods Matched") - puts "--" - stagger_output to_put.join("\n") + print_matches(matches) end end private + # pretty-print a list of matching methods. + # + # @param Array[Method] + def print_matches(matches) + 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 + def puts(item) output.puts item end - def content_search(pattern, klass, current=[], the_methods=[]) - return unless(klass.is_a? Module) - return if current.include? klass - current << klass - meths = [] - (Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).uniq.each do |meth| - next if the_methods.include? meth.name - the_methods << meth.name + # 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) + if done[klass] || !(Module === klass) + return + end + + done[klass] = true + + yield klass + + klass.constants.each do |name| + next if klass.autoload?(name) begin - if meth.source =~ pattern && !meth.alias? - header = "#{klass}##{meth.name}: " - meths << header + colorize_code((meth.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}")) - end - rescue Pry::RescuableException - next - rescue Pry::CommandError - next + const = klass.const_get(name) + rescue RescuableException => e + # 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 - klass.constants.each do |klazz| - begin - meths += ((res = content_search(pattern, klass.const_get(klazz), current, the_methods)) ? res : []) - rescue Pry::RescuableException - next - end - end - return meths.uniq.flatten end - def name_search(regex, klass, current=[], the_methods=[]) - return unless(klass.is_a? Module) - return if current.include? klass - current << klass - header = text.bold("#{klass.name}:") - meths = [] - (Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).uniq.each do |x| - next if the_methods.include? x.name - the_methods << x.name - if x.name =~ regex - meths << " #{x.name}" - begin - if x.alias? - meths[-1] += "#A|#{x.original_name}" if x.original_name - end - rescue Pry::RescuableException - 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 - max = meths.map(&:length).max - meths.map! do |x| - if x =~ /#{"#A"}/ - x = x.sub!("#A|", ((' ' * ((max - x.length) + 3)) + text.bold("(Alias of "))) + text.bold(")") - end - x + + 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 - meths.unshift header if meths.size > 0 - klass.constants.each do |x| + 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 - meths << ((res = name_search(regex, klass.const_get(x), current, the_methods)) ? res : []) - rescue Pry::RescuableException - next + meth.source =~ regex + rescue RescuableException => e + false end end - return meths.uniq.flatten end end end From b1c0494881e741b33154618a2d76cc991bcc331f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Apr 2012 01:14:20 -0700 Subject: [PATCH 2/2] Tweak --- lib/pry/default_commands/find_method.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/pry/default_commands/find_method.rb b/lib/pry/default_commands/find_method.rb index ffab31b6..22962fd6 100644 --- a/lib/pry/default_commands/find_method.rb +++ b/lib/pry/default_commands/find_method.rb @@ -40,9 +40,9 @@ class Pry end if matches.empty? - puts text.bold("No Methods Matched") + output.puts text.bold("No Methods Matched") else - print_matches(matches) + print_matches(matches, pattern) end end @@ -52,7 +52,7 @@ class Pry # pretty-print a list of matching methods. # # @param Array[Method] - def print_matches(matches) + def print_matches(matches, pattern) grouped = matches.group_by(&:owner) order = grouped.keys.sort_by{ |x| x.name || x.to_s } @@ -73,10 +73,6 @@ class Pry end end - def puts(item) - output.puts item - end - # Run the given block against every constant in the provided namespace. # # @param Module The namespace in which to start the search.