Module: PryStackExplorer

Defined in:
lib/pry-stack_explorer.rb,
lib/pry-stack_explorer/version.rb,
lib/pry-stack_explorer/commands.rb,
lib/pry-stack_explorer/frame_manager.rb,
lib/pry-stack_explorer/when_started_hook.rb

Defined Under Namespace

Modules: FrameHelpers Classes: FrameManager, WhenStartedHook

Constant Summary collapse

VERSION =
'0.4.12'
Commands =
Pry::CommandSet.new do
  create_command "up", "Go up to the caller's context." do
    include FrameHelpers

    banner "      Usage: up [OPTIONS]\n        Go up to the caller's context. Accepts optional numeric parameter for how many frames to move up.\n        Also accepts a string (regex) instead of numeric; for jumping to nearest parent method frame which matches the regex.\n        e.g: up      #=> Move up 1 stack frame.\n        e.g: up 3    #=> Move up 2 stack frames.\n        e.g: up meth #=> Jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`.\n    BANNER\n\n    def process\n      inc = args.first.nil? ? \"1\" : args.first\n\n      if !frame_manager\n        raise Pry::CommandError, \"Nowhere to go!\"\n      else\n        if inc =~ /\\d+/\n          frame_manager.change_frame_to frame_manager.binding_index + inc.to_i\n        elsif match = /^([A-Z]+[^#.]*)(#|\\.)(.+)$/.match(inc)\n          new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)\n          frame_manager.change_frame_to new_frame_index\n        elsif inc =~ /^[^-].*$/\n          new_frame_index = find_frame_by_regex(Regexp.new(inc), :up)\n          frame_manager.change_frame_to new_frame_index\n        end\n      end\n    end\n  end\n\n  create_command \"down\", \"Go down to the callee's context.\" do\n    include FrameHelpers\n\n    banner <<-BANNER\n      Usage: down [OPTIONS]\n        Go down to the callee's context. Accepts optional numeric parameter for how many frames to move down.\n        Also accepts a string (regex) instead of numeric; for jumping to nearest child method frame which matches the regex.\n        e.g: down      #=> Move down 1 stack frame.\n        e.g: down 3    #=> Move down 2 stack frames.\n        e.g: down meth #=> Jump to nearest child stack frame whose method matches /meth/ regex, i.e `my_method`.\n    BANNER\n\n    def process\n      inc = args.first.nil? ? \"1\" : args.first\n\n      if !frame_manager\n        raise Pry::CommandError, \"Nowhere to go!\"\n      else\n        if inc =~ /\\d+/\n          if frame_manager.binding_index - inc.to_i < 0\n            raise Pry::CommandError, \"At bottom of stack, cannot go further!\"\n          else\n            frame_manager.change_frame_to frame_manager.binding_index - inc.to_i\n          end\n        elsif match = /^([A-Z]+[^#.]*)(#|\\.)(.+)$/.match(inc)\n          new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :down)\n          frame_manager.change_frame_to new_frame_index\n        elsif inc =~ /^[^-].*$/\n          new_frame_index = find_frame_by_regex(Regexp.new(inc), :down)\n          frame_manager.change_frame_to new_frame_index\n        end\n      end\n    end\n  end\n\n  create_command \"frame\", \"Switch to a particular frame.\" do\n    include FrameHelpers\n\n    banner <<-BANNER\n      Usage: frame [OPTIONS]\n        Switch to a particular frame. Accepts numeric parameter (or regex for method name) for the target frame to switch to (use with show-stack).\n        Negative frame numbers allowed. When given no parameter show information about the current frame.\n\n        e.g: frame 4         #=> jump to the 4th frame\n        e.g: frame meth      #=> jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`\n        e.g: frame -2        #=> jump to the second-to-last frame\n        e.g: frame           #=> show information info about current frame\n    BANNER\n\n    def process\n      if !frame_manager\n        raise Pry::CommandError, \"nowhere to go!\"\n      else\n\n        if args[0] =~ /\\d+/\n          frame_manager.change_frame_to args[0].to_i\n        elsif match = /^([A-Z]+[^#.]*)(#|\\.)(.+)$/.match(args[0])\n          new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)\n          frame_manager.change_frame_to new_frame_index\n        elsif args[0] =~ /^[^-].*$/\n          new_frame_index = find_frame_by_regex(Regexp.new(args[0]), :up)\n          frame_manager.change_frame_to new_frame_index\n        else\n          output.puts \"#\#{frame_manager.binding_index} \#{frame_info(target, true)}\"\n        end\n      end\n    end\n  end\n\n  create_command \"show-stack\", \"Show all frames\" do\n    include FrameHelpers\n\n    banner <<-BANNER\n      Usage: show-stack [OPTIONS]\n        Show all accessible stack frames.\n        e.g: show-stack -v\n    BANNER\n\n    def options(opt)\n      opt.on :v, :verbose, \"Include extra information.\"\n      opt.on :H, :head, \"Display the first N stack frames (defaults to 10).\", :optional_argument => true, :as => Integer, :default => 10\n      opt.on :T, :tail, \"Display the last N stack frames (defaults to 10).\", :optional_argument => true, :as => Integer, :default => 10\n      opt.on :c, :current, \"Display N frames either side of current frame (default to 5).\", :optional_argument => true, :as => Integer, :default => 5\n    end\n\n    def memoized_info(index, b, verbose)\n      frame_manager.user[:frame_info] ||= Hash.new { |h, k| h[k] = [] }\n\n      if verbose\n        frame_manager.user[:frame_info][:v][index]      ||= frame_info(b, verbose)\n      else\n        frame_manager.user[:frame_info][:normal][index] ||= frame_info(b, verbose)\n      end\n    end\n\n    private :memoized_info\n\n    # @return [Array<Fixnum, Array<Binding>>] Return tuple of\n    #   base_frame_index and the array of frames.\n    def selected_stack_frames\n      if opts.present?(:head)\n        [0, frame_manager.bindings[0..(opts[:head] - 1)]]\n\n      elsif opts.present?(:tail)\n        tail = opts[:tail]\n        if tail > frame_manager.bindings.size\n          tail = frame_manager.bindings.size\n        end\n\n        base_frame_index = frame_manager.bindings.size - tail\n        [base_frame_index, frame_manager.bindings[base_frame_index..-1]]\n\n      elsif opts.present?(:current)\n        first_frame_index = frame_manager.binding_index - (opts[:current])\n        first_frame_index = 0 if first_frame_index < 0\n        last_frame_index = frame_manager.binding_index + (opts[:current])\n        [first_frame_index, frame_manager.bindings[first_frame_index..last_frame_index]]\n\n      else\n        [0, frame_manager.bindings]\n      end\n    end\n\n    private :selected_stack_frames\n\n    def process\n      if !frame_manager\n        output.puts \"No caller stack available!\"\n      else\n        content = \"\"\n        content << \"\\n\#{bold(\"Showing all accessible frames in stack (\#{frame_manager.bindings.size} in total):\")}\\n--\\n\"\n\n        base_frame_index, frames = selected_stack_frames\n        frames.each_with_index do |b, index|\n          i = index + base_frame_index\n          if i == frame_manager.binding_index\n            content << \"=> #\#{i} \#{memoized_info(i, b, opts[:v])}\\n\"\n          else\n            content << \"   #\#{i} \#{memoized_info(i, b, opts[:v])}\\n\"\n          end\n        end\n\n        stagger_output content\n      end\n    end\n\n  end\nend\n"

