Module: PryExceptionExplorer

Defined in:
lib/pry-exception_explorer/core_ext.rb,
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

Overview

PryExceptionExplorer monkey-patches to Object

Defined Under Namespace

Modules: CoreExtensions, ExceptionHelpers Classes: Intercept, LazyFrame

Constant Summary collapse

CONTINUE_INLINE_EXCEPTION =

special constant

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

    banner "Usage: enter-exception\nEnter the context of the last exception\n"

    def process
      ex = extract_exception
      if enterable_exception?(ex)
        PryStackExplorer.create_and_push_frame_manager(ex.exception_call_stack, _pry_)
        PryExceptionExplorer.setup_exception_context(ex, _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 ex
        raise Pry::CommandError, "Exception can't be entered! (perhaps an internal exception)"
      else
        raise Pry::CommandError,  "No exception to enter!"
      end
    end

    def extract_exception
      if !arg_string.empty?
        ex = target.eval(arg_string)
        raise if !ex.is_a?(Exception)
        ex
      else
        last_exception
      end
    rescue
      raise Pry::CommandError, "Parameter must be a valid exception object."
    end
  end

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

    banner "Usage: exit-exception\nExit active exception and return to containing context.\n"

    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 "Usage: continue-exception\nAttempt to continue the current exception.\n"

    def process
      if internal_exception?
        raise Pry::CommandError, "Internal exceptions (C-level exceptions) cannot be continued!"
      elsif 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

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.inlineBoolean Also known as: inline?

Returns Whether exceptions are to be intercepted inline (at the raise site).

Returns:

  • (Boolean)

    Whether exceptions are to be intercepted inline (at the raise site).



28
29
30
# File 'lib/pry-exception_explorer.rb', line 28

def inline
  @inline
end

.old_inline_stateBoolean

Returns Holds the previous value of EE.inline.

Returns:

  • (Boolean)

    Holds the previous value of EE.inline



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

def old_inline_state
  @old_inline_state
end

.post_mortemBoolean Also known as: post_mortem?

Returns Whether exceptions are to be auto-rescued if they would terminate the program.

Returns:

  • (Boolean)

    Whether exceptions are to be auto-rescued if they would terminate the program.



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

def post_mortem
  @post_mortem
end

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.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/pry-exception_explorer.rb', line 167

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

.disable!Boolean

Disable Exception Explorer.

Returns:

  • (Boolean)


65
66
67
# File 'lib/pry-exception_explorer.rb', line 65

def disable!
  self.enabled = false
end

.enable!Boolean

Enable Exception Explorer.

Returns:

  • (Boolean)


59
60
61
# File 'lib/pry-exception_explorer.rb', line 59

def enable!
  self.enabled = true
end

.enabledBoolean Also known as: enabled?

Returns Whether Exception Explorer is enabled.

Returns:

  • (Boolean)

    Whether Exception Explorer is enabled.



75
76
77
# File 'lib/pry-exception_explorer.rb', line 75

def enabled
  !!local_hash[:enabled]
end

.enabled=(v) ⇒ Object

Parameters:

  • v (Boolean)

    Whether Exception Explorer is enabled.



70
71
72
# File 'lib/pry-exception_explorer.rb', line 70

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)



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/pry-exception_explorer.rb', line 208

def enter_exception(ex, options={})
  hooks = Pry.config.hooks.dup
  hooks.delete_hook(:before_session, :default)
  hooks.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.inline? && !PryExceptionExplorer.intercept_object.intercept_recurse?
  end.add_hook(:after_session, :manage_intercept_recurse) do
    PryExceptionExplorer.intercept_object.enable! if !PryExceptionExplorer.intercept_object.active?
  end.add_hook(:before_session, :display_exception) do |_, _, _pry_|
    _pry_.run_command "cat --ex"
  end
  
  Pry.start binding, :call_stack => ex.exception_call_stack, :hooks => hooks
end

.initObject

Set initial state



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/pry-exception_explorer.rb', line 225

def init
  # disable EE by default
  PryExceptionExplorer.enabled = false

  # auto-start sessions on exceptions that would kill the program
  PryExceptionExplorer.post_mortem = false

  # default is to capture all exceptions
  PryExceptionExplorer.intercept(Exception)

  # disable inline sessions by defulat
  PryExceptionExplorer.inline = false
  at_exit do
    ex = $!
    
    next if !PryExceptionExplorer.post_mortem? || !ex
    
    if ex.should_intercept?
      enter_exception(ex)
    else
      raise ex
    end
  end
end

.inline!Object

Ensure exceptions are intercepted at the raise site, and enable EE.



38
39
40
41
# File 'lib/pry-exception_explorer.rb', line 38

def inline!
  self.enabled = true 
  self.inline = true
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.



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

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:



140
141
142
# File 'lib/pry-exception_explorer.rb', line 140

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:



135
136
137
# File 'lib/pry-exception_explorer.rb', line 135

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

.local_hashHash

Returns A local hash.

Returns:

  • (Hash)

    A local hash.



53
54
55
# File 'lib/pry-exception_explorer.rb', line 53

def local_hash
  @hash ||= {}
end

.post_mortem!Object

Ensure exceptions are intercepted if they would terminate the program, and enable EE.



44
45
46
47
# File 'lib/pry-exception_explorer.rb', line 44

def post_mortem!
  self.enabled = true 
  self.post_mortem = true
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)



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

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.



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/pry-exception_explorer.rb', line 149

def should_intercept_exception?(frame, ex)
  # special case, or we go into infinite loop. CodeRay uses
  # exceptions for flow control :/
  if defined?(CodeRay::Encoders) && frame.eval('self') == CodeRay::Encoders
    false

    # normal case
  elsif 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.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/pry-exception_explorer.rb', line 85

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