diff --git a/lib/pry/code_object.rb b/lib/pry/code_object.rb index 078fa0b7..f1cd04ac 100644 --- a/lib/pry/code_object.rb +++ b/lib/pry/code_object.rb @@ -67,12 +67,12 @@ class Pry # lookup variables and constants that are not modules def default_lookup - if str !~ /\S#\S/ && target.eval("defined? #{str} ") =~ /variable|constant/ + if variable_or_constant?(str) obj = target.eval(str) # restrict to only objects we KNOW for sure support the full API # Do NOT support just any object that responds to source_location - if [::Proc, ::Method, ::UnboundMethod].any? { |o| obj.is_a?(o) } + if sourcable_object?(obj) Pry::Method(obj) elsif !obj.is_a?(Module) Pry::WrappedModule(obj.class) @@ -98,17 +98,39 @@ class Pry Pry::Method.from_str(str,target) || Pry::WrappedModule.from_str(str, target) end - sup = obj.super(super_level) if obj - if obj && !sup + lookup_super(obj, super_level) + end + + private + + def sourcable_object?(obj) + [::Proc, ::Method, ::UnboundMethod].any? { |o| obj.is_a?(o) } + end + + # Whether `str` represents a variable (or constant) when looked up + # in the context of the `target` binding. This is used to + # distinguish it from methods or expressions. + # @param [String] str The string to lookup + def variable_or_constant?(str) + str !~ /\S#\S/ && target.eval("defined? #{str} ") =~ /variable|constant/ + end + + def target_self + target.eval('self') + end + + # grab the nth (`super_level`) super of `obj + # @param [Object] obj + # @param [Fixnum] super_level How far up the super chain to ascend. + def lookup_super(obj, super_level) + return nil if !obj + + sup = obj.super(super_level) + if !sup raise Pry::CommandError, "No superclass found for #{obj.wrapped}" else sup end end - - private - def target_self - target.eval('self') - end end end diff --git a/lib/pry/commands/edit/method_patcher.rb b/lib/pry/commands/edit/method_patcher.rb index 3edc6ebb..744651be 100644 --- a/lib/pry/commands/edit/method_patcher.rb +++ b/lib/pry/commands/edit/method_patcher.rb @@ -1,36 +1,37 @@ +require 'forwardable' + class Pry class Command::Edit class MethodPatcher - attr_accessor :method_object - attr_accessor :target - attr_accessor :_pry_ + extend Forwardable + + def_delegators :@edit_context, :code_object, :target, :_pry_ def initialize(edit_context) - @method_object = edit_context.code_object - @target = edit_context.target - @_pry_ = edit_context._pry_ + @edit_context = edit_context end # perform the patch def perform_patch - source = wrap_for_nesting(wrap_for_owner(Pry::Editor.edit_tempfile_with_content(adjusted_lines))) - - if method_object.alias? + if code_object.alias? with_method_transaction do - _pry_.evaluate_ruby source - Pry.binding_for(method_object.owner).eval("alias #{method_object.name} #{method_object.original_name}") + _pry_.evaluate_ruby patched_code end else - _pry_.evaluate_ruby source + _pry_.evaluate_ruby patched_code end end private + def patched_code + @patched_code ||= wrap(Pry::Editor.edit_tempfile_with_content(adjusted_lines)) + end + # The method code adjusted so that the first line is rewritten # so that def self.foo --> def foo def adjusted_lines - lines = method_object.source.lines.to_a + lines = code_object.source.lines.to_a lines[0] = definition_line_for_owner(lines.first) lines end @@ -45,12 +46,13 @@ class Pry # @param [String] meth_name The method name before aliasing # @param [Module] target The owner of the method def with_method_transaction - target = Pry.binding_for(method_object.owner) - temp_name = "__pry_#{method_object.original_name}__" + target = owner_binding + temp_name = "__pry_#{code_object.original_name}__" - target.eval("alias #{temp_name} #{method_object.original_name}") + target.eval("alias #{temp_name} #{code_object.original_name}") yield - target.eval("alias #{method_object.original_name} #{temp_name}") + target.eval("alias #{code_object.name} #{code_object.original_name}") + target.eval("alias #{code_object.original_name} #{temp_name}") ensure target.eval("undef #{temp_name}") rescue nil end @@ -68,13 +70,26 @@ class Pry # @param String The original definition line. e.g. def self.foo(bar, baz=1) # @return String The new definition line. e.g. def foo(bar, baz=1) def definition_line_for_owner(line) - if line =~ /^def (?:.*?\.)?#{Regexp.escape(method_object.original_name)}(?=[\(\s;]|$)/ - "def #{method_object.original_name}#{$'}" + if line =~ /^def (?:.*?\.)?#{Regexp.escape(code_object.original_name)}(?=[\(\s;]|$)/ + "def #{code_object.original_name}#{$'}" else - raise CommandError, "Could not find original `def #{method_object.original_name}` line to patch." + raise CommandError, "Could not find original `def #{code_object.original_name}` line to patch." end end + # Provide a binding for the `code_object`'s owner context. + # @return [Binding] + def owner_binding + Pry.binding_for(code_object.owner) + end + + # Apply wrap_for_owner and wrap_for_nesting successively to `source` + # @param [String] source + # @return [String] The wrapped source. + def wrap(source) + wrap_for_nesting(wrap_for_owner(source)) + end + # Update the source code so that when it has the right owner when eval'd. # # This (combined with definition_line_for_owner) is backup for the case that @@ -84,7 +99,7 @@ class Pry # @param [String] source The source to wrap # @return [String] def wrap_for_owner(source) - Thread.current[:__pry_owner__] = method_object.owner + Thread.current[:__pry_owner__] = code_object.owner source = "Thread.current[:__pry_owner__].class_eval do\n#{source}\nend" end @@ -101,7 +116,7 @@ class Pry # @param [String] source The source to wrap. # @return [String] def wrap_for_nesting(source) - nesting = Pry::Code.from_file(method_object.source_file).nesting_at(method_object.source_line) + nesting = Pry::Code.from_file(code_object.source_file).nesting_at(code_object.source_line) (nesting + [source] + nesting.map{ "end" } + [""]).join("\n") rescue Pry::Indent::UnparseableNestingError => e diff --git a/lib/pry/commands/whereami.rb b/lib/pry/commands/whereami.rb index a8d49e22..e7dc30d8 100644 --- a/lib/pry/commands/whereami.rb +++ b/lib/pry/commands/whereami.rb @@ -44,19 +44,11 @@ class Pry "#{@file} @ line #{@line} #{@method && @method.name_with_owner}" end - def nothing_to_do? - opts.quiet? && (internal_binding?(target) || !code?) - end - def process if nothing_to_do? return elsif internal_binding?(target) - if target_self == TOPLEVEL_BINDING.eval("self") - output.puts "At the top level." - else - output.puts "Inside #{Pry.view_clip(target_self)}." - end + handle_internal_binding return end @@ -69,6 +61,22 @@ class Pry private + def nothing_to_do? + opts.quiet? && (internal_binding?(target) || !code?) + end + + def top_level? + target_self == TOPLEVEL_BINDING.eval("self") + end + + def handle_internal_binding + if top_level? + output.puts "At the top level." + else + output.puts "Inside #{Pry.view_clip(target_self)}." + end + end + def show_method? args.empty? && @method && @method.source? && @method.source_range.count < 20 && # These checks are needed in case of an eval with a binding and file/line diff --git a/lib/pry/commands/wtf.rb b/lib/pry/commands/wtf.rb index 214c70ad..ba6ee8a9 100644 --- a/lib/pry/commands/wtf.rb +++ b/lib/pry/commands/wtf.rb @@ -32,10 +32,16 @@ class Pry if opts.verbose? output.puts Pry::Code.new(_pry_.last_exception.backtrace, 0, :text).with_line_numbers.to_s else - output.puts Pry::Code.new(_pry_.last_exception.backtrace.first([captures[0].size, 0.5].max * 10), 0, :text).with_line_numbers.to_s + output.puts Pry::Code.new(_pry_.last_exception.backtrace.first(size_of_backtrace), 0, :text).with_line_numbers.to_s end end - end - Pry::Commands.add_command(Pry::Command::Wtf) + private + + def size_of_backtrace + [captures[0].size, 0.5].max * 10 + end +end + +Pry::Commands.add_command(Pry::Command::Wtf) end