enhanced_errors 0.1.7 → 0.1.8

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.
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