diff --git a/lib/pry.rb b/lib/pry.rb index 9a79d8e1..2b1460be 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -169,3 +169,4 @@ require "pry/indent" require "pry/last_exception" require "pry/prompt" require "pry/inspector" +require 'pry/object_path' diff --git a/lib/pry/commands/cd.rb b/lib/pry/commands/cd.rb index b628176e..8e8cddb3 100644 --- a/lib/pry/commands/cd.rb +++ b/lib/pry/commands/cd.rb @@ -21,7 +21,8 @@ class Pry def process state.old_stack ||= [] - stack, state.old_stack = context_from_object_path(arg_string, _pry_, state.old_stack) + stack, state.old_stack = + ObjectPath.new(arg_string, _pry_.binding_stack, state.old_stack).resolve _pry_.binding_stack = stack if stack end end diff --git a/lib/pry/helpers/base_helpers.rb b/lib/pry/helpers/base_helpers.rb index 771535a8..b4b3f13d 100644 --- a/lib/pry/helpers/base_helpers.rb +++ b/lib/pry/helpers/base_helpers.rb @@ -119,73 +119,6 @@ class Pry Pry::Pager.page(text, out) end - - # @param [String] arg_string The object path expressed as a string. - # @param [Pry] _pry_ The relevant Pry instance. - # @param [Array] old_stack The state of the old binding stack - # @return [Array, Array>] An array - # containing two elements: The new `binding_stack` and the old `binding_stack`. - def context_from_object_path(arg_string, _pry_=nil, old_stack=[]) - - # Extract command arguments. Delete blank arguments like " ", but - # don't delete empty strings like "". - path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } - stack = _pry_.binding_stack.dup - state_old_stack = old_stack - - # Special case when we only get a single "/", return to root. - if path.empty? - state_old_stack = stack.dup unless old_stack.empty? - stack = [stack.first] - end - - path.each_with_index do |context, i| - begin - case context.chomp - when "" - state_old_stack = stack.dup - stack = [stack.first] - when "::" - state_old_stack = stack.dup - stack.push(TOPLEVEL_BINDING) - when "." - next - when ".." - unless stack.size == 1 - # Don't rewrite old_stack if we're in complex expression - # (e.g.: `cd 1/2/3/../4). - state_old_stack = stack.dup if path.first == ".." - stack.pop - end - when "-" - unless old_stack.empty? - # Interchange current stack and old stack with each other. - stack, state_old_stack = state_old_stack, stack - end - else - state_old_stack = stack.dup if i == 0 - stack.push(Pry.binding_for(stack.last.eval(context))) - end - - rescue RescuableException => e - # Restore old stack to its initial values. - state_old_stack = old_stack - - msg = [ - "Bad object path: #{arg_string}.", - "Failed trying to resolve: #{context}.", - e.inspect - ].join(' ') - - CommandError.new(msg).tap do |err| - err.set_backtrace e.backtrace - raise err - end - end - end - return stack, state_old_stack - end - end end end diff --git a/lib/pry/input_completer.rb b/lib/pry/input_completer.rb index 74a249f9..86f9abf0 100644 --- a/lib/pry/input_completer.rb +++ b/lib/pry/input_completer.rb @@ -60,7 +60,7 @@ class Pry::InputCompleter if path.call.empty? target = options[:target] else - target, _ = Pry::Helpers::BaseHelpers.context_from_object_path(path.call, @pry) + target, _ = Pry::ObjectPath.new(path.call, @pry.binding_stack).resolve target = target.last end diff --git a/lib/pry/object_path.rb b/lib/pry/object_path.rb new file mode 100644 index 00000000..b669d3ec --- /dev/null +++ b/lib/pry/object_path.rb @@ -0,0 +1,89 @@ +class Pry + # `ObjectPath` implements the resolution of "object paths", which are strings + # that are similar to filesystem paths but meant for traversing Ruby objects. + # Examples of valid object paths include: + # + # x + # @foo/@bar + # "string"/upcase + # Pry/Method + # + # Object paths are mostly relevant in the context of the `cd` command. + # @see https://github.com/pry/pry/wiki/State-navigation + class ObjectPath + # @param [String] path_string The object path expressed as a string. + # @param [Array] current_stack The current state of the binding + # stack. + # @param [Array] old_stack The previous state of the binding + # stack, if applicable. + def initialize(path_string, current_stack, old_stack=[]) + @path_string = path_string + @current_stack = current_stack + @old_stack = old_stack + end + + # @return [Array(Array, Array)] an array + # containing two elements, the new binding stack and the old binding + # stack. + def resolve + # Extract command arguments. Delete blank arguments like " ", but + # don't delete empty strings like "". + path = @path_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } + stack = @current_stack.dup + state_old_stack = @old_stack + + # Special case when we only get a single "/", return to root. + if path.empty? + state_old_stack = stack.dup unless @old_stack.empty? + stack = [stack.first] + end + + path.each_with_index do |context, i| + begin + case context.chomp + when "" + state_old_stack = stack.dup + stack = [stack.first] + when "::" + state_old_stack = stack.dup + stack.push(TOPLEVEL_BINDING) + when "." + next + when ".." + unless stack.size == 1 + # Don't rewrite old_stack if we're in complex expression + # (e.g.: `cd 1/2/3/../4). + state_old_stack = stack.dup if path.first == ".." + stack.pop + end + when "-" + unless @old_stack.empty? + # Interchange current stack and old stack with each other. + stack, state_old_stack = state_old_stack, stack + end + else + state_old_stack = stack.dup if i == 0 + stack.push(Pry.binding_for(stack.last.eval(context))) + end + + rescue RescuableException => e + # Restore old stack to its initial values. + state_old_stack = @old_stack + + msg = [ + "Bad object path: #{@path_string}.", + "Failed trying to resolve: #{context}.", + e.inspect + ].join(' ') + + CommandError.new(msg).tap do |err| + err.set_backtrace e.backtrace + raise err + end + end + end + + [stack, state_old_stack] + end + end +end