class PryRescue

PryRescue provides the ability to open a Pry shell whenever an unhandled exception is raised in your code.

The main API is exposed via the Pry object, but here are a load of helpers that I didn't want to pollute the Pry namespace with.

@see {Pry::rescue}

Attributes

any_exception_captured[RW]

Public Class Methods

enter_exception_context(exception) click to toggle source

Start a Pry session in the context of the exception. @param [Exception] exception The exception raised

# File lib/pry-rescue.rb, line 46
def enter_exception_context(exception)
  @any_exception_captured = true
  @exception_context_depth ||= 0
  @exception_context_depth += 1

  exception = exception.instance_variable_get(:@rescue_cause) if phantom_load_raise?(exception)
  bindings = exception.instance_variable_get(:@rescue_bindings)

  bindings = without_bindings_below_raise(bindings)
  bindings = without_duplicates(bindings)

  with_program_name "#$PROGRAM_NAME [in pry-rescue @ #{Dir.pwd}]" do
    if defined?(PryStackExplorer)
      pry :call_stack => bindings,
          :hooks => pry_hooks(exception),
          :initial_frame => initial_frame(bindings)
    else
      Pry.start bindings.first, :hooks => pry_hooks(exception)
    end
  end
ensure
  @exception_context_depth -= 1
end
exit_callbacks() click to toggle source
# File lib/pry-rescue/kernel_exit_hooks.rb, line 2
def exit_callbacks
  @exit_callbacks ||= []
end
in_exception_context?() click to toggle source

Is the user currently inside pry rescue? @return [Boolean]

# File lib/pry-rescue.rb, line 93
def in_exception_context?
  @exception_context_depth && @exception_context_depth > 0
end
load(script, ensure_repl = false) click to toggle source

Load a script wrapped in Pry::rescue{ } @param [String] script The name of the script

# File lib/pry-rescue.rb, line 72
def load(script, ensure_repl = false)
  require File.expand_path('../pry-rescue/kernel_exit_hooks.rb', __FILE__) if ensure_repl
  Pry::rescue do
    begin
      TOPLEVEL_BINDING.eval File.read(script), script, 1
    rescue SyntaxError => exception
      puts "#{exception}\n"
    end
  end
end
load_rake(task) click to toggle source
# File lib/pry-rescue.rb, line 83
def load_rake(task)
  require 'rake'
  Pry::rescue do
    load "#{Dir.pwd}/Rakefile"
    Rake::Task[task].invoke
  end
end
peek!(*) click to toggle source

Called when rescue –peek is used and the user hits <Ctrl+C> or sends whichever signal is configured.

# File lib/pry-rescue/peek.rb, line 8
def self.peek!(*)
  puts 'Preparing to peek via pry!' unless ENV['NO_PEEK_STARTUP_MESSAGE']
  require 'pry'
  unless binding.respond_to?(:of_caller)
    raise "pry-stack_explorer is not installed"
  end
  throw :raise_up, Interrupt if Pry === binding.of_caller(1).eval('self')
  binding.of_caller(1).pry
  # TODO pry :call_stack => binding.of_callers, :initial_frame => 1
end
peek_on_signal(signal) click to toggle source
# File lib/pry-rescue/peek.rb, line 2
def self.peek_on_signal signal
  trap signal, &method(:peek!)
end
run_exit_callbacks() click to toggle source
# File lib/pry-rescue/kernel_exit_hooks.rb, line 6
def run_exit_callbacks
  Pry::rescue do
    exit_callbacks.dup.each(&:call)
  end
  unless any_exception_captured
    print "\n"
    TOPLEVEL_BINDING.pry
  end
end

Private Class Methods

current_path?(file) click to toggle source

Is this file definitely part of the codebase the user is working on?

This function exists because sometimes Dir.pwd can be a gem_path?, and the user expects to be able to debug a gem when they're cd'd into it.

@param [String] file the absolute path @return [Boolean]

# File lib/pry-rescue.rb, line 141
def current_path?(file)
  file.start_with?(Dir.pwd) && !file.match(%r(/vendor/))
end
gem_path?(file) click to toggle source

