enhanced_errors 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12d823c10e0bd52d8ca38f39e5851104676be83be50ee5d3246b339eea00b3d4
4
- data.tar.gz: 3d2df93d9c9b9712fe62bd1b3909032ece37ed8dc32ac9453d3b751655f6df83
3
+ metadata.gz: edc10f9326ea041bd8b367e57eb1037767847df8359408529445160ae76876b2
4
+ data.tar.gz: d5a4aee4cd8f3cca01a57fb10c25b120f5d3e7e1f5bf0255ef107255823704ef
5
5
  SHA512:
6
- metadata.gz: 4952714cb57a30e8100889c7e275d96626abdb41b9bae9a894f4e8eb9d74de429d0bc9d3e1b8848cc89759d017258044c71014453186ffde80d5b19b90d1ce54
7
- data.tar.gz: 152b2feb1962389acf32e13ad965bb0959f5aed731840cf96979c840a2dd1e9843a599821b5b54acf599bf74cf0c4a9f90e21c34e6837aab412826a6662d072c
6
+ metadata.gz: 4f3519f1c11d31cccfe4110deda8a2dcbc26b9cbd7ab0c9a0cd6b9ac65d32c57cc24b35f2a4a0f075888d20be3a69d397a0f631e617741705d1634834096a084
7
+ data.tar.gz: 0a9d12908d54b1173123cea3f66de3bbfa96863d92fad8990c281459de0bd66b2e26e405910c31cb6336f726f1bba41e99aac2793238835ce144576acdb3004e
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "enhanced_errors"
3
- spec.version = "0.1.7"
3
+ spec.version = "0.1.8"
4
4
  spec.authors = ["Eric Beland"]
5
5
 
6
6
  spec.summary = "Automatically enhance your errors with messages containing variable values from the moment they were raised."
data/lib/binding.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # binding.rb
2
+
1
3
  module Debugging
2
4
  def let_vars_hash
3
5
  memoized_values = self.receiver.instance_variable_get(:@__memoized)&.instance_variable_get(:@memoized)
@@ -8,4 +10,3 @@ end
8
10
  class Binding
9
11
  include Debugging
10
12
  end
11
-
@@ -1,3 +1,5 @@
1
+ # enhanced_errors.rb
2
+
1
3
  require 'set'
2
4
  require 'json'
3
5
 
@@ -6,7 +8,6 @@ require_relative 'error_enhancements'
6
8
  require_relative 'binding'
7
9
 
8
10
  # While we could just catch StandardError, we would miss a number of things.
