nested_exceptions 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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