nested_exceptions 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # NestedExceptions
2
+
3
+ This simple library adds support for nested exceptions in Ruby.
4
+
5
+ ## Why?
6
+
7
+ It can be really painful to debug a situation where a library rescues an
8
+ exception and throws a different one, or when it has a bug in its
9
+ exception handling logic. This gem goes a diving catch and recovers the
10
+ play!
11
+
12
+ The best way to learn more about this topic is to investigate the great
13
+ source of information at the [Exceptional Ruby](http://exceptionalruby.com/) site.
14
+
15
+ ## How do I use it?
16
+
17
+ The best way to use it is to simply add the following line of code to
18
+ your project:
19
+
20
+ require 'nested_exceptions/global'
21
+
22
+ That includes the NestedExceptions module in all of Ruby's base
23
+ exception classes (except the Exception root class), magically enabling
24
+ you to debug the trickiest, buggiest libraries.
25
+
26
+ ### I'm not sure I want to include it globally
27
+
28
+ If you want to try it out without extending Ruby's exception classes,
29
+ you can also do the following:
30
+
31
+ require 'nested_exceptions'
32
+
33
+ class MyException < StandardError
34
+ include NestedExceptions
35
+ end
36
+
37
+ That will only extend your class and leave the rest of the exception
38
+ classes untouched.
39
+
40
+ ## Does it change anything?
41
+
42
+ Yes. A bit of extra information is added to your backtraces. In addition
43
+ to the standard backtrace, you'll get a little bit of nesting
44
+ information.
45
+
46
+ ruby examples/example.rb original
47
+
48
+ examples/example.rb:32:in `rescue in double_bug': oops (RuntimeError)
49
+ from examples/example.rb:30:in `double_bug'
50
+ from examples/example.rb:42:in `<main>'
51
+
52
+
53
+ ruby examples/example.rb nested
54
+
55
+ examples/example.rb:30:in `rescue in double_bug': oops (RuntimeError)
56
+ from --- cause: ErrorSpec::InternalError: problem
57
+ from examples/example.rb:20:in `rescue in problem'
58
+ from --- cause: RuntimeError: bug
59
+ from examples/example.rb:10:in `bug'
60
+ from examples/example.rb:14:in `nested_bug'
61
+ from examples/example.rb:18:in `problem'
62
+ from examples/example.rb:24:in `nested_problem'
63
+ from examples/example.rb:28:in `double_bug'
64
+ from examples/example.rb:35:in `<main>'
65
+
66
+ It also adds two methods to the exception:
67
+
68
+ * #root_cause: allows you to easily get the first exception
69
+ * #cause: get the exception (if any) that may have caused this one
70
+
71
+ ## Gotchas
72
+
73
+ Do not include the NestedExceptions module in a subclass before adding
74
+ it to its superclass. That will cause the nested exceptions feature to
75
+ deactivate itself to prevent a stack overflow.
@@ -0,0 +1,66 @@
1
+ require 'pp'
2
+
3
+ $: << './lib'
4
+ require 'nested_exceptions'
5
+
6
+ module ErrorSpec
7
+
8
+ NestedExceptions.define_standard_exception_classes_in ErrorSpec
9
+
10
+ class Example
11
+ def bug
12
+ raise 'bug'
13
+ end
14
+
15
+ def nested_bug
16
+ bug
17
+ end
18
+
19
+ def problem
20
+ nested_bug
21
+ rescue
22
+ raise InternalError, 'problem'
23
+ end
24
+
25
+ def nested_problem
26
+ problem
27
+ end
28
+
29
+ def double_bug
30
+ nested_problem
31
+ rescue
32
+ raise 'oops'
33
+ end
34
+ end
35
+ end
36
+
37
+ example = ErrorSpec::Example.new
38
+
39
+ if ARGV.first == 'original'
40
+
41
+ puts "Original exception:"
42
+ example.double_bug
43
+
44
+ elsif ARGV.first == 'nested'
45
+
46
+ require 'nested_exceptions/global'
47
+ begin
48
+ example.double_bug
49
+ rescue StandardError => e
50
+ if Object.const_defined? :RUBY_ENGINE and (RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx')
51
+ puts "Note that JRuby and Rubinius do not display the modified backtrace:"
52
+ pp e.backtrace
53
+ puts
54
+ end
55
+ puts "Nested exception:"
56
+ raise
57
+ end
58
+
59
+ else
60
+
61
+ puts "Usage:"
62
+ puts
63
+ puts "ruby #{ $0 } [original|nested]"
64
+
65
+ end
66
+
@@ -6,18 +6,33 @@ module NestedExceptions
6
6
  attr_reader :cause
7
7
 
8
8
  def initialize(message = nil, cause = nil)
9
- @cause = cause || $!
10
- super(message)
9
+ # @cause could be defined if this module is included multiple
10
+ # times in a class heirarchy.
11
+ @illegal_nesting = defined? @cause
12
+ @recursing = false
13
+ if @illegal_nesting
14
+ warn "WARNING: NestedExceptions is included in the class heirarchy of #{ self.class } more than once."
15
+ warn "- Ensure if you require 'nested_exceptions/global' or manually add"
16
+ warn " NestedExceptions to a built-in Ruby exception class, you must do it at"
17
+ warn " the beginning of the program."
18
+ else
19
+ @cause = cause || $!
20
+ super(message)
21
+ end
11
22
  end
12
23
 
13
- if Object.const_defined? :RUBY_ENGINE and RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx'
24
+ if Object.const_defined? :RUBY_ENGINE and (RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx')
14
25
  def backtrace
15
- return @processed_backtrace if defined? @processed_backtrace and @processed_backtrace
16
- @processed_backtrace = process_backtrace(super)
26
+ prevent_recursion do
27
+ return @processed_backtrace if defined? @processed_backtrace and @processed_backtrace
28
+ @processed_backtrace = process_backtrace(super)
29
+ end
17
30
  end
18
31
  else
19
32
  def set_backtrace(bt)
20
- super process_backtrace(bt)
33
+ prevent_recursion do
34
+ super process_backtrace(bt)
35
+ end
21
36
  end
22
37
  end
23
38
 
@@ -33,18 +48,34 @@ module NestedExceptions
33
48
 
34
49
  protected
35
50
 
51
+ # This shouldn't be necessary anymore
52
+ def prevent_recursion
53
+ if not @recursing and not @illegal_nesting
54
+ begin
55
+ @recursing = true
56
+ yield
57
+ ensure
58
+ @recursing = false
59
+ end
60
+ end
61
+ end
62
+
36
63
  def process_backtrace(bt)
37
- if cause
38
- cause.backtrace.reverse.each do |line|
64
+ bt ||= []
65
+ if @cause and not @illegal_nesting
66
+ cause_backtrace = @cause.backtrace || []
67
+ cause_backtrace.reverse.each do |line|
39
68
  if bt.last == line
40
69
  bt.pop
41
70
  else
42
71
  break
43
72
  end
44
73
  end
45
- bt << "--- cause: #{cause.class.name}: #{cause}"
46
- bt.concat cause.backtrace
74
+ bt << "--- cause: #{@cause.class}: #{@cause}"
75
+ bt.concat cause_backtrace
47
76
  end
48
77
  bt
78
+ rescue Exception => e
79
+ warn "exception processing backtrace: #{ e.class }: #{ e.message }\n #{ caller(0).join("\n ") }"
49
80
  end
50
81
  end
@@ -5,9 +5,9 @@ module NestedExceptions
5
5
  #
6
6
  # I actually recommend that you just define these manually, but this may be
7
7
  # a handy shortcut in smaller projects.
8
- def define_standard_exception_classes_in(target_module)
8
+ def define_standard_exception_classes_in(target_module, add_module = false)
9
9
  definitions = %{
10
- class Error < StandardError; include NestedExceptions; end
10
+ class Error < StandardError; #{ add_module ? 'include NestedExceptions;' : '' } end
11
11
  class UserError < Error; end
12
12
  class LogicError < Error; end
13
13
  class ClientError < LogicError; end
@@ -1,5 +1,5 @@
1
1
  module NestedExceptions
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
 
4
4
  def self.const_missing(name)
5
5
  if name == 'GLOBAL' or name == :GLOBAL
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nested_exceptions
3
3
  version: !ruby/object:Gem::Version
4
- hash: 2387456872237229630
4
+ hash: 3005118107932468525
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
- - 0
10
- version: 1.0.0
9
+ - 1
10
+ version: 1.0.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Darrick Wiebe
@@ -45,7 +45,9 @@ files:
45
45
  - .gitignore
46
46
  - .rspec
47
47
  - Gemfile
48
+ - README.md
48
49
  - Rakefile
50
+ - examples/example.rb
49
51
  - lib/nested_exceptions.rb
50
52
  - lib/nested_exceptions/define.rb
51
53
  - lib/nested_exceptions/global.rb