9
-
10
11
  IGNORED_EXCEPTIONS = [
11
12
  SystemExit,
12
13
  NoMemoryError,
@@ -64,6 +65,11 @@ class EnhancedErrors
64
65
  # @return [Set<Symbol>]
65
66
  attr_accessor :skip_list
66
67
 
68
+ # Determines whether to capture :rescue events.
69
+ #
70
+ # @return [Boolean]
71
+ attr_accessor :capture_rescue
72
+
67
73
  # Regular expression to identify gem paths.
68
74
  #
69
75
  # @return [Regexp]
@@ -137,6 +143,19 @@ class EnhancedErrors
137
143
  @capture_let_variables
138
144
  end
139
145
 
146
+ # Gets or sets whether to capture :rescue events.
147
+ #
148
+ # @param value [Boolean, nil] The desired state. If `nil`, returns the current value.
149
+ # @return [Boolean] Whether :rescue events are being captured.
150
+ def capture_rescue(value = nil)
151
+ if value.nil?
152
+ @capture_rescue = @capture_rescue.nil? ? false : @capture_rescue
153
+ else
154
+ @capture_rescue = value
155
+ end
156
+ @capture_rescue
157
+ end
158
+
140
159
  # Retrieves the current skip list, initializing it with default values if not already set.
141
160
  #
142
161
  # @return [Set<Symbol>] The current skip list.
@@ -166,8 +185,7 @@ class EnhancedErrors
166
185
  # @param options [Hash] Additional configuration options.
167
186
  # @yield [void] A block for additional configuration.
168
187
  # @return [void]
169
- def enhance!(enabled: true, debug: false, capture_events: default_capture_events, **options, &block)
170
- capture_events = Array(capture_events)
188
+ def enhance!(enabled: true, debug: false, capture_events: nil, **options, &block)
171
189
  @output_format = nil
172
190
  @eligible_for_capture = nil
173
191
  @original_global_variables = nil
@@ -180,7 +198,6 @@ class EnhancedErrors
180
198
  @debug = debug
181
199
  @original_global_variables = global_variables
182
200
 
183
- validate_and_set_capture_events(capture_events)
184
201
  options.each do |key, value|
185
202
  setter_method = "#{key}="
186
203
  if respond_to?(setter_method)
@@ -195,6 +212,7 @@ class EnhancedErrors
195
212
  @config_block = block_given? ? block : nil
196
213
  instance_eval(&@config_block) if @config_block
197
214
 
215
+ validate_and_set_capture_events(capture_events)
198
216
  start_tracing
199
217
  end
200
218
  end
@@ -259,7 +277,13 @@ class EnhancedErrors
259
277
  def format(captured_bindings = [], output_format = get_default_format_for_environment)
260
278
  result = binding_infos_array_to_string(captured_bindings, output_format)
261
279
  if @on_format_hook
262
- result = @on_format_hook.call(result)
280
+ begin
281
+ result = @on_format_hook.call(result)
282
+ rescue => e
283
+ # Since the on_format_hook failed, do not display the data
284
+ result = ''
285
+ # Optionally, log the error safely if logging is guaranteed not to raise exceptions
286
+ end
263
287
  else
264
288
  result = default_on_format(result)
265
289
  end
@@ -345,7 +369,7 @@ class EnhancedErrors
345
369
  # @return [Hash, nil] The validated binding information or `nil` if invalid.
346
370
  def validate_binding_format(binding_info)
347
371
  unless binding_info.keys.include?(:capture_event) && binding_info[:variables].is_a?(Hash)
348
- puts "Invalid binding_info format."
372
+ # Log or handle the invalid format as needed
349
373
  return nil
350
374
  end
351
375
  binding_info
@@ -356,10 +380,12 @@ class EnhancedErrors
356
380
  # @param binding_info [Hash] The binding information to format.
357
381
  # @return [String] The formatted string.
358
382
  def binding_info_string(binding_info)
359
- capture_event = binding_info[:capture_event].to_s.capitalize
360
- result = "#{Colors.red(capture_event)}: #{Colors.blue(binding_info[:source])}"
383
+ capture_event = safe_to_s(binding_info[:capture_event]).capitalize
384
+ source = safe_to_s(binding_info[:source])
385
+ result = "#{Colors.red(capture_event)}: #{Colors.blue(source)}"
361
386
 
362
- result += method_and_args_desc(binding_info[:method_and_args])
387
+ method_desc = method_and_args_desc(binding_info[:method_and_args])
388
+ result += method_desc
363
389
 
364
390
  variables = binding_info[:variables] || {}
365
391
 
@@ -386,9 +412,7 @@ class EnhancedErrors
386
412
  end
387
413
  result + "\n"
388
414
  rescue => e
389
- # we swallow and don't re-raise to avoid any recursion problems. We don't want to
390
- # mess up the original exception handling.
391
- puts "EnhancedErrors error in binding_info_string: #{e.message} #{e.backtrace}"
415
+ # Avoid raising exceptions during formatting
392
416
  return ''
393
417
  end
394
418
 
@@ -399,7 +423,7 @@ class EnhancedErrors
399
423
  # @return [void]
400
424
  def start_tracing
401
425
  return if @trace && @trace.enabled?
402
- events = @capture_events ? @capture_events.to_a : [:raise]
426
+ events = @capture_events ? @capture_events.to_a : default_capture_events
403
427
  @trace = TracePoint.new(*events) do |tp|
404
428
  next if Thread.current[:enhanced_errors_processing] || ignored_exception?(tp.raised_exception)
405
429
  Thread.current[:enhanced_errors_processing] = true
@@ -411,7 +435,6 @@ class EnhancedErrors
411
435
  next
412
436
  end
413
437
 
414
- exception = tp.raised_exception
415
438
  binding_context = tp.binding
416
439
 
417
440
  unless exception.instance_variable_defined?(:@binding_infos)
@@ -426,13 +449,13 @@ class EnhancedErrors
426
449
  }
427
450
 
428
451
  locals = binding_context.local_variables.map { |var|
429
- [var, binding_context.local_variable_get(var)]
452
+ [var, safe_local_variable_get(binding_context, var)]
430
453
  }.to_h
431
454
 
432
455
  instance_vars = binding_context.receiver.instance_variables
433
456
 
434
457
  instances = instance_vars.map { |var|
435
- [var, (binding_context.receiver.instance_variable_get(var) rescue "#<Error getting instance variable: #{$!.message}>")]
458
+ [var, safe_instance_variable_get(binding_context.receiver, var)]
436
459
  }.to_h
