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.9.2"
Commands =
Pry::CommandSet.new do
  create_command "up", "Go up to the caller's context." do
    include FrameHelpers

    banner "Usage: up [OPTIONS]\nGo up to the caller's context. Accepts optional numeric parameter for how many frames to move up.\nAlso accepts a string (regex) instead of numeric; for jumping to nearest parent method frame which matches the regex.\ne.g: up      #=> Move up 1 stack frame.\ne.g: up 3    #=> Move up 2 stack frames.\ne.g: up meth #=> Jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`.\n"

    def process
      inc = args.first.nil? ? "1" : args.first

      if !frame_manager
        raise Pry::CommandError, "Nowhere to go!"
      else
        if inc =~ /\d+/
          frame_manager.change_frame_to frame_manager.binding_index + inc.to_i
        elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(inc)
          new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)
          frame_manager.change_frame_to new_frame_index
        elsif inc =~ /^[^-].*$/
          new_frame_index = find_frame_by_regex(Regexp.new(inc), :up)
          frame_manager.change_frame_to new_frame_index
        end
      end
    end
  end

  create_command "down", "Go down to the callee's context." do
    include FrameHelpers

    banner "Usage: down [OPTIONS]\nGo down to the callee's context. Accepts optional numeric parameter for how many frames to move down.\nAlso accepts a string (regex) instead of numeric; for jumping to nearest child method frame which matches the regex.\ne.g: down      #=> Move down 1 stack frame.\ne.g: down 3    #=> Move down 2 stack frames.\ne.g: down meth #=> Jump to nearest child stack frame whose method matches /meth/ regex, i.e `my_method`.\n"

    def process
      inc = args.first.nil? ? "1" : args.first

      if !frame_manager
        raise Pry::CommandError, "Nowhere to go!"
      else
        if inc =~ /\d+/
          if frame_manager.binding_index - inc.to_i < 0
            raise Pry::CommandError, "At bottom of stack, cannot go further!"
          else
            frame_manager.change_frame_to frame_manager.binding_index - inc.to_i
          end
        elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(inc)
          new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :down)
          frame_manager.change_frame_to new_frame_index
        elsif inc =~ /^[^-].*$/
          new_frame_index = find_frame_by_regex(Regexp.new(inc), :down)
          frame_manager.change_frame_to new_frame_index
        end
      end
    end
  end

  create_command "frame", "Switch to a particular frame." do
    include FrameHelpers

    banner "Usage: frame [OPTIONS]\nSwitch to a particular frame. Accepts numeric parameter (or regex for method name) for the target frame to switch to (use with show-stack).\nNegative frame numbers allowed. When given no parameter show information about the current frame.\n\ne.g: frame 4         #=> jump to the 4th frame\ne.g: frame meth      #=> jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`\ne.g: frame -2        #=> jump to the second-to-last frame\ne.g: frame           #=> show information info about current frame\n"

    def process
      if !frame_manager
        raise Pry::CommandError, "nowhere to go!"
      else

        if args[0] =~ /\d+/
          frame_manager.change_frame_to args[0].to_i
        elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(args[0])
          new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)
          frame_manager.change_frame_to new_frame_index
        elsif args[0] =~ /^[^-].*$/
          new_frame_index = find_frame_by_regex(Regexp.new(args[0]), :up)
          frame_manager.change_frame_to new_frame_index
        else
          output.puts "##{frame_manager.binding_index} #{frame_info(target, true)}"
        end
      end
    end
  end

  create_command "show-stack", "Show all frames" do
    include FrameHelpers

    banner "Usage: show-stack [OPTIONS]\nShow all accessible stack frames.\ne.g: show-stack -v\n"

    def options(opt)
      opt.on :v, :verbose, "Include extra information."
      opt.on :H, :head, "Display the first N stack frames (defaults to 10).", :optional_argument => true, :as => Integer, :default => 10
      opt.on :T, :tail, "Display the last N stack frames (defaults to 10).", :optional_argument => true, :as => Integer, :default => 10
      opt.on :c, :current, "Display N frames either side of current frame (default to 5).", :optional_argument => true, :as => Integer, :default => 5
    end

    def memoized_info(index, b, verbose)
      frame_manager.user[:frame_info] ||= Hash.new { |h, k| h[k] = [] }

      if verbose
        frame_manager.user[:frame_info][:v][index]      ||= frame_info(b, verbose)
      else
        frame_manager.user[:frame_info][:normal][index] ||= frame_info(b, verbose)
      end
    end

    private :memoized_info

    # @return [Array<Fixnum, Array<Binding>>] Return tuple of
    #   base_frame_index and the array of frames.
    def selected_stack_frames
      if opts.present?(:head)
        [0, frame_manager.bindings[0..(opts[:head] - 1)]]

      elsif opts.present?(:tail)
        tail = opts[:tail]
        if tail > frame_manager.bindings.size
          tail = frame_manager.bindings.size
        end

        base_frame_index = frame_manager.bindings.size - tail
        [base_frame_index, frame_manager.bindings[base_frame_index..-1]]

      elsif opts.present?(:current)
        first_frame_index = frame_manager.binding_index - (opts[:current])
        first_frame_index = 0 if first_frame_index < 0
        last_frame_index = frame_manager.binding_index + (opts[:current])
        [first_frame_index, frame_manager.bindings[first_frame_index..last_frame_index]]

      else
        [0, frame_manager.bindings]
      end
    end

    private :selected_stack_frames

    def process
      if !frame_manager
        output.puts "No caller stack available!"
      else
        content = ""
        content << "\n#{text.bold("Showing all accessible frames in stack (#{frame_manager.bindings.size} in total):")}\n--\n"

        base_frame_index, frames = selected_stack_frames
        frames.each_with_index do |b, index|
          i = index + base_frame_index
          if i == frame_manager.binding_index
            content << "=> ##{i} #{memoized_info(i, b, opts[:v])}\n"
          else
            content << "   ##{i} #{memoized_info(i, b, opts[:v])}\n"
          end
        end

        stagger_output content
      end
    end

  end
end

Class Method Summary collapse

Class Method Details

.bindings_equal?(b1, b2) ⇒ Boolean

Simple test to check whether two Binding objects are equal.



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

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



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

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.



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

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



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

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

.frame_manager(_pry_) ⇒ PryStackExplorer::FrameManager



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

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

.frame_managers(_pry_) ⇒ Array

Return the complete frame manager stack for the Pry instance



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

def frame_managers(_pry_)
  frame_hash[_pry_]
end

.pop_frame_manager(_pry_) ⇒ Pry::FrameManager

Delete the currently active frame manager



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

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