Module: PryExceptionExplorer

Defined in:
lib/pry-exception_explorer.rb,
lib/pry-exception_explorer/version.rb,
lib/pry-exception_explorer/commands.rb,
lib/pry-exception_explorer/intercept.rb,
lib/pry-exception_explorer/lazy_frame.rb,
lib/pry-exception_explorer/shim_builder.rb

Defined Under Namespace

Modules: ExceptionHelpers, ShimBuilder Classes: Intercept, LazyFrame

Constant Summary collapse

CONTINUE_INLINE_EXCEPTION =

special constant

Object.new
VERSION =
"0.1.6"
Commands =
Pry::CommandSet.new do
  create_command "enter-exception", "Enter the context of the last exception" do
    include PryExceptionExplorer::ExceptionHelpers

    banner <<-BANNER
      Usage: enter-exception
      Enter the context of the last exception
    BANNER

    def process
      if enterable_exception?
        PryStackExplorer.create_and_push_frame_manager(last_exception.exception_call_stack, _pry_)
        PryExceptionExplorer.setup_exception_context(last_exception, _pry_)

        # have to use _pry_.run_command instead of 'run' here as
        # 'run' works on the current target which hasnt been updated
        # yet, whereas _pry_.run_command operates on the newly
        # updated target (the context of the exception)
        _pry_.run_command "whereami"
      elsif last_exception
        raise Pry::CommandError, "Current exception can't be entered! (perhaps a C exception)"
      else
        raise Pry::CommandError,  "No exception to enter!"
      end
    end
  end

  create_command "exit-exception", "Leave the context of the current exception." do
    include ExceptionHelpers

    banner <<-BANNER
      Usage: exit-exception
      Exit active exception and return to containing context.
    BANNER

    def process
      if !in_exception?
        raise Pry::CommandError, "You are not in an exception!"
      elsif !prior_context_exists?
        run "exit-all"
      else
        popped_fm = PryStackExplorer.pop_frame_manager(_pry_)
        _pry_.last_exception = popped_fm.user[:exception]
      end
    end
  end

  create_command "continue-exception", "Attempt to continue the current exception." do
    include ExceptionHelpers

    banner <<-BANNER
      Usage: continue-exception
      Attempt to continue the current exception.
    BANNER

    def process
      if inline_exception?
        PryStackExplorer.pop_frame_manager(_pry_)
        run "exit-all PryExceptionExplorer::CONTINUE_INLINE_EXCEPTION"
      elsif normal_exception?
        popped_fm = PryStackExplorer.pop_frame_manager(_pry_)
        popped_fm.user[:exception].continue
      else
        raise Pry::CommandError, "No exception to continue!"
      end
    end
  end

end
CompileError =
Class.new(StandardError)

Class Method Summary collapse

Class Method Details

.amend_exception_call_stack!(ex) ⇒ Object

Amends (destructively) an exception call stack according to the info in PryExceptionExplorer.intercept_object, specifically PryExceptionExplorer::Intercept#skip_until_block and PryExceptionExplorer::Intercept#skip_while_block.

Parameters:

  • ex (Exception)

    The exception whose call stack will be amended.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/pry-exception_explorer.rb', line 136

def amend_exception_call_stack!(ex)
  call_stack = ex.exception_call_stack

  # skip_until
  if intercept_object.skip_until_block
    idx = call_stack.each_with_index.find_index do |frame, idx|
      intercept_object.skip_until_block.call(LazyFrame.new(frame, idx, call_stack))
    end
    call_stack = call_stack.drop(idx) if idx

  # skip_while
  elsif intercept_object.skip_while_block
    idx = call_stack.each_with_index.find_index do |frame, idx|
      intercept_object.skip_while_block.call(LazyFrame.new(frame, idx, call_stack)) == false
    end
    call_stack = call_stack.drop(idx) if idx
  end

  ex.exception_call_stack = call_stack
end

.enabledBoolean Also known as: enabled?

Returns Whether Exception Explorer is enabled.

Returns:

  • (Boolean)

    Whether Exception Explorer is enabled.



36
37
38
# File 'lib/pry-exception_explorer.rb', line 36

def enabled
  !!local_hash[:enabled]
end

.enabled=(v) ⇒ Object

Parameters:

  • v (Boolean)

    Whether Exception Explorer is enabled.



31
32
33
# File 'lib/pry-exception_explorer.rb', line 31

def enabled=(v)
  local_hash[:enabled] = v
end

.enter_exception(ex, options = {}) ⇒ Object

Enter the exception context.

Parameters:

  • ex (Exception)

    The exception.

  • options (Hash) (defaults to: {})

    The optional configuration parameters.

Options Hash (options):

  • :inline (Boolean)

    Whether the exception is being entered inline (i.e within the raise method itself)



177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/pry-exception_explorer.rb', line 177

def enter_exception(ex, options={})
  hooks = Pry.config.hooks.dup.add_hook(:before_session, :set_exception_flag) do |_, _, _pry_|
    setup_exception_context(ex, _pry_, options)
  end.add_hook(:before_session, :manage_intercept_recurse) do
    PryExceptionExplorer.intercept_object.disable! if !PryExceptionExplorer.intercept_object.intercept_recurse?
  end.add_hook(:after_session, :manage_intercept_recurse) do
    PryExceptionExplorer.intercept_object.enable! if !PryExceptionExplorer.intercept_object.active?
  end

  #   Pry.load_plugins
  #   binding.pry    # if we have this here and step through with  pry-nav sometimes we get segfaults :/

  Pry.start binding, :call_stack => ex.exception_call_stack, :hooks => hooks