437
460
 
438
461
  # Extract 'let' variables from :@__memoized (RSpec specific)
@@ -453,8 +476,8 @@ class EnhancedErrors
453
476
  }.to_h
454
477
  end
455
478
 
456
- capture_event = tp.event.to_s # 'raise' or 'rescue'
457
- location = "#{tp.path}:#{tp.lineno}"
479
+ capture_event = safe_to_s(tp.event) # 'raise' or 'rescue'
480
+ location = "#{safe_to_s(tp.path)}:#{safe_to_s(tp.lineno)}"
458
481
 
459
482
  binding_info = {
460
483
  source: location,
@@ -468,25 +491,31 @@ class EnhancedErrors
468
491
  lets: lets,
469
492
  globals: globals
470
493
  },
471
- exception: exception.class.name,
472
- capture_event: capture_event.to_s
494
+ exception: safe_to_s(exception.class.name),
495
+ capture_event: capture_event
473
496
  }
474
497
 
498
+ binding_info = default_on_capture(binding_info) # Apply default processing
499
+
475
500
  if on_capture_hook
476
- binding_info = on_capture_hook.call(binding_info)
477
- else
478
- binding_info = default_on_capture(binding_info)
501
+ begin
502
+ binding_info = on_capture_hook.call(binding_info)
503
+ rescue => e
504
+ # Since the on_capture_hook failed, do not capture this binding_info
505
+ binding_info = nil
506
+ # Optionally, log the error safely if logging is guaranteed not to raise exceptions
507
+ end
479
508
  end
480
509
 
481
- binding_info = validate_binding_format(binding_info)
482
-
510
+ # Proceed only if binding_info is valid
483
511
  if binding_info
484
- exception.instance_variable_get(:@binding_infos) << binding_info
485
- else
486
- puts "Invalid binding_info returned from on_capture, skipping."
512
+ binding_info = validate_binding_format(binding_info)
513
+ if binding_info
514
+ exception.instance_variable_get(:@binding_infos) << binding_info
515
+ end
487
516
  end
488
517
  rescue => e
489
- puts "Error in TracePoint block: #{e.message}"
518
+ # Avoid any code here that could raise exceptions
490
519
  ensure
491
520
  Thread.current[:enhanced_errors_processing] = false
492
521
  end
@@ -494,22 +523,22 @@ class EnhancedErrors
494
523
  @trace.enable
495
524
  end
496
525
 
526
+ # Checks if the exception is in the ignored exceptions list.
527
+ #
528
+ # @param exception [Exception] The exception to check.
529
+ # @return [Boolean] `true` if the exception should be ignored, otherwise `false`.
497
530
  def ignored_exception?(exception)
498
- IGNORED_EXCEPTIONS.each do |klass|
499
- return true if exception.is_a?(klass)
500
- end
501
- false
531
+ IGNORED_EXCEPTIONS.any? { |klass| exception.is_a?(klass) }
502
532
  end
503
533
 
504
-
505
534
  # Retrieves the current test name from RSpec, if available.
506
535
  #
507
536
  # @return [String, nil] The current test name or `nil` if not in a test context.
508
537
  def test_name
509
538
  if defined?(RSpec)
510
- return RSpec&.current_example&.full_description
539
+ RSpec&.current_example&.full_description
511
540
  end
512
- rescue => e
541
+ rescue
513
542
  nil
514
543
  end
515
544
 
@@ -517,32 +546,47 @@ class EnhancedErrors
517
546
  #
518
547
  # @return [Set<Symbol>] The default set of capture types
519
548
  def default_capture_events
520
- default_events = [:raise]
521
- default_events << :rescue if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3.0')
522
- Set.new(default_events)
549
+ events = [:raise]
550
+ if capture_rescue && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3.0')
551
+ events << :rescue
552
+ end
553
+ Set.new(events)
523
554
  end
524
555
 
556
+ # Validates and sets the capture events for TracePoint.
557
+ #
558
+ # @param capture_events [Array<Symbol>, nil] The events to capture.
559
+ # @return [void]
525
560
  def validate_and_set_capture_events(capture_events)
526
- if capture_events.nil? || !valid_capture_events?(capture_events)
561
+ if capture_events.nil?
562
+ @capture_events = default_capture_events
563
+ return
564
+ end
565
+
566
+ unless valid_capture_events?(capture_events)
527
567
  puts "EnhancedErrors: Invalid capture_events provided. Falling back to defaults."