Class Method Summary collapse

Class Method Details

.bindings_equal?(b1, b2) ⇒ Boolean

Simple test to check whether two Binding objects are equal.



109
110
111
112
113
114
# File 'lib/pry-stack_explorer.rb', line 109

def bindings_equal?(b1, b2)
  (b1.eval('self').equal?(b2.eval('self'))) &&
    (b1.eval('__method__') == b2.eval('__method__')) &&
    (b1.eval('local_variables').map { |v| b1.eval("#{v}") }.equal?(
     b2.eval('local_variables').map { |v| b2.eval("#{v}") }))
end

.clear_frame_managers(_pry_) ⇒ Object Also known as: delete_frame_managers

Clear the stack of frame managers for the Pry instance



93
94
95
96
# File 'lib/pry-stack_explorer.rb', line 93

def clear_frame_managers(_pry_)
  pop_frame_manager(_pry_) until frame_managers(_pry_).empty?
  frame_hash.delete(_pry_) # this line should be unnecessary!
end

.create_and_push_frame_manager(bindings, _pry_, options = {}) ⇒ Object

Create a Pry::FrameManager object and push it onto the frame manager stack for the relevant _pry_ instance.



35
36
37
38
39
40
# File 'lib/pry-stack_explorer.rb', line 35

def create_and_push_frame_manager(bindings, _pry_, options={})
  fm = FrameManager.new(bindings, _pry_)
  frame_hash[_pry_].push fm
  push_helper(fm, options)
  fm
end

.frame_hashHash



19
20
21
# File 'lib/pry-stack_explorer.rb', line 19

def frame_hash
  Thread.current[:__pry_frame_managers__] ||= Hash.new { |h, k| h[k] = [] }
end

.frame_manager(_pry_) ⇒ PryStackExplorer::FrameManager



101
102
103
# File 'lib/pry-stack_explorer.rb', line 101

def frame_manager(_pry_)
  frame_hash[_pry_].last
end

.frame_managers(_pry_) ⇒ Array

Return the complete frame manager stack for the Pry instance



27
28
29
# File 'lib/pry-stack_explorer.rb', line 27

def frame_managers(_pry_)
  frame_hash[_pry_]
end

.pop_frame_manager(_pry_) ⇒ Pry::FrameManager

Delete the currently active frame manager



60
61
62
63
64
65
66
# File 'lib/pry-stack_explorer.rb', line 60

def pop_frame_manager(_pry_)
  return if frame_managers(_pry_).empty?

  popped_fm = frame_managers(_pry_).pop
  pop_helper(popped_fm, _pry_)
  popped_fm
end

.pop_helper(popped_fm, _pry_) ⇒ Object

Restore the Pry instance to operate on the previous binding. Also responsible for restoring Pry instance's backtrace.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/pry-stack_explorer.rb', line 72

def pop_helper(popped_fm, _pry_)
  if frame_managers(_pry_).empty?
    if _pry_.binding_stack.empty?
      _pry_.binding_stack.push popped_fm.prior_binding
    else
      _pry_.binding_stack[-1] = popped_fm.prior_binding
    end

    frame_hash.delete(_pry_)
  else
    frame_manager(_pry_).refresh_frame(false)
  end

  # restore backtrace
  _pry_.backtrace = popped_fm.prior_backtrace
end

.push_helper(fm, options = {}) ⇒ Object

Update the Pry instance to operate on the specified frame for the current frame manager.



46
47
48
49
50
51
52
# File 'lib/pry-stack_explorer.rb', line 46

def push_helper(fm, options={})
  options = {
    :initial_frame => 0
  }.merge!(options)

  fm.change_frame_to(options[:initial_frame], false)
end