end

.initObject

Set initial state



193
194
195
196
197
198
199
200
201
202
# File 'lib/pry-exception_explorer.rb', line 193

def init
  # disable by default (intercept exceptions inline)
  PryExceptionExplorer.wrap_active = false

  # default is to capture all exceptions
  PryExceptionExplorer.intercept { true }

  # disable by default
  PryExceptionExplorer.enabled = false
end

.intercept(*exceptions) {|lazy_frame, exception| ... } ⇒ Object

This method allows the user to assert the situations where an exception interception occurs. This method can be invoked in two ways. The general form takes a block, the block is passed both the frame where the exception was raised, and the exception itself. The user then creates an assertion (a stack-assertion) based on these attributes. If the assertion is later satisfied by a raised exception, that exception will be intercepted. In the second form, the method simply takes an exception class, or a number of exception classes. If one of these exceptions is raised, it will be intercepted.

Examples:

First form: Assert method name is toad and exception is an ArgumentError

PryExceptionExplorer.intercept do |frame, ex|
  frame.method_name == :toad && ex.is_a?(ArgumentError)
end

Second form: Assert exception is either ArgumentError or RuntimeError

PryExceptionExplorer.intercept(ArgumentError, RuntimeError)

Parameters:

  • exceptions (Array)

    The exception classes that will be intercepted.

Yields:

  • (lazy_frame, exception)

    The block that determines whether an exception will be intercepted.

Yield Parameters:

  • frame (PryExceptionExplorer::Lazyframe)

    The frame where the exception was raised.

  • exception (Exception)

    The exception that was raised.

Yield Returns:

  • (Boolean)

    The result of the stack assertion.



99
100
101
102
103
104
105
106
107
# File 'lib/pry-exception_explorer.rb', line 99

def intercept(*exceptions, &block)
  return if exceptions.empty? && block.nil?

  if !exceptions.empty?
    block = proc { |_, ex| exceptions.any? { |v| v === ex } }
  end

  local_hash[:intercept_object] = Intercept.new(block)
end

.intercept_objectPryExceptionExplorer::Intercept

Returns The object defined earlier by a call to PryExceptionExplorer.intercept.

Returns:



115
116
117
# File 'lib/pry-exception_explorer.rb', line 115

def intercept_object
  local_hash[:intercept_object]
end

.intercept_object=(b) ⇒ PryExceptionExplorer::Intercept

Returns The object defined earlier by a call to PryExceptionExplorer.intercept.

Returns:



110
111
112
# File 'lib/pry-exception_explorer.rb', line 110

def intercept_object=(b)
  local_hash[:intercept_object] = b
end

.local_hashHash

Returns A local hash.

Returns:

  • (Hash)

    A local hash.



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

def local_hash
  @hash ||= {}
end

.setup_exception_context(ex, _pry_, options = {}) ⇒ Object

Prepare the Pry instance and associated call-stack when entering into an exception context.

Parameters:

  • ex (Exception)

    The exception.

  • _pry_ (Pry)

    The relevant Pry instance.

  • options (Hash) (defaults to: {})

    The optional configuration parameters.

Options Hash (options):

  • :inline (Boolean)

    Whether the exception is being entered inline (i.e within the raise method itself)



164
165
166
167
168
169
170
# File 'lib/pry-exception_explorer.rb', line 164

def setup_exception_context(ex, _pry_, options={})
  _pry_.last_exception = ex
  _pry_.backtrace = ex.backtrace

  PryStackExplorer.frame_manager(_pry_).user[:exception]        = ex
  PryStackExplorer.frame_manager(_pry_).user[:inline_exception] = !!options[:inline]
end

.should_intercept_exception?(frame, ex) ⇒ Boolean

This method invokes the PryExceptionExplorer.intercept_object, passing in the exception's frame and the exception object itself.

Parameters:

  • frame (Binding)

    The stack frame where the exception occurred.

  • ex (Exception)

    The exception that was raised.

Returns:

  • (Boolean)

    Whether the exception should be intercepted.



124
125
126
127
128
129
130
# File 'lib/pry-exception_explorer.rb', line 124

def should_intercept_exception?(frame, ex)
  if intercept_object
    intercept_object.call(LazyFrame.new(frame), ex)
  else
    false
  end
end

.wrap { ... } ⇒ Object

Wrap the provided block - intercepting all exceptions that bubble out, provided they satisfy the assertion in PryExceptionExplorer.intercept.

Yields:

  • The block to wrap.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/pry-exception_explorer.rb', line 60

def wrap
  old_enabled, old_wrap_active = enabled, wrap_active
  self.enabled     = true
  self.wrap_active = true
  yield
rescue Exception => ex
  if ex.should_intercept?
    enter_exception(ex)
  else
    raise ex
  end
ensure
  self.enabled     = old_enabled
  self.wrap_active = old_wrap_active
end

.wrap_activeBoolean Also known as: wrap_active?

Returns Whether to intercept only those exceptions that bubble out of EE.wrap block.

Returns:

  • (Boolean)

    Whether to intercept only those exceptions that bubble out of EE.wrap block.



48
49
50
# File 'lib/pry-exception_explorer.rb', line 48

def wrap_active
  !!local_hash[:wrap_active]
end

.wrap_active=(v) ⇒ Object

Parameters:

  • v (Boolean)

    Whether to intercept only those exceptions that bubble out of EE.wrap block.



42
43
44
# File 'lib/pry-exception_explorer.rb', line 42

def wrap_active=(v)
  local_hash[:wrap_active] = v
end