├── .gemtest ├── .gitignore ├── .travis.yml ├── .yardopts ├── CHANGELOG ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── examples ├── example.rb └── example2.rb ├── lib ├── pry-stack_explorer.rb └── pry-stack_explorer │ ├── commands.rb │ ├── frame_manager.rb │ ├── version.rb │ └── when_started_hook.rb ├── pry-stack_explorer.gemspec ├── test ├── helper.rb ├── test_commands.rb ├── test_frame_manager.rb └── test_stack_explorer.rb └── tester.rb /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banister/pry-stack_explorer/5642864b25293af3cf0c3374658a10ec61b012c9/.gemtest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | *.so 3 | *.o 4 | *.def 5 | doc/ 6 | pkg/ 7 | .yardoc/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: 2 | rake test --trace 3 | 4 | rvm: 5 | - 1.9.2 6 | - 1.9.3 7 | 8 | notifications: 9 | irc: "irc.freenode.org#pry" 10 | recipients: 11 | - jrmair@gmail.com 12 | 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banister/pry-stack_explorer/5642864b25293af3cf0c3374658a10ec61b012c9/CHANGELOG -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | ------- 3 | 4 | (The MIT License) 5 | 6 | Copyright (c) 2011 John Mair (banisterfiend) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | 'Software'), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MOVED TO: https://github.com/pry/pry-stack_explorer -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $:.unshift 'lib' 2 | 3 | dlext = Config::CONFIG['DLEXT'] 4 | direc = File.dirname(__FILE__) 5 | 6 | PROJECT_NAME = "pry-stack_explorer" 7 | 8 | require 'rake/clean' 9 | require 'rake/gempackagetask' 10 | require "#{PROJECT_NAME}/version" 11 | 12 | CLOBBER.include("**/*~", "**/*#*", "**/*.log") 13 | CLEAN.include("**/*#*", "**/*#*.*", "**/*_flymake*.*", "**/*_flymake", 14 | "**/*.rbc", "**/.#*.*") 15 | 16 | def apply_spec_defaults(s) 17 | s.name = PROJECT_NAME 18 | s.summary = "Walk the stack in a Pry session" 19 | s.version = PryStackExplorer::VERSION 20 | s.date = Time.now.strftime '%Y-%m-%d' 21 | s.author = "John Mair (banisterfiend)" 22 | s.email = 'jrmair@gmail.com' 23 | s.description = s.summary 24 | s.require_path = 'lib' 25 | s.add_dependency("binding_of_caller","~>0.6.2") 26 | s.add_dependency("pry","~>0.9.8.2") 27 | s.add_development_dependency("bacon","~>1.1.0") 28 | s.add_development_dependency('rake', '~> 0.9') 29 | s.required_ruby_version = '>= 1.9.2' 30 | s.homepage = "https://github.com/banister" 31 | s.files = `git ls-files`.split("\n") 32 | s.test_files = `git ls-files -- test/*`.split("\n") 33 | end 34 | 35 | desc "run pry with plugin enabled" 36 | task :pry do 37 | exec("pry -I#{direc}/lib/ -r #{direc}/lib/#{PROJECT_NAME}") 38 | end 39 | 40 | desc "Run example" 41 | task :example do 42 | sh "ruby -I#{direc}/lib/ #{direc}/examples/example.rb " 43 | end 44 | 45 | desc "Run example2" 46 | task :example2 do 47 | sh "ruby -I#{direc}/lib/ #{direc}/examples/example2.rb " 48 | end 49 | 50 | desc "Show version" 51 | task :version do 52 | puts "PryStackExplorer version: #{PryStackExplorer::VERSION}" 53 | end 54 | 55 | desc "run tests" 56 | task :default => :test 57 | 58 | desc "run tests" 59 | task :test do 60 | sh "bacon -Itest -rubygems -a -q" 61 | end 62 | 63 | desc "generate gemspec" 64 | task :gemspec => "ruby:gemspec" 65 | 66 | namespace :ruby do 67 | spec = Gem::Specification.new do |s| 68 | apply_spec_defaults(s) 69 | s.platform = Gem::Platform::RUBY 70 | end 71 | 72 | Rake::GemPackageTask.new(spec) do |pkg| 73 | pkg.need_zip = false 74 | pkg.need_tar = false 75 | end 76 | 77 | desc "Generate gemspec file" 78 | task :gemspec do 79 | File.open("#{spec.name}.gemspec", "w") do |f| 80 | f << spec.to_ruby 81 | end 82 | end 83 | end 84 | 85 | desc "build all platform gems at once" 86 | task :gems => [:clean, :rmgems, :gemspec, "ruby:gem"] 87 | 88 | desc "remove all platform gems" 89 | task :rmgems => ["ruby:clobber_package"] 90 | 91 | desc "reinstall gem" 92 | task :reinstall => :gems do 93 | sh "gem uninstall pry-stack_explorer" rescue nil 94 | sh "gem install #{direc}/pkg/#{PROJECT_NAME}-#{PryStackExplorer::VERSION}.gem" 95 | end 96 | 97 | desc "build and push latest gems" 98 | task :pushgems => :gems do 99 | chdir("#{File.dirname(__FILE__)}/pkg") do 100 | Dir["*.gem"].each do |gemfile| 101 | sh "gem push #{gemfile}" 102 | end 103 | end 104 | end 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/example.rb: -------------------------------------------------------------------------------- 1 | unless Object.const_defined? :PryStackExplorer 2 | $:.unshift File.expand_path '../../lib', __FILE__ 3 | require 'pry' 4 | end 5 | 6 | require 'pry-stack_explorer' 7 | 8 | def alphabet(y) 9 | x = 20 10 | b 11 | end 12 | 13 | def b 14 | x = 30 15 | proc { 16 | c 17 | }.call 18 | end 19 | 20 | def c 21 | u = 50 22 | binding.pry 23 | end 24 | 25 | # hello 26 | def beta 27 | gamma 28 | end 29 | 30 | def gamma 31 | zeta 32 | end 33 | 34 | def zeta 35 | vitamin = 100 36 | binding.pry 37 | end 38 | # 39 | 40 | proc { 41 | class J 42 | alphabet(22) 43 | end 44 | }.call 45 | 46 | -------------------------------------------------------------------------------- /examples/example2.rb: -------------------------------------------------------------------------------- 1 | unless Object.const_defined? :PryStackExplorer 2 | $:.unshift File.expand_path '../../lib', __FILE__ 3 | require 'pry' 4 | end 5 | 6 | require 'pry-stack_explorer' 7 | 8 | def alpha 9 | x = "hello" 10 | beta 11 | puts x 12 | end 13 | 14 | def beta 15 | binding.pry 16 | end 17 | 18 | alpha 19 | -------------------------------------------------------------------------------- /lib/pry-stack_explorer.rb: -------------------------------------------------------------------------------- 1 | # pry-stack_explorer.rb 2 | # (C) John Mair (banisterfiend); MIT license 3 | 4 | require "pry-stack_explorer/version" 5 | require "pry-stack_explorer/commands" 6 | require "pry-stack_explorer/frame_manager" 7 | require "pry-stack_explorer/when_started_hook" 8 | require "pry" 9 | require "binding_of_caller" 10 | 11 | module PryStackExplorer 12 | 13 | # short-hand for `PryStackExplorer` 14 | ::SE = self 15 | 16 | class << self 17 | # @return [Hash] The hash storing all frames for all Pry instances for 18 | # the current thread. 19 | def frame_hash 20 | Thread.current[:__pry_frame_managers__] ||= Hash.new { |h, k| h[k] = [] } 21 | end 22 | 23 | # Return the complete frame manager stack for the Pry instance 24 | # @param [Pry] _pry_ The Pry instance associated with the frame 25 | # managers 26 | # @return [Array] The stack of Pry::FrameManager objections 27 | def frame_managers(_pry_) 28 | frame_hash[_pry_] 29 | end 30 | 31 | # Create a `Pry::FrameManager` object and push it onto the frame 32 | # manager stack for the relevant `_pry_` instance. 33 | # @param [Array] bindings The array of bindings (frames) 34 | # @param [Pry] _pry_ The Pry instance associated with the frame manager 35 | def create_and_push_frame_manager(bindings, _pry_, options={}) 36 | fm = FrameManager.new(bindings, _pry_) 37 | frame_hash[_pry_].push fm 38 | push_helper(fm, options) 39 | fm 40 | end 41 | 42 | # Update the Pry instance to operate on the specified frame for the 43 | # current frame manager. 44 | # @param [PryStackExplorer::FrameManager] fm The active frame manager. 45 | # @param [Hash] options The options hash. 46 | def push_helper(fm, options={}) 47 | options = { 48 | :initial_frame => 0 49 | }.merge!(options) 50 | 51 | fm.change_frame_to(options[:initial_frame], false) 52 | end 53 | 54 | private :push_helper 55 | 56 | # Delete the currently active frame manager 57 | # @param [Pry] _pry_ The Pry instance associated with the frame 58 | # managers. 59 | # @return [Pry::FrameManager] The popped frame manager. 60 | def pop_frame_manager(_pry_) 61 | return if frame_managers(_pry_).empty? 62 | 63 | popped_fm = frame_managers(_pry_).pop 64 | pop_helper(popped_fm, _pry_) 65 | popped_fm 66 | end 67 | 68 | # Restore the Pry instance to operate on the previous 69 | # binding. Also responsible for restoring Pry instance's backtrace. 70 | # @param [Pry::FrameManager] popped_fm The recently popped frame manager. 71 | # @param [Pry] _pry_ The Pry instance associated with the frame managers. 72 | def pop_helper(popped_fm, _pry_) 73 | if frame_managers(_pry_).empty? 74 | if _pry_.binding_stack.empty? 75 | _pry_.binding_stack.push popped_fm.prior_binding 76 | else 77 | _pry_.binding_stack[-1] = popped_fm.prior_binding 78 | end 79 | 80 | frame_hash.delete(_pry_) 81 | else 82 | frame_manager(_pry_).refresh_frame(false) 83 | end 84 | 85 | # restore backtrace 86 | _pry_.backtrace = popped_fm.prior_backtrace 87 | end 88 | 89 | private :pop_helper 90 | 91 | # Clear the stack of frame managers for the Pry instance 92 | # @param [Pry] _pry_ The Pry instance associated with the frame managers 93 | def clear_frame_managers(_pry_) 94 | pop_frame_manager(_pry_) until frame_managers(_pry_).empty? 95 | frame_hash.delete(_pry_) # this line should be unnecessary! 96 | end 97 | 98 | alias_method :delete_frame_managers, :clear_frame_managers 99 | 100 | # @return [PryStackExplorer::FrameManager] The currently active frame manager 101 | def frame_manager(_pry_) 102 | frame_hash[_pry_].last 103 | end 104 | 105 | # Simple test to check whether two `Binding` objects are equal. 106 | # @param [Binding] b1 First binding. 107 | # @param [Binding] b2 Second binding. 108 | # @return [Boolean] Whether the `Binding`s are equal. 109 | def bindings_equal?(b1, b2) 110 | (b1.eval('self') == b2.eval('self')) && 111 | (b1.eval('__method__') == b2.eval('__method__')) && 112 | (b1.eval('local_variables').map { |v| b1.eval("#{v}") } == 113 | b2.eval('local_variables').map { |v| b2.eval("#{v}") }) 114 | end 115 | end 116 | end 117 | 118 | Pry.config.hooks.add_hook(:after_session, :delete_frame_manager) do |_, _, _pry_| 119 | PryStackExplorer.clear_frame_managers(_pry_) 120 | end 121 | 122 | Pry.config.hooks.add_hook(:when_started, :save_caller_bindings, PryStackExplorer::WhenStartedHook.new) 123 | 124 | # Import the StackExplorer commands 125 | Pry.config.commands.import PryStackExplorer::Commands 126 | 127 | # monkey-patch the whereami command to show some frame information, 128 | # useful for navigating stack. 129 | Pry.config.commands.before_command("whereami") do |num| 130 | if PryStackExplorer.frame_manager(_pry_) 131 | bindings = PryStackExplorer.frame_manager(_pry_).bindings 132 | binding_index = PryStackExplorer.frame_manager(_pry_).binding_index 133 | 134 | output.puts "\n" 135 | output.puts "#{Pry::Helpers::Text.bold('Frame number:')} #{binding_index}/#{bindings.size - 1}" 136 | output.puts "#{Pry::Helpers::Text.bold('Frame type:')} #{bindings[binding_index].frame_type}" if bindings[binding_index].frame_type 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /lib/pry-stack_explorer/commands.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | module PryStackExplorer 4 | module FrameHelpers 5 | private 6 | 7 | # @return [PryStackExplorer::FrameManager] The active frame manager for 8 | # the current `Pry` instance. 9 | def frame_manager 10 | PryStackExplorer.frame_manager(_pry_) 11 | end 12 | 13 | # @return [Array] All the frame 14 | # managers for the current `Pry` instance. 15 | def frame_managers 16 | PryStackExplorer.frame_managers(_pry_) 17 | end 18 | 19 | # @return [Boolean] Whether there is a context to return to once 20 | # the current `frame_manager` is popped. 21 | def prior_context_exists? 22 | frame_managers.count > 1 || frame_manager.prior_binding 23 | end 24 | 25 | # Return a description of the frame (binding). 26 | # This is only useful for regular old bindings that have not been 27 | # enhanced by `#of_caller`. 28 | # @param [Binding] b The binding. 29 | # @return [String] A description of the frame (binding). 30 | def frame_description(b) 31 | b_self = b.eval('self') 32 | b_method = b.eval('__method__') 33 | 34 | if b_method && b_method != :__binding__ && b_method != :__binding_impl__ 35 | b_method.to_s 36 | elsif b_self.instance_of?(Module) 37 | "" 38 | elsif b_self.instance_of?(Class) 39 | "" 40 | else 41 | "
" 42 | end 43 | end 44 | 45 | # Return a description of the passed binding object. Accepts an 46 | # optional `verbose` parameter. 47 | # @param [Binding] b The binding. 48 | # @param [Boolean] verbose Whether to generate a verbose description. 49 | # @return [String] The description of the binding. 50 | def frame_info(b, verbose = false) 51 | meth = b.eval('__method__') 52 | b_self = b.eval('self') 53 | meth_obj = Pry::Method.from_binding(b) if meth 54 | 55 | type = b.frame_type ? "[#{b.frame_type}]".ljust(9) : "" 56 | desc = b.frame_description ? "#{b.frame_description}" : "#{frame_description(b)}" 57 | sig = meth_obj ? "<#{signature_with_owner(meth_obj)}>" : "" 58 | 59 | self_clipped = "#{Pry.view_clip(b_self)}" 60 | path = "@ #{b.eval('__FILE__')}:#{b.eval('__LINE__')}" 61 | 62 | if !verbose 63 | "#{type} #{desc} #{sig}" 64 | else 65 | "#{type} #{desc} #{sig}\n in #{self_clipped} #{path}" 66 | end 67 | end 68 | 69 | # @param [Pry::Method] meth_obj The method object. 70 | # @return [String] Signature for the method object in Class#method format. 71 | def signature_with_owner(meth_obj) 72 | if !meth_obj.undefined? 73 | args = meth_obj.parameters.inject([]) do |arr, (type, name)| 74 | name ||= (type == :block ? 'block' : "arg#{arr.size + 1}") 75 | arr << case type 76 | when :req then name.to_s 77 | when :opt then "#{name}=?" 78 | when :rest then "*#{name}" 79 | when :block then "&#{name}" 80 | else '?' 81 | end 82 | end 83 | "#{meth_obj.name_with_owner}(#{args.join(', ')})" 84 | else 85 | "#{meth_obj.name_with_owner}(UNKNOWN) (undefined method)" 86 | end 87 | end 88 | 89 | # Regexp.new(args[0]) 90 | def find_frame_by_regex(regex, up_or_down) 91 | start_index = frame_manager.binding_index 92 | 93 | if up_or_down == :down 94 | enum = frame_manager.bindings[0..start_index - 1].reverse_each 95 | else 96 | enum = frame_manager.bindings[start_index + 1..-1] 97 | end 98 | 99 | new_frame = enum.find do |b| 100 | b.eval("__method__").to_s =~ regex 101 | end 102 | 103 | frame_index = frame_manager.bindings.index(new_frame) 104 | 105 | if frame_index 106 | frame_index 107 | else 108 | raise Pry::CommandError, "No frame that matches #{regex.source} found!" 109 | end 110 | end 111 | end 112 | 113 | Commands = Pry::CommandSet.new do 114 | create_command "up", "Go up to the caller's context." do 115 | include FrameHelpers 116 | 117 | banner <<-BANNER 118 | Usage: up [OPTIONS] 119 | Go up to the caller's context. Accepts optional numeric parameter for how many frames to move up. 120 | Also accepts a string (regex) instead of numeric; for jumping to nearest parent method frame which matches the regex. 121 | e.g: up #=> Move up 1 stack frame. 122 | e.g: up 3 #=> Move up 2 stack frames. 123 | e.g: up meth #=> Jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`. 124 | BANNER 125 | 126 | def process 127 | inc = args.first.nil? ? "1" : args.first 128 | 129 | if !frame_manager 130 | raise Pry::CommandError, "Nowhere to go!" 131 | else 132 | if inc =~ /\d+/ 133 | frame_manager.change_frame_to frame_manager.binding_index + inc.to_i 134 | elsif inc =~ /^[^-].*$/ 135 | new_frame_index = find_frame_by_regex(Regexp.new(inc), :up) 136 | frame_manager.change_frame_to new_frame_index 137 | end 138 | end 139 | end 140 | end 141 | 142 | create_command "down", "Go down to the callee's context." do 143 | include FrameHelpers 144 | 145 | banner <<-BANNER 146 | Usage: down [OPTIONS] 147 | Go down to the callee's context. Accepts optional numeric parameter for how many frames to move down. 148 | Also accepts a string (regex) instead of numeric; for jumping to nearest child method frame which matches the regex. 149 | e.g: down #=> Move down 1 stack frame. 150 | e.g: down 3 #=> Move down 2 stack frames. 151 | e.g: down meth #=> ump to nearest child stack frame whose method matches /meth/ regex, i.e `my_method`. 152 | BANNER 153 | 154 | def process 155 | inc = args.first.nil? ? "1" : args.first 156 | 157 | if !frame_manager 158 | raise Pry::CommandError, "Nowhere to go!" 159 | else 160 | if inc =~ /\d+/ 161 | if frame_manager.binding_index - inc.to_i < 0 162 | raise Pry::CommandError, "At bottom of stack, cannot go further!" 163 | else 164 | frame_manager.change_frame_to frame_manager.binding_index - inc.to_i 165 | end 166 | elsif inc =~ /^[^-].*$/ 167 | new_frame_index = find_frame_by_regex(Regexp.new(inc), :down) 168 | frame_manager.change_frame_to new_frame_index 169 | end 170 | end 171 | end 172 | end 173 | 174 | create_command "frame", "Switch to a particular frame." do 175 | include FrameHelpers 176 | 177 | banner <<-BANNER 178 | Usage: frame [OPTIONS] 179 | Switch to a particular frame. Accepts numeric parameter (or regex for method name) for the target frame to switch to (use with show-stack). 180 | Negative frame numbers allowed. When given no parameter show information about the current frame. 181 | 182 | e.g: frame 4 #=> jump to the 4th frame 183 | e.g: frame meth #=> jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method` 184 | e.g: frame -2 #=> jump to the second-to-last frame 185 | e.g: frame #=> show information info about current frame 186 | BANNER 187 | 188 | def process 189 | if !frame_manager 190 | raise Pry::CommandError, "nowhere to go!" 191 | else 192 | 193 | if args[0] =~ /\d+/ 194 | frame_manager.change_frame_to args[0].to_i 195 | elsif args[0] =~ /^[^-].*$/ 196 | new_frame_index = find_frame_by_regex(Regexp.new(args[0]), :up) 197 | frame_manager.change_frame_to new_frame_index 198 | else 199 | output.puts "##{frame_manager.binding_index} #{frame_info(target, true)}" 200 | end 201 | end 202 | end 203 | end 204 | 205 | create_command "show-stack", "Show all frames" do 206 | include FrameHelpers 207 | 208 | banner <<-BANNER 209 | Usage: show-stack [OPTIONS] 210 | Show all accessible stack frames. 211 | e.g: show-stack -v 212 | BANNER 213 | 214 | def options(opt) 215 | opt.on :v, :verbose, "Include extra information." 216 | opt.on :H, :head, "Display the first N stack frames (defaults to 10).", :optional => true, :as => Integer, :default => 10 217 | opt.on :T, :tail, "Display the last N stack frames (defaults to 10).", :optional => true, :as => Integer, :default => 10 218 | opt.on :c, :current, "Display N frames either side of current frame (default to 5).", :optional => true, :as => Integer, :default => 5 219 | end 220 | 221 | def memoized_info(index, b, verbose) 222 | frame_manager.user[:frame_info] ||= Hash.new { |h, k| h[k] = [] } 223 | 224 | if verbose 225 | frame_manager.user[:frame_info][:v][index] ||= frame_info(b, verbose) 226 | else 227 | frame_manager.user[:frame_info][:normal][index] ||= frame_info(b, verbose) 228 | end 229 | end 230 | 231 | private :memoized_info 232 | 233 | # @return [Array>] Return tuple of 234 | # base_frame_index and the array of frames. 235 | def selected_stack_frames 236 | if opts.present?(:head) 237 | [0, frame_manager.bindings[0..(opts[:head] - 1)]] 238 | 239 | elsif opts.present?(:tail) 240 | tail = opts[:tail] 241 | if tail > frame_manager.bindings.size 242 | tail = frame_manager.bindings.size 243 | end 244 | 245 | base_frame_index = frame_manager.bindings.size - tail 246 | [base_frame_index, frame_manager.bindings[base_frame_index..-1]] 247 | 248 | elsif opts.present?(:current) 249 | first_frame_index = frame_manager.binding_index - (opts[:current]) 250 | first_frame_index = 0 if first_frame_index < 0 251 | last_frame_index = frame_manager.binding_index + (opts[:current]) 252 | [first_frame_index, frame_manager.bindings[first_frame_index..last_frame_index]] 253 | 254 | else 255 | [0, frame_manager.bindings] 256 | end 257 | end 258 | 259 | private :selected_stack_frames 260 | 261 | def process 262 | if !frame_manager 263 | output.puts "No caller stack available!" 264 | else 265 | content = "" 266 | content << "\n#{text.bold("Showing all accessible frames in stack (#{frame_manager.bindings.size} in total):")}\n--\n" 267 | 268 | base_frame_index, frames = selected_stack_frames 269 | frames.each_with_index do |b, index| 270 | i = index + base_frame_index 271 | if i == frame_manager.binding_index 272 | content << "=> ##{i} #{memoized_info(i, b, opts[:v])}\n" 273 | else 274 | content << " ##{i} #{memoized_info(i, b, opts[:v])}\n" 275 | end 276 | end 277 | 278 | stagger_output content 279 | end 280 | 281 | end 282 | end 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /lib/pry-stack_explorer/frame_manager.rb: -------------------------------------------------------------------------------- 1 | module PryStackExplorer 2 | 3 | # This class represents a call-stack. It stores the 4 | # frames that make up the stack and is responsible for updating the 5 | # associated Pry instance to reflect the active frame. It is fully Enumerable. 6 | class FrameManager 7 | include Enumerable 8 | 9 | # @return [Array] The array of bindings that constitute 10 | # the call-stack. 11 | attr_accessor :bindings 12 | 13 | # @return [Fixnum] The index of the active frame (binding) in the call-stack. 14 | attr_accessor :binding_index 15 | 16 | # @return [Hash] A hash for user defined data 17 | attr_reader :user 18 | 19 | # @return [Binding] The binding of the Pry instance before the 20 | # FrameManager took over. 21 | attr_reader :prior_binding 22 | 23 | # @return [Array] The backtrace of the Pry instance before the 24 | # FrameManager took over. 25 | attr_reader :prior_backtrace 26 | 27 | def initialize(bindings, _pry_) 28 | self.bindings = bindings 29 | self.binding_index = 0 30 | @pry = _pry_ 31 | @user = {} 32 | @prior_binding = _pry_.binding_stack.last 33 | @prior_backtrace = _pry_.backtrace 34 | end 35 | 36 | # Iterate over all frames 37 | def each(&block) 38 | bindings.each(&block) 39 | end 40 | 41 | # Ensure the Pry instance's active binding is the frame manager's 42 | # active binding. 43 | def refresh_frame(run_whereami=true) 44 | change_frame_to binding_index, run_whereami 45 | end 46 | 47 | # @return [Binding] The currently active frame 48 | def current_frame 49 | bindings[binding_index] 50 | end 51 | 52 | # Set the binding index (aka frame index), but raising an Exception when invalid 53 | # index received. Also converts negative indices to their positive counterparts. 54 | # @param [Fixnum] index The index. 55 | def set_binding_index_safely(index) 56 | if index > bindings.size - 1 57 | raise Pry::CommandError, "At top of stack, cannot go further!" 58 | elsif index < -bindings.size 59 | raise Pry::CommandError, "At bottom of stack, cannot go further!" 60 | else 61 | # wrap around negative indices 62 | index = (bindings.size - 1) + index + 1 if index < 0 63 | 64 | self.binding_index = index 65 | end 66 | end 67 | 68 | # Change active frame to the one indexed by `index`. 69 | # Note that indexing base is `0` 70 | # @param [Fixnum] index The index of the frame. 71 | def change_frame_to(index, run_whereami=true) 72 | 73 | set_binding_index_safely(index) 74 | 75 | if @pry.binding_stack.empty? 76 | @pry.binding_stack.replace [bindings[binding_index]] 77 | else 78 | @pry.binding_stack[-1] = bindings[binding_index] 79 | end 80 | 81 | @pry.run_command "whereami" if run_whereami 82 | end 83 | 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/pry-stack_explorer/version.rb: -------------------------------------------------------------------------------- 1 | module PryStackExplorer 2 | VERSION = "0.4.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/pry-stack_explorer/when_started_hook.rb: -------------------------------------------------------------------------------- 1 | module PryStackExplorer 2 | class WhenStartedHook 3 | 4 | def caller_bindings(target) 5 | bindings = binding.callers 6 | 7 | start_frames = bindings.each_with_index.select do |b, i| 8 | b.frame_type == :method && 9 | b.eval("self") == Pry && 10 | b.eval("__method__") == :start 11 | end 12 | 13 | start_frame_index = start_frames.first.last 14 | 15 | if start_frames.size >= 2 16 | idx1, idx2 = start_frames.take(2).map(&:last) 17 | 18 | is_nested_session = bindings[idx1..idx2].detect do |b| 19 | b.eval("__method__") == :re && 20 | b.eval("self.class") == Pry 21 | end 22 | 23 | start_frame_index = idx2 if !is_nested_session 24 | end 25 | 26 | bindings = bindings.drop(start_frame_index + 1) 27 | 28 | bindings = bindings.drop(1) if bindings.first.eval("__method__") == :pry 29 | bindings = bindings.drop_while { |b| b.eval("self.inspect") =~ /PryNav/ } 30 | 31 | # Use the binding returned by #of_caller if possible (as we get 32 | # access to frame_type). 33 | # Otherwise stick to the given binding (target). 34 | if !PryStackExplorer.bindings_equal?(target, bindings.first) 35 | bindings.shift 36 | bindings.unshift(target) 37 | end 38 | 39 | bindings 40 | end 41 | 42 | def call(target, options, _pry_) 43 | options = { 44 | :call_stack => true, 45 | :initial_frame => 0 46 | }.merge!(options) 47 | 48 | return if !options[:call_stack] 49 | 50 | if options[:call_stack].is_a?(Array) 51 | bindings = options[:call_stack] 52 | 53 | if bindings.empty? || !bindings.all? { |v| v.is_a?(Binding) } 54 | raise ArgumentError, ":call_stack must be an array of bindings" 55 | end 56 | else 57 | bindings = caller_bindings(target) 58 | end 59 | 60 | PryStackExplorer.create_and_push_frame_manager bindings, _pry_, :initial_frame => options[:initial_frame] 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /pry-stack_explorer.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "pry-stack_explorer" 5 | s.version = "0.4.0" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["John Mair (banisterfiend)"] 9 | s.date = "2012-02-17" 10 | s.description = "Walk the stack in a Pry session" 11 | s.email = "jrmair@gmail.com" 12 | s.files = [".gemtest", ".gitignore", ".travis.yml", ".yardopts", "CHANGELOG", "Gemfile", "LICENSE", "README.md", "Rakefile", "examples/example.rb", "examples/example2.rb", "lib/pry-stack_explorer.rb", "lib/pry-stack_explorer/commands.rb", "lib/pry-stack_explorer/frame_manager.rb", "lib/pry-stack_explorer/version.rb", "lib/pry-stack_explorer/when_started_hook.rb", "pry-stack_explorer.gemspec", "test/helper.rb", "test/test_commands.rb", "test/test_frame_manager.rb", "test/test_stack_explorer.rb", "tester.rb"] 13 | s.homepage = "https://github.com/banister" 14 | s.require_paths = ["lib"] 15 | s.required_ruby_version = Gem::Requirement.new(">= 1.9.2") 16 | s.rubygems_version = "1.8.11" 17 | s.summary = "Walk the stack in a Pry session" 18 | s.test_files = ["test/helper.rb", "test/test_commands.rb", "test/test_frame_manager.rb", "test/test_stack_explorer.rb"] 19 | 20 | if s.respond_to? :specification_version then 21 | s.specification_version = 3 22 | 23 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 24 | s.add_runtime_dependency(%q, ["~> 0.6.2"]) 25 | s.add_runtime_dependency(%q, ["~> 0.9.8.2"]) 26 | s.add_development_dependency(%q, ["~> 1.1.0"]) 27 | s.add_development_dependency(%q, ["~> 0.9"]) 28 | else 29 | s.add_dependency(%q, ["~> 0.6.2"]) 30 | s.add_dependency(%q, ["~> 0.9.8.2"]) 31 | s.add_dependency(%q, ["~> 1.1.0"]) 32 | s.add_dependency(%q, ["~> 0.9"]) 33 | end 34 | else 35 | s.add_dependency(%q, ["~> 0.6.2"]) 36 | s.add_dependency(%q, ["~> 0.9.8.2"]) 37 | s.add_dependency(%q, ["~> 1.1.0"]) 38 | s.add_dependency(%q, ["~> 0.9"]) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'ostruct' 3 | 4 | unless Object.const_defined? 'PryStackExplorer' 5 | $:.unshift File.expand_path '../../lib', __FILE__ 6 | require 'pry-stack_explorer' 7 | end 8 | 9 | require 'bacon' 10 | 11 | puts "Testing pry-stack_explorer version #{PryStackExplorer::VERSION}..." 12 | puts "Ruby version: #{RUBY_VERSION}" 13 | 14 | PE = PryStackExplorer 15 | 16 | class << Pry 17 | alias_method :orig_reset_defaults, :reset_defaults 18 | def reset_defaults 19 | orig_reset_defaults 20 | 21 | Pry.color = false 22 | Pry.pager = false 23 | Pry.config.should_load_rc = false 24 | Pry.config.should_load_plugins = false 25 | Pry.config.history.should_load = false 26 | Pry.config.history.should_save = false 27 | Pry.config.auto_indent = false 28 | Pry.config.hooks = Pry::Hooks.new 29 | Pry.config.collision_warning = false 30 | end 31 | end 32 | 33 | AfterSessionHook = Pry.config.hooks.get_hook(:after_session, :delete_frame_manager) 34 | WhenStartedHook = Pry.config.hooks.get_hook(:when_started, :save_caller_bindings) 35 | 36 | Pry.reset_defaults 37 | 38 | class InputTester 39 | def initialize(*actions) 40 | if actions.last.is_a?(Hash) && actions.last.keys == [:history] 41 | @hist = actions.pop[:history] 42 | end 43 | @orig_actions = actions.dup 44 | @actions = actions 45 | end 46 | 47 | def readline(*) 48 | @actions.shift.tap{ |line| @hist << line if @hist } 49 | end 50 | 51 | def rewind 52 | @actions = @orig_actions.dup 53 | end 54 | end 55 | 56 | # Set I/O streams. 57 | # 58 | # Out defaults to an anonymous StringIO. 59 | # 60 | def redirect_pry_io(new_in, new_out = StringIO.new) 61 | old_in = Pry.input 62 | old_out = Pry.output 63 | 64 | Pry.input = new_in 65 | Pry.output = new_out 66 | begin 67 | yield 68 | ensure 69 | Pry.input = old_in 70 | Pry.output = old_out 71 | end 72 | end 73 | 74 | def mock_pry(*args) 75 | 76 | binding = args.first.is_a?(Binding) ? args.shift : binding() 77 | 78 | input = InputTester.new(*args) 79 | output = StringIO.new 80 | 81 | redirect_pry_io(input, output) do 82 | binding.pry 83 | end 84 | 85 | output.string 86 | end 87 | -------------------------------------------------------------------------------- /test/test_commands.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | describe PryStackExplorer::Commands do 4 | 5 | before do 6 | Pry.config.hooks.add_hook(:when_started, :save_caller_bindings, WhenStartedHook) 7 | Pry.config.hooks.add_hook(:after_session, :delete_frame_manager, AfterSessionHook) 8 | 9 | @o = Object.new 10 | class << @o; attr_accessor :first_method, :second_method, :third_method; end 11 | def @o.bing() bong end 12 | def @o.bong() bang end 13 | def @o.bang() Pry.start(binding) end 14 | end 15 | 16 | after do 17 | Pry.config.hooks.delete_hook(:when_started, :save_caller_bindings) 18 | Pry.config.hooks.delete_hook(:after_session, :delete_frame_manager) 19 | end 20 | 21 | describe "up" do 22 | it 'should move up the call stack one frame at a time' do 23 | redirect_pry_io(InputTester.new("@first_method = __method__", 24 | "up", 25 | "@second_method = __method__", 26 | "up", 27 | "@third_method = __method__", 28 | "exit-all"), out=StringIO.new) do 29 | @o.bing 30 | end 31 | 32 | @o.first_method.should == :bang 33 | @o.second_method.should == :bong 34 | @o.third_method.should == :bing 35 | end 36 | 37 | it 'should move up the call stack two frames at a time' do 38 | redirect_pry_io(InputTester.new("@first_method = __method__", 39 | "up 2", 40 | "@second_method = __method__", 41 | "exit-all"), out=StringIO.new) do 42 | @o.bing 43 | end 44 | 45 | @o.first_method.should == :bang 46 | @o.second_method.should == :bing 47 | end 48 | 49 | describe "by method name regex" do 50 | it 'should move to the method name that matches the regex' do 51 | redirect_pry_io(InputTester.new("@first_method = __method__", 52 | "up bi", 53 | "@second_method = __method__", 54 | "exit-all"), out=StringIO.new) do 55 | @o.bing 56 | end 57 | 58 | @o.first_method.should == :bang 59 | @o.second_method.should == :bing 60 | end 61 | 62 | it 'should move through all methods that match regex in order' do 63 | redirect_pry_io(InputTester.new("@first_method = __method__", 64 | "up b", 65 | "@second_method = __method__", 66 | "up b", 67 | "@third_method = __method__", 68 | "exit-all"), out=StringIO.new) do 69 | @o.bing 70 | end 71 | 72 | @o.first_method.should == :bang 73 | @o.second_method.should == :bong 74 | @o.third_method.should == :bing 75 | end 76 | 77 | it 'should error if it cant find frame to match regex' do 78 | redirect_pry_io(InputTester.new("up conrad_irwin", 79 | "exit-all"), out=StringIO.new) do 80 | @o.bing 81 | end 82 | 83 | out.string.should =~ /Error: No frame that matches/ 84 | end 85 | end 86 | end 87 | 88 | describe "down" do 89 | it 'should move down the call stack one frame at a time' do 90 | def @o.bang() Pry.start(binding, :initial_frame => 1) end 91 | 92 | redirect_pry_io(InputTester.new("@first_method = __method__", 93 | "down", 94 | "@second_method = __method__", 95 | "exit-all"), out=StringIO.new) do 96 | @o.bing 97 | end 98 | 99 | @o.first_method.should == :bong 100 | @o.second_method.should == :bang 101 | end 102 | 103 | it 'should move down the call stack two frames at a time' do 104 | def @o.bang() Pry.start(binding, :initial_frame => 2) end 105 | 106 | redirect_pry_io(InputTester.new("@first_method = __method__", 107 | "down 2", 108 | "@second_method = __method__", 109 | "exit-all"), out=StringIO.new) do 110 | @o.bing 111 | end 112 | 113 | @o.first_method.should == :bing 114 | @o.second_method.should == :bang 115 | end 116 | 117 | describe "by method name regex" do 118 | it 'should move to the method name that matches the regex' do 119 | redirect_pry_io(InputTester.new("frame -1", 120 | "down bo", 121 | "@first_method = __method__", 122 | "exit-all"), out=StringIO.new) do 123 | @o.bing 124 | end 125 | 126 | @o.first_method.should == :bong 127 | end 128 | 129 | it 'should move through all methods that match regex in order' do 130 | redirect_pry_io(InputTester.new("frame bing", 131 | "@first_method = __method__", 132 | "down b", 133 | "@second_method = __method__", 134 | "down b", 135 | "@third_method = __method__", 136 | "exit-all"), out=StringIO.new) do 137 | @o.bing 138 | end 139 | 140 | @o.first_method.should == :bing 141 | @o.second_method.should == :bong 142 | @o.third_method.should == :bang 143 | end 144 | 145 | it 'should error if it cant find frame to match regex' do 146 | redirect_pry_io(InputTester.new("frame -1", 147 | "down conrad_irwin", 148 | "exit-all"), out=StringIO.new) do 149 | @o.bing 150 | end 151 | 152 | out.string.should =~ /Error: No frame that matches/ 153 | end 154 | end 155 | 156 | end 157 | 158 | describe "frame" do 159 | describe "by method name regex" do 160 | it 'should jump to correct stack frame when given method name' do 161 | redirect_pry_io(InputTester.new("frame bi", 162 | "@first_method = __method__", 163 | "exit-all"), out=StringIO.new) do 164 | @o.bing 165 | end 166 | 167 | @o.first_method.should == :bing 168 | end 169 | 170 | it 'should NOT jump to frames lower down stack when given method name' do 171 | redirect_pry_io(InputTester.new("frame -1", 172 | "frame bang", 173 | "exit-all"), out=StringIO.new) do 174 | @o.bing 175 | end 176 | 177 | out.string.should =~ /Error: No frame that matches/ 178 | end 179 | 180 | end 181 | 182 | it 'should move to the given frame in the call stack' do 183 | redirect_pry_io(InputTester.new("frame 2", 184 | "@first_method = __method__", 185 | "exit-all"), out=StringIO.new) do 186 | @o.bing 187 | end 188 | 189 | @o.first_method.should == :bing 190 | end 191 | 192 | it 'should return info on current frame when given no parameters' do 193 | redirect_pry_io(InputTester.new("frame", 194 | "exit-all"), out=StringIO.new) do 195 | @o.bing 196 | end 197 | 198 | out.string.should =~ /\#0.*?bang/ 199 | out.string.should.not =~ /\#1/ 200 | end 201 | 202 | describe "negative indices" do 203 | it 'should work with negative frame numbers' do 204 | o = Object.new 205 | class << o; attr_accessor :frame; end 206 | def o.alpha() binding end 207 | def o.beta() binding end 208 | def o.gamma() binding end 209 | 210 | call_stack = [o.alpha, o.beta, o.gamma] 211 | method_names = call_stack.map { |v| v.eval('__method__') }.reverse 212 | (1..3).each_with_index do |v, idx| 213 | redirect_pry_io(InputTester.new("frame -#{v}", 214 | "@frame = __method__", 215 | "exit-all"), out=StringIO.new) do 216 | Pry.start(o, :call_stack => call_stack) 217 | end 218 | o.frame.should == method_names[idx] 219 | end 220 | end 221 | 222 | it 'should convert negative indices to their positive counterparts' do 223 | o = Object.new 224 | class << o; attr_accessor :frame_number; end 225 | def o.alpha() binding end 226 | def o.beta() binding end 227 | def o.gamma() binding end 228 | 229 | call_stack = [o.alpha, o.beta, o.gamma] 230 | (1..3).each_with_index do |v, idx| 231 | redirect_pry_io(InputTester.new("frame -#{v}", 232 | "@frame_number = PryStackExplorer.frame_manager(_pry_).binding_index", 233 | "exit-all"), out=StringIO.new) do 234 | Pry.start(o, :call_stack => call_stack) 235 | end 236 | o.frame_number.should == call_stack.size - v 237 | end 238 | end 239 | end 240 | end 241 | end 242 | -------------------------------------------------------------------------------- /test/test_frame_manager.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | Pry.config.output = StringIO.new 4 | 5 | describe PryStackExplorer::FrameManager do 6 | 7 | before do 8 | @pry_instance = Pry.new 9 | @bindings = [binding, binding, binding, binding] 10 | @bindings.each_with_index { |v, i| v.eval("x = #{i}") } 11 | @pry_instance.binding_stack.push @bindings.last 12 | @frame_manager = PE::FrameManager.new(@bindings, @pry_instance) 13 | end 14 | 15 | describe "creation" do 16 | it "should make bindings accessible via 'bindings' method" do 17 | @frame_manager.bindings.should == @bindings 18 | end 19 | 20 | it "should set binding_index to 0" do 21 | @frame_manager.binding_index.should == 0 22 | end 23 | 24 | it "should set current_frame to first frame" do 25 | @frame_manager.current_frame.should == @bindings.first 26 | end 27 | end 28 | 29 | describe "FrameManager#change_frame_to" do 30 | it 'should change the frame to the given one' do 31 | @frame_manager.change_frame_to(1) 32 | 33 | @frame_manager.binding_index.should == 1 34 | @frame_manager.current_frame.should == @bindings[1] 35 | @pry_instance.binding_stack.last.should == @frame_manager.current_frame 36 | end 37 | 38 | it 'should accept negative indices when specifying frame' do 39 | @frame_manager.change_frame_to(-1) 40 | 41 | # negative index is converted to a positive one inside change_frame_to 42 | @frame_manager.binding_index.should == @bindings.size - 1 43 | 44 | @frame_manager.current_frame.should == @bindings[-1] 45 | @pry_instance.binding_stack.last.should == @frame_manager.current_frame 46 | end 47 | end 48 | 49 | describe "FrameManager#refresh_frame" do 50 | it 'should change the Pry frame to the active one in the FrameManager' do 51 | @frame_manager.binding_index = 2 52 | @frame_manager.refresh_frame 53 | 54 | @pry_instance.binding_stack.last.should == @frame_manager.current_frame 55 | end 56 | end 57 | 58 | describe "FrameManager is Enumerable" do 59 | it 'should perform an Enumerable#map on the frames' do 60 | @frame_manager.map { |v| v.eval("x") }.should == (0..(@bindings.size - 1)).to_a 61 | end 62 | end 63 | 64 | end 65 | 66 | -------------------------------------------------------------------------------- /test/test_stack_explorer.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | describe PryStackExplorer do 4 | 5 | describe "Pry.start" do 6 | before do 7 | Pry.config.hooks.add_hook(:when_started, :save_caller_bindings, WhenStartedHook) 8 | Pry.config.hooks.add_hook(:after_session, :delete_frame_manager, AfterSessionHook) 9 | 10 | @o = Object.new 11 | class << @o; attr_reader :frame; end 12 | def @o.bing() bong end 13 | def @o.bong() bang end 14 | def @o.bang() Pry.start(binding) end 15 | end 16 | 17 | after do 18 | Pry.config.hooks.delete_hook(:when_started, :save_caller_bindings) 19 | Pry.config.hooks.delete_hook(:after_session, :delete_frame_manager) 20 | end 21 | 22 | describe ":initial_frame option" do 23 | it 'should default to first frame when no option provided' do 24 | redirect_pry_io(StringIO.new("@frame = __method__\nexit\n"), out=StringIO.new) do 25 | @o.bing 26 | end 27 | 28 | @o.frame.should == :bang 29 | end 30 | 31 | it 'should begin at correct frame even if Pry.start is monkey-patched (only works with one monkey-patch currently)' do 32 | class << Pry 33 | alias_method :old_start, :start 34 | 35 | def start(*args, &block) 36 | old_start(*args, &block) 37 | end 38 | end 39 | 40 | o = Object.new 41 | class << o; attr_reader :frames; end 42 | def o.bing() bong end 43 | def o.bong() bang end 44 | def o.bang() Pry.start(binding) end 45 | 46 | redirect_pry_io(InputTester.new( 47 | "@frames = SE.frame_manager(_pry_).bindings.take(3)", 48 | "exit-all")) do 49 | o.bing 50 | end 51 | 52 | o.frames.map { |f| f.eval("__method__") }.should == [:bang, :bong, :bing] 53 | 54 | class << Pry 55 | alias_method :start, :old_start 56 | end 57 | end 58 | 59 | it 'should begin session at specified frame' do 60 | o = Object.new 61 | class << o; attr_reader :frame; end 62 | def o.bing() bong end 63 | def o.bong() bang end 64 | def o.bang() Pry.start(binding, :initial_frame => 1) end #* 65 | 66 | redirect_pry_io(StringIO.new("@frame = __method__\nexit-all\n"), out=StringIO.new) do 67 | o.bing 68 | end 69 | 70 | o.frame.should == :bong 71 | end 72 | 73 | it 'should begin session at specified frame when using :call_stack' do 74 | o = Object.new 75 | class << o; attr_accessor :frame; end 76 | def o.alpha() binding end 77 | def o.beta() binding end 78 | def o.gamma() binding end 79 | 80 | redirect_pry_io(StringIO.new("@frame = __method__\nexit\n"), out=StringIO.new) do 81 | Pry.start(binding, :call_stack => [o.gamma, o.beta, o.alpha], :initial_frame => 1) 82 | end 83 | 84 | o.frame.should == :beta 85 | end 86 | 87 | end 88 | 89 | describe ":call_stack option" do 90 | it 'should invoke a session with the call stack set' do 91 | redirect_pry_io(StringIO.new("show-stack\nexit\n"), out=StringIO.new) do 92 | @o.bing 93 | end 94 | 95 | out.string.should =~ /bang.*?bong.*?bing/m 96 | end 97 | 98 | it 'should set no call stack when :call_stack => false' do 99 | o = Object.new 100 | def o.bing() bong end 101 | def o.bong() bang end 102 | def o.bang() Pry.start(binding, :call_stack => false) end 103 | 104 | redirect_pry_io(StringIO.new("show-stack\nexit\n"), out=StringIO.new) do 105 | o.bing 106 | end 107 | 108 | out.string.should =~ /No caller stack/ 109 | end 110 | 111 | it 'should set custom call stack when :call_stack => [b1, b2]' do 112 | o = Object.new 113 | def o.alpha() binding end 114 | def o.beta() binding end 115 | def o.gamma() binding end 116 | 117 | redirect_pry_io(StringIO.new("show-stack\nexit\n"), out=StringIO.new) do 118 | Pry.start(binding, :call_stack => [o.beta, o.gamma, o.alpha]) 119 | end 120 | 121 | out.string.should =~ /beta.*?gamma.*?alpha/m 122 | end 123 | 124 | it 'should raise if custom call stack does not contain bindings' do 125 | o = OpenStruct.new 126 | redirect_pry_io(StringIO.new("self.errors = _pry_.hooks.errors\nexit\n")) do 127 | Pry.start(o, :call_stack => [1, 2, 3]) 128 | end 129 | o.errors.first.is_a?(ArgumentError).should == true 130 | end 131 | 132 | it 'should raise if custom call stack is empty' do 133 | o = OpenStruct.new 134 | redirect_pry_io(StringIO.new("self.errors = _pry_.hooks.errors\nexit\n")) do 135 | Pry.start o, :call_stack => [] 136 | end 137 | o.errors.first.is_a?(ArgumentError).should == true 138 | end 139 | end 140 | end 141 | 142 | describe "unit tests for PryStackExplorer class methods" do 143 | before do 144 | @pry_instance = Pry.new 145 | @bindings = [binding, binding] 146 | end 147 | 148 | after do 149 | PE.clear_frame_managers(@pry_instance) 150 | end 151 | 152 | describe "PryStackExplorer.create_and_push_frame_manager" do 153 | 154 | it "should create and push one new FrameManager" do 155 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 156 | PE.frame_manager(@pry_instance).is_a?(PE::FrameManager).should == true 157 | PE.frame_managers(@pry_instance).count.should == 1 158 | end 159 | 160 | it "should refresh Pry instance to use FrameManager's active binding" do 161 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 162 | @pry_instance.binding_stack.size.should == 1 163 | @pry_instance.binding_stack.first.should == @bindings.first 164 | end 165 | 166 | it 'should save prior binding in FrameManager instance' do 167 | _pry_ = Pry.new 168 | _pry_.binding_stack.push(b=binding) 169 | PryStackExplorer.create_and_push_frame_manager(@bindings, _pry_) 170 | PryStackExplorer.frame_manager(_pry_).prior_binding.should == b 171 | end 172 | 173 | describe ":initial_frame option" do 174 | it 'should start on specified frame' do 175 | PE.create_and_push_frame_manager(@bindings, @pry_instance, :initial_frame => 1) 176 | @pry_instance.binding_stack.size.should == 1 177 | @pry_instance.binding_stack.first.should == @bindings.last 178 | end 179 | 180 | describe "negative numbers" do 181 | it 'should work with negative frame number (-1)' do 182 | PE.create_and_push_frame_manager(@bindings, @pry_instance, :initial_frame => -1) 183 | @pry_instance.binding_stack.size.should == 1 184 | @pry_instance.binding_stack.first.should == @bindings.last 185 | end 186 | 187 | it 'should work with negative frame number (-2)' do 188 | PE.create_and_push_frame_manager(@bindings, @pry_instance, :initial_frame => -2) 189 | @pry_instance.binding_stack.size.should == 1 190 | @pry_instance.binding_stack.first.should == @bindings.first 191 | end 192 | end 193 | end 194 | 195 | it 'should save prior backtrace in FrameManager instance' do 196 | _pry_ = Pry.new 197 | _pry_.backtrace = ["my backtrace"] 198 | PryStackExplorer.create_and_push_frame_manager(@bindings, _pry_) 199 | PryStackExplorer.frame_manager(_pry_).prior_backtrace.should == _pry_.backtrace 200 | end 201 | 202 | it "should create and push multiple FrameManagers" do 203 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 204 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 205 | PE.frame_managers(@pry_instance).count.should == 2 206 | end 207 | 208 | it 'should push FrameManagers to stacks based on Pry instance' do 209 | p2 = Pry.new 210 | bindings = [binding, binding] 211 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 212 | PE.create_and_push_frame_manager(bindings, p2) 213 | PE.frame_managers(@pry_instance).count.should == 1 214 | PE.frame_managers(p2).count.should == 1 215 | end 216 | end 217 | 218 | describe "PryStackExplorer.frame_manager" do 219 | it "should have the correct bindings" do 220 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 221 | PE.frame_manager(@pry_instance).bindings.should == @bindings 222 | end 223 | 224 | it "should return the last pushed FrameManager" do 225 | bindings = [binding, binding] 226 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 227 | PE.create_and_push_frame_manager(bindings, @pry_instance) 228 | PE.frame_manager(@pry_instance).bindings.should == bindings 229 | end 230 | 231 | it "should return the correct FrameManager for the given Pry instance" do 232 | bindings = [binding, binding] 233 | p2 = Pry.new 234 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 235 | PE.create_and_push_frame_manager(bindings, p2) 236 | PE.frame_manager(@pry_instance).bindings.should == @bindings 237 | PE.frame_manager(p2).bindings.should == bindings 238 | end 239 | end 240 | 241 | describe "PryStackExplorer.pop_frame_manager" do 242 | it "should remove FrameManager from stack" do 243 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 244 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 245 | PE.pop_frame_manager(@pry_instance) 246 | PE.frame_managers(@pry_instance).count.should == 1 247 | end 248 | 249 | it "should return the most recently added FrameManager" do 250 | bindings = [binding, binding] 251 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 252 | PE.create_and_push_frame_manager(bindings, @pry_instance) 253 | PE.pop_frame_manager(@pry_instance).bindings.should == bindings 254 | end 255 | 256 | it "should remove FrameManager from the appropriate stack based on Pry instance" do 257 | p2 = Pry.new 258 | bindings = [binding, binding] 259 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 260 | PE.create_and_push_frame_manager(bindings, p2) 261 | PE.pop_frame_manager(@pry_instance) 262 | PE.frame_managers(@pry_instance).count.should == 0 263 | PE.frame_managers(p2).count.should == 1 264 | end 265 | 266 | it "should remove key when no frames remaining for Pry instance" do 267 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 268 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 269 | PE.pop_frame_manager(@pry_instance) 270 | PE.pop_frame_manager(@pry_instance) 271 | PE.frame_hash.has_key?(@pry_instance).should == false 272 | end 273 | 274 | it 'should not change size of binding_stack when popping' do 275 | bindings = [bindings, bindings] 276 | PE.create_and_push_frame_manager(bindings, @pry_instance) 277 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 278 | PE.pop_frame_manager(@pry_instance) 279 | @pry_instance.binding_stack.size.should == 1 280 | end 281 | 282 | it 'should return nil when popping non-existent frame manager' do 283 | PE.pop_frame_manager(@pry_instance).should == nil 284 | end 285 | 286 | describe "restoring previous binding" do 287 | it 'should restore previous binding for Pry instance on pop, where previous binding is not first frame' do 288 | bindings = [binding, binding] 289 | PE.create_and_push_frame_manager(bindings, @pry_instance).binding_index = 1 290 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 291 | PE.pop_frame_manager(@pry_instance) 292 | @pry_instance.binding_stack.first.should == bindings[1] 293 | end 294 | 295 | it 'should restore previous binding for Pry instance on pop (previous frame frame manager)' do 296 | bindings = [binding, binding] 297 | PE.create_and_push_frame_manager(bindings, @pry_instance) 298 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 299 | PE.pop_frame_manager(@pry_instance) 300 | @pry_instance.binding_stack.first.should == bindings.first 301 | end 302 | 303 | it 'should restore previous binding for Pry instance on pop (no previous frame manager)' do 304 | b = binding 305 | @pry_instance.binding_stack = [b] 306 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 307 | PE.pop_frame_manager(@pry_instance) 308 | @pry_instance.binding_stack.first.should == b 309 | end 310 | 311 | it 'should restore previous binding for Pry instance on pop (no previous frame manager AND no empty binding_stack)' do 312 | b = binding 313 | @pry_instance.binding_stack = [b] 314 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 315 | @pry_instance.binding_stack.clear 316 | PE.pop_frame_manager(@pry_instance) 317 | @pry_instance.binding_stack.first.should == b 318 | end 319 | end 320 | 321 | describe "_pry_.backtrace" do 322 | it "should restore backtrace when frame is popped" do 323 | p1 = Pry.new 324 | bindings = [binding, binding] 325 | p1.backtrace = "my backtrace1" 326 | PE.create_and_push_frame_manager(bindings, p1) 327 | p1.backtrace = "my backtrace2" 328 | PE.create_and_push_frame_manager(bindings, p1) 329 | p1.backtrace = "my backtrace3" 330 | 331 | PE.pop_frame_manager(p1) 332 | p1.backtrace.should == "my backtrace2" 333 | PE.pop_frame_manager(p1) 334 | p1.backtrace.should == "my backtrace1" 335 | end 336 | end 337 | end 338 | 339 | describe "PryStackExplorer.clear_frame_managers" do 340 | it "should clear all FrameManagers for a Pry instance" do 341 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 342 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 343 | PE.clear_frame_managers(@pry_instance) 344 | PE.frame_hash.has_key?(@pry_instance).should == false 345 | end 346 | 347 | it "should clear all FrameManagers for a Pry instance but leave others untouched" do 348 | p2 = Pry.new 349 | bindings = [binding, binding] 350 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 351 | PE.create_and_push_frame_manager(bindings, p2) 352 | PE.clear_frame_managers(@pry_instance) 353 | PE.frame_managers(p2).count.should == 1 354 | PE.frame_hash.has_key?(@pry_instance).should == false 355 | end 356 | 357 | it "should remove key" do 358 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 359 | PE.create_and_push_frame_manager(@bindings, @pry_instance) 360 | PE.clear_frame_managers(@pry_instance) 361 | PE.frame_hash.has_key?(@pry_instance).should == false 362 | end 363 | 364 | describe "_pry_.backtrace" do 365 | it "should restore backtrace to initial one when frame managers are cleared" do 366 | p1 = Pry.new 367 | bindings = [binding, binding] 368 | p1.backtrace = "my backtrace1" 369 | PE.create_and_push_frame_manager(bindings, p1) 370 | p1.backtrace = "my backtrace2" 371 | PE.create_and_push_frame_manager(bindings, p1) 372 | p1.backtrace = "my backtrace3" 373 | 374 | PE.clear_frame_managers(p1) 375 | p1.backtrace.should == "my backtrace1" 376 | end 377 | end 378 | 379 | end 380 | end 381 | end 382 | -------------------------------------------------------------------------------- /tester.rb: -------------------------------------------------------------------------------- 1 | def a 2 | x = 20 3 | b 4 | end 5 | 6 | def b 7 | x = 30 8 | c 9 | end 10 | 11 | def c 12 | u = 50 13 | binding.pry 14 | puts "hi" 15 | puts "bye" 16 | v = 20 17 | puts v 18 | k 19 | end 20 | 21 | def k 22 | puts "lovely girl" 23 | james = "hello" 24 | puts james 25 | end 26 | 27 | a 28 | --------------------------------------------------------------------------------