Is this path included in a gem?

@param [String] file the absolute path @return [Boolean]

# File lib/pry-rescue.rb, line 149
def gem_path?(file)
  # rubygems 1.8
  if Gem::Specification.respond_to?(:any?)
    Gem::Specification.any?{ |gem| file.start_with?(gem.full_gem_path) }
  # rubygems 1.6
  else
    Gem.all_load_paths.any?{ |path| file.start_with?(path) }
  end
end
initial_frame(bindings) click to toggle source

When using pry-stack-explorer we want to start the rescue session outside of gems and the standard library, as that is most helpful for users.

@param [Array<Bindings>] bindings All bindings @return [Fixnum] The offset of the first binding of user code

# File lib/pry-rescue.rb, line 115
def initial_frame(bindings)
  bindings.each_with_index do |binding, i|
    return i if user_path?(SourceLocation.call(binding)[0])
  end

  0
end
phantom_load_raise?(e) click to toggle source

Did this raise happen within pry-rescue?

This is designed to remove the extra raise that is caused by PryRescue.load. TODO: we should figure out why it happens…

@param [Exception] e The raised exception

# File lib/pry-rescue.rb, line 105
def phantom_load_raise?(e)
  bindings = e.instance_variable_get(:@rescue_bindings)
  bindings.any? && SourceLocation.call(bindings.first)[0] == __FILE__
end
pry_hooks(ex) click to toggle source

Define the :before_session hook for the Pry instance. This ensures that the `ex` and `raised` sticky locals are properly set.

@param [Exception] ex The exception we're currently looking at

# File lib/pry-rescue.rb, line 198
def pry_hooks(ex)
  hooks = Pry.config.hooks.dup
  hooks.add_hook(:before_session, :save_captured_exception) do |_, _, _pry_|
    _pry_.last_exception = ex
    _pry_.backtrace = ex.backtrace
    _pry_.sticky_locals.merge!(:_rescued_ => ex)
    _pry_.exception_handler.call(_pry_.output, ex, _pry_)
  end

  hooks
end
stdlib_path?(file) click to toggle source

Is this path in the ruby standard library?

@param [String] file the absolute path @return [Boolean]

# File lib/pry-rescue.rb, line 163
def stdlib_path?(file)
  file.start_with?(RbConfig::CONFIG['libdir']) || %w( (eval) <internal:prelude> ).include?(file)
end
user_path?(file) click to toggle source

Is this path likely to be code the user is working with right now?

@param [String] file the absolute path @return [Boolean]

# File lib/pry-rescue.rb, line 127
def user_path?(file)
  return true  if current_path?(file)
  return false if stdlib_path?(file) || gem_path?(file)
  true
end
with_program_name(name) { || ... } click to toggle source
# File lib/pry-rescue.rb, line 210
def with_program_name name
  before = $PROGRAM_NAME
  $PROGRAM_NAME = name
  yield
ensure
  $PROGRAM_NAME = before
end
without_bindings_below_raise(bindings) click to toggle source

Remove bindings that are part of Interception/Pry.rescue's internal event handling that happens as part of the exception hooking process.

@param [Array<Binding>] bindings The call stack.

# File lib/pry-rescue.rb, line 171
def without_bindings_below_raise(bindings)
  return bindings if bindings.size <= 1
  bindings.drop_while do |b|
    SourceLocation.call(b)[0] == File.expand_path("../pry-rescue/core_ext.rb", __FILE__)
  end.drop_while do |b|
    Interception == b.eval("self")
  end
end
without_duplicates(bindings) click to toggle source

Remove multiple bindings for the same function.

@param [Array<Bindings>] bindings The call stack @return [Array<Bindings>]

# File lib/pry-rescue.rb, line 184
def without_duplicates(bindings)
  bindings.zip([nil] + bindings).reject do |b, c|
    # The eval('__method__') is there as a shortcut as loading a method
    # from a binding is very slow.
    c && (b.eval("::Kernel.__method__") == c.eval("::Kernel.__method__")) &&
                Pry::Method.from_binding(b) == Pry::Method.from_binding(c)
  end.map(&:first)
end