528
- capture_events = default_capture_events
568
+ @capture_events = default_capture_events
569
+ return
529
570
  end
530
571
 
531
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.0') && capture_events.include?(:rescue)
572
+ if capture_events.include?(:rescue) && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.0')
532
573
  puts "EnhancedErrors: Warning: :rescue capture_event is not supported in Ruby versions below 3.3.0 and will be ignored."
533
- capture_events.delete(:rescue)
574
+ capture_events = capture_events - [:rescue]
534
575
  end
535
576
 
536
577
  if capture_events.empty?
537
578
  puts "No valid capture_events provided to EnhancedErrors.enhance! Falling back to defaults."
538
- capture_events = default_capture_events
579
+ @capture_events = default_capture_events
580
+ return
539
581
  end
540
582
 
541
- @capture_events = capture_events.to_a
583
+ @capture_events = capture_events.to_set
542
584
  end
543
585
 
544
-
545
- # Validate capture_events: must be an Array or Set containing only :raise and/or :rescue.
586
+ # Validates the capture events.
587
+ #
588
+ # @param capture_events [Array<Symbol>] The events to validate.
589
+ # @return [Boolean] `true` if valid, otherwise `false`.
546
590
  def valid_capture_events?(capture_events)
547
591
  return false unless capture_events.is_a?(Array) || capture_events.is_a?(Set)
548
592
  valid_types = [:raise, :rescue].to_set
@@ -564,8 +608,8 @@ class EnhancedErrors
564
608
  locals = bind.local_variables
565
609
 
566
610
  parameters.map do |(type, name)|
567
- value = locals.include?(name) ? bind.local_variable_get(name) : nil
568
- "#{name}=#{value.inspect}"
611
+ value = locals.include?(name) ? safe_local_variable_get(bind, name) : nil
612
+ "#{name}=#{safe_inspect(value)}"
569
613
  rescue => e
570
614
  "#{name}=#<Error getting argument: #{e.message}>"
571
615
  end.join(", ")
@@ -581,9 +625,9 @@ class EnhancedErrors
581
625
  # @return [String] The formatted object name.
582
626
  def determine_object_name(tp, method_name)
583
627
  if tp.self.is_a?(Class) && tp.self.singleton_class == tp.defined_class
584
- "#{tp.self}.#{method_name}"
628
+ "#{safe_to_s(tp.self)}.#{method_name}"
585
629
  else
586
- "#{tp.self.class.name}##{method_name}"
630
+ "#{safe_to_s(tp.self.class.name)}##{method_name}"
587
631
  end
588
632
  rescue => e
589
633
  "#<Error inspecting value: #{e.message}>"
@@ -597,7 +641,7 @@ class EnhancedErrors
597
641
  begin
598
642
  var.is_a?(Symbol) ? eval("#{var}") : nil
599
643
  rescue => e
600
- "#<Error getting value for #{var}>" rescue '<value error>'
644
+ "#<Error getting value for #{var}>"
601
645
  end
602
646
  end
603
647
 
@@ -606,11 +650,14 @@ class EnhancedErrors
606
650
  # @param method_info [Hash] Information about the method and its arguments.
607
651
  # @return [String] The formatted description.
608
652
  def method_and_args_desc(method_info)
609
- return '' unless method_info[:object_name] != '' || method_info[:args]&.length.to_i > 0
610
- arg_str = method_info[:args]
611
- arg_str = "(#{arg_str})" if arg_str != ""
612
- str = method_info[:object_name] + arg_str
653
+ object_name = safe_to_s(method_info[:object_name])
654
+ args = safe_to_s(method_info[:args])
655
+ return '' if object_name.empty? && args.empty?
656
+ arg_str = args.empty? ? '' : "(#{args})"
657
+ str = object_name + arg_str
613
658
  "\n#{Colors.green('Method: ')}#{Colors.blue(str)}\n"
659
+ rescue => e
660
+ ''
614
661
  end
615
662
 
616
663
  # Generates a formatted description for a set of variables.
@@ -621,26 +668,23 @@ class EnhancedErrors
621
668
  vars_hash.map do |name, value|
622
669
  " #{Colors.purple(name)}: #{format_variable(value)}\n"
623
670
  end.join
671
+ rescue => e
672
+ ''
624
673
  end
625
674
 
626
675
  # Formats a variable for display, using `awesome_print` if available and enabled.
627
676
  #
