From 80bdcd44b08fc92ec2debeaf77616cc02d0a2f48 Mon Sep 17 00:00:00 2001 From: Reginald Tan Date: Tue, 17 Jul 2012 21:55:18 -0400 Subject: [PATCH] Added input_type method to determine where to dispatch show-source. Enabled show-source to also work for any object that responds to source_location Local variables now takes precedence over methods that they shadow for show-source --- lib/pry.rb | 3 +- lib/pry/default_commands/editing.rb | 2 +- lib/pry/default_commands/introspection.rb | 125 ++++++++++-------- lib/pry/helpers/command_helpers.rb | 6 +- test/helper.rb | 1 + .../test_default_commands/test_show_source.rb | 42 +++++- 6 files changed, 117 insertions(+), 62 deletions(-) diff --git a/lib/pry.rb b/lib/pry.rb index b0bc4776..90522e9c 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -144,8 +144,7 @@ class Pry # CommandErrors are caught by the REPL loop and displayed to the user. They # indicate an exceptional condition that's fatal to the current command. class CommandError < StandardError; end - class NonMethodContextError < CommandError; end - class NonMethodError < CommandError; end + class MethodNotFound < CommandError; end # indicates obsolete API class ObsoleteError < StandardError; end diff --git a/lib/pry/default_commands/editing.rb b/lib/pry/default_commands/editing.rb index e1b7f0f1..c3cb4ad2 100644 --- a/lib/pry/default_commands/editing.rb +++ b/lib/pry/default_commands/editing.rb @@ -189,7 +189,7 @@ class Pry begin @method = method_object - rescue NonMethodContextError => err + rescue MethodNotFound => err end if opts.present?(:patch) || (@method && @method.dynamically_defined?) diff --git a/lib/pry/default_commands/introspection.rb b/lib/pry/default_commands/introspection.rb index e03e37c0..636b9e99 100644 --- a/lib/pry/default_commands/introspection.rb +++ b/lib/pry/default_commands/introspection.rb @@ -7,51 +7,72 @@ class Pry module ModuleIntrospectionHelpers attr_accessor :module_object - # is our target binding in a method? - def method? - !!method_object - rescue NonMethodError,NonMethodContextError - false + def module_object + name = args.first + @module_object ||= WrappedModule.from_str(name, target) end - def module?(name) - self.module_object = get_module(name) - end - - def get_module(name) - wrapped = Pry::WrappedModule.from_str(name, target) - if !wrapped - if args.empty? && internal_binding?(target) - wrapped = Pry::WrappedModule(get_module_from_internal_binding) - end + # @param [String] + # @param [Binding] target The binding context of the input. + # @return [Symbol] type of input + def input_type(input,target) + if input == "" + :blank + elsif target.eval("defined? #{input} ") =~ /variable/ && + target.eval(input).respond_to?(:source_location) + :sourcable_object + elsif Pry::Method.from_str(input,target) + :method + elsif Pry::WrappedModule.from_str(input, target) + :module + else + :unknown end - wrapped - end - - def get_module_from_internal_binding - mod = target_self.is_a?(Module) ? target_self : target_self.class - end - - def proc?(name) - target.eval(name).is_a? Proc - rescue TypeError, NameError - false end def process(name) - if module?(name) - code_or_doc = process_module - elsif method? - code_or_doc = process_method - elsif proc?(name) - code_or_doc = process_proc - else - command_error("method or module for '#{name}' could not be found or derived", false) - end + input = args.join(" ") + type = input_type(input, target) + + code_or_doc = case type + when :blank + process_blank + when :sourcable_object + process_sourcable_object + when :method + process_method + when :module + process_module + else + command_error("method or module for '#{input}' could not be found or derived", false) + end render_output(code_or_doc, opts) end + def process_blank + if mod = extract_module_from_internal_binding + @module_object = mod + process_module + elsif meth = extract_method_from_binding + @method_object = meth + process_method + else + command_error("method or module for '' could not be derived", false) + end + end + + def extract_module_from_internal_binding + if args.empty? && internal_binding?(target) + mod = target_self.is_a?(Module) ? target_self : target_self.class + Pry::WrappedModule(mod) + end + end + + def extract_method_from_binding + Pry::Method.from_binding(target) + end + def module_start_line(mod, candidate_rank=0) if opts.present?(:'base-one') 1 @@ -256,6 +277,22 @@ class Pry opt.on :a, :all, "Show source for all definitions and monkeypatches of the module/class" end + def process_sourcable_object + name = args.first + object = target.eval(name) + + file_name, line = object.source_location + + source = Pry::Code.from_file(file_name).expression_at(line) + code = Pry::Code.new(source).with_line_numbers(use_line_numbers?).to_s + + result = "" + result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" + result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" + result << code + result << "\n" + end + def process_method raise CommandError, "Could not find method source" unless method_object.source @@ -313,24 +350,6 @@ class Pry result end - def process_proc - name = args.first - target_proc = target.eval(name) - - file_name, line = target_proc.source_location - - source = Pry::Code.from_file(file_name).expression_at(line) - code = Pry::Code.new(source).with_line_numbers(use_line_numbers?).to_s - #code = Pry::Code.new(target_proc.source, line).with_line_numbers(use_line_numbers?).to_s - - - result = "" - result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" - result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" - result << code - result << "\n" - end - def use_line_numbers? opts.present?(:b) || opts.present?(:l) end diff --git a/lib/pry/helpers/command_helpers.rb b/lib/pry/helpers/command_helpers.rb index a0ae89f6..df9c400b 100644 --- a/lib/pry/helpers/command_helpers.rb +++ b/lib/pry/helpers/command_helpers.rb @@ -49,19 +49,19 @@ class Pry meth = Pry::Method.from_str(name, target, opts) if name && !meth - command_error("The method '#{name}' could not be found.", omit_help, NonMethodError) + command_error("The method '#{name}' could not be found.", omit_help, MethodNotFound) end (opts[:super] || 0).times do if meth.super meth = meth.super else - command_error("'#{meth.name_with_owner}' has no super method.", omit_help, NonMethodError) + command_error("'#{meth.name_with_owner}' has no super method.", omit_help, MethodNotFound) end end if !meth || (!name && internal_binding?(target)) - command_error("No method name given, and context is not a method.", omit_help, NonMethodContextError) + command_error("No method name given, and context is not a method.", omit_help, MethodNotFound) end set_file_and_dir_locals(meth.source_file) diff --git a/test/helper.rb b/test/helper.rb index b5455938..6e25ae3f 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -126,6 +126,7 @@ end def mock_pry(*args) + args.flatten! binding = args.first.is_a?(Binding) ? args.shift : binding() input = InputTester.new(*args) diff --git a/test/test_default_commands/test_show_source.rb b/test/test_default_commands/test_show_source.rb index acf35ef9..87194cbd 100644 --- a/test/test_default_commands/test_show_source.rb +++ b/test/test_default_commands/test_show_source.rb @@ -149,7 +149,7 @@ if !mri18_and_no_real_source_location? it "should not raise an exception when a non-extant super method is requested" do def @o.foo(*bars); end - mock_pry(binding, "show-source --super @o.foo").should =~ /'@o.foo' could not be found/ + mock_pry(binding, "show-source --super @o.foo").should =~ /'self.foo' has no super method/ end # dynamically defined method source retrieval is only supported in @@ -194,7 +194,7 @@ if !mri18_and_no_real_source_location? end end - describe "on procs/lambdas" do + describe "on sourcable objects" do if RUBY_VERSION =~ /1.9/ it "should output source defined inside pry" do @@ -206,11 +206,47 @@ if !mri18_and_no_real_source_location? end end - it "should output source" do + it "should output source for procs/lambdas" do hello = proc { puts 'hello world!' } mock_pry(binding, "show-source hello").should =~ /proc { puts 'hello world!' }/ end + it "should output source for method objects" do + def @o.hi; puts 'hi world'; end + meth = @o.method(:hi) + mock_pry(binding, "show-source meth").should =~ /puts 'hi world'/ + end + + describe "on variables that shadow methods" do + before do + @method_shadow = [ + "class TestHost ", + "def hello", + "hello = proc { ' smile ' }", + "binding.pry", + "end", + "end", + "TestHost.new.hello" + ] + end + + after do + Object.remove_const(:TestHost) + end + + it "source of variable should take precedence over method that is being shadowed" do + string = mock_pry(@method_shadow,"show-source hello","exit-all") + string.include?("def hello").should == false + string =~ /proc { ' smile ' }/ + end + + it "source of method being shadowed should take precedence over variable + if given self.meth_name syntax" do + string = mock_pry(@method_shadow,"show-source self.hello","exit-all") + string.include?("def hello").should == true + end + end + end describe "on modules" do