628
677
  # @param variable [Object] The variable to format.
629
678
  # @return [String] The formatted variable.
630
-
631
679
  def format_variable(variable)
632
680
  if awesome_print_available? && Colors.enabled?
633
681
  variable.ai
634
682
  else
635
- variable.inspect
683
+ safe_inspect(variable)
636
684
  end
637
685
  rescue => e
638
- var_str = begin
639
- variable.to_s.truncate(30)
640
- rescue
641
- "[Unprintable variable]"
642
- end
643
- return "#{var_str}: [Inspection Error]"
686
+ var_str = safe_to_s(variable)
687
+ "#{var_str}: [Inspection Error]"
644
688
  end
645
689
 
646
690
  # Checks if the `AwesomePrint` gem is available.
@@ -651,6 +695,53 @@ class EnhancedErrors
651
695
  @awesome_print_available = defined?(AwesomePrint)
652
696
  end
653
697
 
698
+ # Safely calls `inspect` on a variable.
699
+ #
700
+ # @param variable [Object] The variable to inspect.
701
+ # @return [String] The inspected variable or a safe fallback.
702
+ def safe_inspect(variable)
703
+ variable.inspect
704
+ rescue => e
705
+ safe_to_s(variable)
706
+ end
707
+
708
+ # Safely converts a variable to a string, handling exceptions.
709
+ #
710
+ # @param variable [Object] The variable to convert.
711
+ # @return [String] The string representation or a safe fallback.
712
+ def safe_to_s(variable)
713
+ str = variable.to_s
714
+ if str.length > 30
715
+ str[0...30] + '...'
716
+ else
717
+ str
718
+ end
719
+ rescue
720
+ "[Unprintable variable]"
721
+ end
722
+
723
+ # Safely retrieves a local variable from a binding.
724
+ #
725
+ # @param binding_context [Binding] The binding context.
726
+ # @param var_name [Symbol] The name of the local variable.
727
+ # @return [Object] The value of the local variable or a safe fallback.
728
+ def safe_local_variable_get(binding_context, var_name)
729
+ binding_context.local_variable_get(var_name)
730
+ rescue
731
+ "[Error accessing local variable #{var_name}]"
732
+ end
733
+
734
+ # Safely retrieves an instance variable from an object.
735
+ #
736
+ # @param obj [Object] The object.
737
+ # @param var_name [Symbol] The name of the instance variable.
738
+ # @return [Object] The value of the instance variable or a safe fallback.
739
+ def safe_instance_variable_get(obj, var_name)
740
+ obj.instance_variable_get(var_name)
741
+ rescue
742
+ "[Error accessing instance variable #{var_name}]"
743
+ end
744
+
654
745
  # Default implementation for the on_format hook.
655
746
  #
656
747
  # @param string [String] The formatted exception message.
@@ -1,13 +1,20 @@
1
+ # error_enhancements.rb
2
+
1
3
  module ErrorEnhancements
2
4
  def message
3
- original_message = super()
4
- if original_message.include?(variables_message)
5
+ original_message = begin
6
+ super()
7
+ rescue
8
+ ''
9
+ end
10
+ vars_message = variables_message rescue ""
11
+ if original_message.include?(vars_message)
5
12
  original_message
6
13
  else
7
- "#{original_message}\n#{variables_message}"
14
+ "#{original_message}\n#{vars_message}"
8
15
  end
9
16
  rescue => e
10
- original_message
17
+ original_message || ''
11
18
  end
12
19
 
13
20
  def variables_message
@@ -18,7 +25,8 @@ module ErrorEnhancements
18
25
  end
19
26
  EnhancedErrors.format(bindings_of_interest)
20
27
  rescue => e
21
- puts "Error in variables_message: #{e.message}"
28
+ # Avoid using puts; consider logging instead
29
+ # Avoid raising exceptions in rescue blocks
22
30
  ""
23
31
  end
24
32
  end
@@ -44,7 +52,7 @@ module ErrorEnhancements
44
52
  bindings_of_interest << binding_infos.first if binding_infos.first
45
53
  end
46
54
 
47
- # find the last rescue binding if there is one
55
+ # Find the last rescue binding if there is one
48
56
  binding_infos.reverse.each do |info|
49
57
  if info[:capture_event] == 'rescue'
50
58
  bindings_of_interest << info
@@ -53,5 +61,4 @@ module ErrorEnhancements
53
61
  end
54
62
  bindings_of_interest
55
63
  end
56
-
57
64
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enhanced_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Beland