im-lost 1.2.0 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38b1a5b729438621357238f12f64f1d397ee56e18174c2cc1ec60067dd0ab15e
4
- data.tar.gz: 157e15bcf3c9e9539467347e9cac31b5ed40adf9953bc6ab28dab6d163907288
3
+ metadata.gz: 533f4f50596d5c6a55f674e813d7053d954a26864ea80c2e266f8c6bd92c00fd
4
+ data.tar.gz: 31a7bd884fabb3aba95789ff9771811300e3c7cb6e75ec3c762a394df603b2ed
5
5
  SHA512:
6
- metadata.gz: 8ad0cc365f318ef0113d6f099618204b11ecd547a84d525893cc2691a59a5159594c8606de34bad9268867751708cf98f03102a1b0feefbab6cefa41c2436449
7
- data.tar.gz: eb0a3abf3fffc2273593741c880c9aa5b4461d2b14877e324834216327d9ec427a80eaf2b075381df601b920477b50c8b95190c34ae9cc73fb3d1b25407aa4a1
6
+ metadata.gz: 4631c248cc8af2691d09651ed3a90b863f72ba6954e3c14172208ceb153e69b834e5f67c12555347fa474f2f97a44ad579f8ef85a6b5db4be07ea574b76a7f36
7
+ data.tar.gz: d1accd54c95db533220dd98a6a741380be91050568d16cbc3b6acdaef1ba147b98f2ad39f560b1613b7351c01dca4a44bafd4db46e7ef526b169e5821da68fc0
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Mike Blumtritt
3
+ Copyright (c) 2024-2025 Mike Blumtritt
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -10,7 +10,7 @@ ImLost helps you by analyzing function calls of objects, informing you about exc
10
10
 
11
11
  ## Description
12
12
 
13
- If you like to undertsand method call details you get a call trace with `ImLost.trace`:
13
+ If you like to understand method call details you get a call trace with `ImLost.trace`:
14
14
 
15
15
  ```ruby
16
16
  File.open('test.txt', 'w') do |file|
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ImLost
4
4
  # The version number of the gem.
5
- VERSION = '1.2.0'
5
+ VERSION = '1.2.1'
6
6
  end
data/lib/im-lost.rb CHANGED
@@ -54,7 +54,7 @@ module ImLost
54
54
  return @output = value if defined?(value.<<)
55
55
  raise(
56
56
  NoMethodError,
57
- "undefined method `<<' for an instance of #{
57
+ "undefined method '<<' for an instance of #{
58
58
  Kernel.instance_method(:class).bind(value).call
59
59
  }"
60
60
  )
@@ -73,18 +73,18 @@ module ImLost
73
73
  # @attribute [r] trace_calls
74
74
  # @return [Boolean] whether method calls will be traced
75
75
  #
76
- def trace_calls = @trace_calls[0].enabled?
76
+ def trace_calls = @trace_calls.enabled?
77
77
 
78
78
  def trace_calls=(value)
79
79
  if value
80
- @trace_calls.each(&:enable) unless trace_calls
80
+ @trace_calls.enable unless trace_calls
81
81
  elsif trace_calls
82
- @trace_calls.each(&:disable)
82
+ @trace_calls.disable
83
83
  end
84
84
  end
85
85
 
86
86
  #
87
- # Traces execptions raised within a given block.
87
+ # Traces exceptions raised within a given block.
88
88
  #
89
89
  # @example Trace exception and rescue handling
90
90
  # ImLost.trace_exceptions do
@@ -101,7 +101,7 @@ module ImLost
101
101
  # # x RuntimeError: something went wrong!
102
102
  # # /examples/test.rb:4
103
103
  #
104
- # @param with_locations [Boolean] wheter the locations should be included
104
+ # @param with_locations [Boolean] whether the locations should be included
105
105
  # into the exception trace information
106
106
  # @yieldreturn [Object] return result
107
107
  #
@@ -118,19 +118,19 @@ module ImLost
118
118
  end
119
119
 
120
120
  #
121
- # Enables/disables tracing of returned valuess of method calls.
121
+ # Enables/disables tracing of returned values of method calls.
122
122
  # This is enabled by default.
123
123
  #
124
124
  # @attribute [r] trace_results
125
125
  # @return [Boolean] whether return values will be traced
126
126
  #
127
- def trace_results = @trace_results[0].enabled?
127
+ def trace_results = @trace_results.enabled?
128
128
 
129
129
  def trace_results=(value)
130
130
  if value
131
- @trace_results.each(&:enable) unless trace_results
131
+ @trace_results.enable unless trace_results
132
132
  elsif trace_results
133
- @trace_results.each(&:disable)
133
+ @trace_results.disable
134
134
  end
135
135
  end
136
136
 
@@ -259,7 +259,7 @@ module ImLost
259
259
  #
260
260
  # Inspect internal variables of a given object.
261
261
  #
262
- # @note The dedictaed handling of `Fiber` is platform dependend!
262
+ # @note The dedicated handling of `Fiber` is platform dependent!
263
263
  #
264
264
  # @example Inspect current instance variables
265
265
  # @a = 22
@@ -303,19 +303,18 @@ module ImLost
303
303
  # @return [Object] the given object
304
304
  #
305
305
  def vars(object)
306
- out = Out.new
306
+ out = LineStore.new
307
307
  traced = @trace.delete(object)
308
308
  return _local_vars(out, object) if Binding === object
309
- location = Kernel.caller_locations(1, 1)[0]
310
- out << "* #{location.path}:#{location.lineno}"
309
+ out.location(Kernel.caller_locations(1, 1)[0])
311
310
  return _thread_vars(out, object) if Thread === object
312
- return _fiber_vars(out, object) if @fiber_supported && Fiber === object
311
+ return _fiber_vars(out, object) if @fiber_support && Fiber === object
313
312
  return _instance_vars(out, object) if defined?(object.instance_variables)
314
313
  out << ' !!! unable to retrieve vars'
315
314
  object
316
315
  ensure
317
316
  @trace[traced] = traced if traced
318
- out.flush(@output)
317
+ @output << out.to_s
319
318
  end
320
319
 
321
320
  private
@@ -329,10 +328,7 @@ module ImLost
329
328
  arg
330
329
  end
331
330
 
332
- def _trace_all(args)
333
- args.each { |arg| @trace[arg] = arg if _can_trace?(arg) }
334
- args
335
- end
331
+ def _trace_all(args) = args.each { @trace[_1] = _1 if _can_trace?(_1) }
336
332
 
337
333
  def _trace_b(arg)
338
334
  return yield(arg) if @trace.key?(arg) || !_can_trace?(arg)
@@ -390,14 +386,11 @@ module ImLost
390
386
  object
391
387
  end
392
388
 
393
- def _thread_identifier(thread)
394
- "#{THREAD_STATE[thread.status] || thread.status} Thread #{
395
- if defined?(thread.native_thread_id)
396
- thread.native_thread_id
397
- else
398
- thread.__id__
399
- end
400
- } #{thread.name}".rstrip
389
+ def _thread_identifier(thr)
390
+ state = thr.status
391
+ "#{state || (state.nil? ? 'aborted' : 'terminated')} Thread #{
392
+ defined?(thr.native_thread_id) ? thr.native_thread_id : thr.__id__
393
+ } #{thr.name}".rstrip
401
394
  end
402
395
  end
403
396
 
@@ -456,12 +449,12 @@ module ImLost
456
449
  def count = ids.size
457
450
 
458
451
  # @attribute [r] empty?
459
- # @return [Boolean] wheter the timer store is empty or not
452
+ # @return [Boolean] whether the timer store is empty or not
460
453
  def empty? = ids.empty?
461
454
 
462
455
  # @attribute [r] ids
463
456
  # @return [Array<Integer>] IDs of all registered timers
464
- def ids = (@ll.keys.keep_if { _1.is_a?(Integer) })
457
+ def ids = (@ll.keys.keep_if { Integer === _1 })
465
458
 
466
459
  #
467
460
  # Create and register a new named or anonymous timer.
@@ -487,7 +480,7 @@ module ImLost
487
480
  #
488
481
  def delete(*id_or_names)
489
482
  id_or_names.flatten.each do |id|
490
- if id.is_a?(Integer)
483
+ if Integer === id
491
484
  del = @ll.delete(id)
492
485
  @ll.delete(del[0]) if del
493
486
  else
@@ -508,10 +501,10 @@ module ImLost
508
501
  # identifier or name
509
502
  #
510
503
  def [](id_or_name)
511
- time = self.class.now
512
- timer = @ll[id_or_name.is_a?(Integer) ? id_or_name : id_or_name.to_s]
504
+ now = self.class.now
505
+ timer = @ll[Integer === id_or_name ? id_or_name : id_or_name.to_s]
513
506
  raise(ArgumentError, "not a timer - #{id_or_name.inspect}") unless timer
514
- @cb[timer[0], Kernel.caller_locations(1, 1)[0], time - timer[1]]
507
+ @cb[timer[0], Kernel.caller_locations(1, 1)[0], (now - timer[1]).round(4)]
515
508
  timer.__id__
516
509
  end
517
510
 
@@ -528,125 +521,132 @@ module ImLost
528
521
  nil
529
522
  end
530
523
 
531
- # @!visibility private
524
+ private
525
+
532
526
  def initialize(&block)
533
527
  @cb = block
534
528
  @ll = {}
535
529
  end
536
530
  end
537
531
 
538
- class Out
532
+ class LineStore
539
533
  def initialize(*lines) = (@lines = lines)
534
+ def to_s = (@lines << nil).join("\n")
540
535
  def <<(str) = @lines << str
541
- def location(loc) = @lines << " #{loc.path}:#{loc.lineno}"
542
- def flush(dev) = dev << (@lines << nil).join("\n")
536
+ def location(loc) = @lines << "* #{loc.path}:#{loc.lineno}"
537
+
538
+ def vars(kind, names)
539
+ return @lines << " <no #{kind} defined>" if names.empty?
540
+ @lines << " > #{kind}"
541
+ names.sort!.each { @lines << " #{_1}: #{yield(_1).inspect}" }
542
+ end
543
+ end
544
+ private_constant :LineStore
543
545
 
544
- def sig(prefix, info, args)
545
- args = args.join(', ')
546
- @lines << case info.self
546
+ class CCallLineStore < LineStore
547
+ def initialize(prefix, info, include_location)
548
+ @info = info
549
+ sig = "#{info.method_id}(#{args.join(', ')})"
550
+ case info.self
547
551
  when Class, Module
548
- "#{prefix} #{info.self}.#{info.method_id}(#{args})"
552
+ super("#{prefix} #{info.self}.#{sig}")
549
553
  else
550
- "#{prefix} #{info.defined_class}##{info.method_id}(#{args})"
554
+ super("#{prefix} #{info.defined_class}##{sig}")
551
555
  end
556
+ return unless include_location
557
+ loc = location
558
+ path = loc.path
559
+ path = path[10..-2] if path.start_with?('<internal:')
560
+ @lines << " #{path}:#{loc.lineno}"
552
561
  end
553
562
 
554
- def vars(kind, names)
555
- return @lines << " <no #{kind} defined>" if names.empty?
556
- @lines << " > #{kind}"
557
- names.sort!.each { @lines << " #{_1}: #{yield(_1).inspect}" }
563
+ def location = @info
564
+
565
+ def args
566
+ argc = -1
567
+ @info.parameters.map do |kind, _|
568
+ argc += 1
569
+ CARG_TYPE[kind] || "arg#{argc}"
570
+ end
558
571
  end
572
+
573
+ CARG_TYPE = {
574
+ rest: '*args',
575
+ keyrest: '**kwargs',
576
+ block: '&block'
577
+ }.compare_by_identity.freeze
559
578
  end
560
- private_constant :Out
579
+ private_constant :CCallLineStore
561
580
 
562
- ARG_SIG = { rest: '*', keyrest: '**', block: '&' }.compare_by_identity.freeze
563
- NO_NAME = { :* => 1, :** => 1, :& => 1 }.compare_by_identity.freeze
564
- THREAD_STATE = {
565
- false => 'terminated',
566
- nil => 'aborted'
567
- }.compare_by_identity.freeze
568
- private_constant :ARG_SIG, :NO_NAME, :THREAD_STATE
581
+ class RbCallLineStore < CCallLineStore
582
+ def initialize(prefix, info, include_location)
583
+ @ctx = info.binding
584
+ super
585
+ @ctx = nil
586
+ end
569
587
 
570
- @trace = {}.compare_by_identity
571
- @caller_locations = @exception_locations = true
572
- @output = STDERR
588
+ def location
589
+ locations = @ctx.eval('caller_locations(7,3)')
590
+ unless locations[1].path.start_with?('<internal:prelude')
591
+ return locations[1]
592
+ end
593
+ locations[0].path.start_with?('<internal:') ? locations[0] : locations[2]
594
+ end
573
595
 
574
- @timer = TimerStore.new { |title, location, time| @output << <<~TIMER_MSG }
575
- T #{title}: #{time ? "#{time} sec." : 'created'}
576
- #{location.path}:#{location.lineno}
577
- TIMER_MSG
578
- TimerStore.private_class_method(:new)
596
+ def args
597
+ @info.parameters.map do |kind, name|
598
+ next name if ARG_SKIP.key?(name)
599
+ "#{ARG_TYPE[kind]}#{@ctx.local_variable_get(name).inspect}"
600
+ end
601
+ end
579
602
 
580
- @trace_calls = [
581
- TracePoint.new(:c_call) do |tp|
582
- next if !@trace.key?(tp.self) || tp.path == __FILE__
583
- out = Out.new
584
- out.sig('>', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' })
585
- out.location(tp) if @caller_locations
586
- out.flush(@output)
587
- end,
588
- TracePoint.new(:call) do |tp|
603
+ ARG_TYPE = { rest: :*, keyrest: :**, block: :& }.compare_by_identity.freeze
604
+ ARG_SKIP = ARG_TYPE.invert.compare_by_identity.freeze
605
+ end
606
+ private_constant :RbCallLineStore
607
+
608
+ @trace_calls =
609
+ TracePoint.new(:c_call, :call) do |tp|
589
610
  next if !@trace.key?(tp.self) || tp.path == __FILE__
590
- ctx = tp.binding
591
- out = Out.new
592
- out.sig(
593
- '>',
594
- tp,
595
- tp.parameters.map do |kind, name|
596
- next name if NO_NAME.key?(name)
597
- "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
598
- end
599
- )
600
- out.location(ctx.eval('caller_locations(4,1)')[0]) if @caller_locations
601
- out.flush(@output)
611
+ klass = tp.event == :c_call ? CCallLineStore : RbCallLineStore
612
+ @output << klass.new('>', tp, @caller_locations).to_s
602
613
  end
603
- ]
604
614
 
605
- @trace_results = [
606
- TracePoint.new(:c_return) do |tp|
615
+ @trace_results =
616
+ TracePoint.new(:c_return, :return) do |tp|
607
617
  next if !@trace.key?(tp.self) || tp.path == __FILE__
608
- out = Out.new
609
- out.sig('<', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' })
610
- out.location(tp) if @caller_locations
618
+ klass = tp.event == :c_return ? CCallLineStore : RbCallLineStore
619
+ out = klass.new('<', tp, @caller_locations)
611
620
  out << " = #{tp.return_value.inspect}"
612
- out.flush(@output)
613
- end,
614
- TracePoint.new(:return) do |tp|
615
- next if !@trace.key?(tp.self) || tp.path == __FILE__
616
- ctx = tp.binding
617
- out = Out.new
618
- out.sig(
619
- '<',
620
- tp,
621
- tp.parameters.map do |kind, name|
622
- next name if NO_NAME.key?(name)
623
- "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
624
- end
625
- )
626
- out.location(ctx.eval('caller_locations(4,1)')[0]) if @caller_locations
627
- out << " = #{tp.return_value.inspect}"
628
- out.flush(@output)
621
+ @output << out.to_s
629
622
  end
630
- ]
631
623
 
632
- supported = RUBY_VERSION.to_f < 3.3 ? %i[raise] : %i[raise rescue]
624
+ exception_support = RUBY_VERSION.to_f < 3.3 ? %i[raise] : %i[raise rescue]
633
625
  @trace_exceptions =
634
- TracePoint.new(*supported) do |tp|
626
+ TracePoint.new(*exception_support) do |tp|
635
627
  ex = tp.raised_exception
636
628
  mark, parent = tp.event == :rescue ? ['!', ex.cause] : 'x'
637
629
  ex = ex.inspect
638
- out = Out.new("#{mark} #{ex[0] == '#' ? ex[2..-2] : ex}")
630
+ out = LineStore.new("#{mark} #{ex[0] == '#' ? ex[2..-2] : ex}")
639
631
  while parent
640
632
  ex = parent.inspect
641
633
  out << " [#{ex[0] == '#' ? ex[2..-2] : ex}]"
642
634
  parent = parent.cause
643
635
  end
644
- out.location(tp) if @exception_locations
645
- out.flush(@output)
636
+ out << " #{tp.path}:#{tp.lineno}" if @exception_locations
637
+ @output << out.to_s
646
638
  end
647
639
 
648
- @fiber_supported =
649
- !!(defined?(Fiber.current) && defined?(Fiber.current.storage))
640
+ @timer = TimerStore.new { |title, location, time| @output << <<~TIMER_MSG }
641
+ T #{title}: #{time ? "#{time} sec." : 'created'}
642
+ #{location.path}:#{location.lineno}
643
+ TIMER_MSG
644
+ TimerStore.private_class_method(:new)
645
+
646
+ @fiber_support = !!defined?(Fiber.current.storage)
650
647
 
648
+ @output = STDERR
649
+ @trace = {}.compare_by_identity
650
+ @caller_locations = @exception_locations = true
651
651
  self.trace_calls = self.trace_results = true
652
652
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: im-lost
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-06-18 00:00:00.000000000 Z
10
+ date: 2025-01-30 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |
14
13
  If you have overlooked something again and don't really understand what
@@ -19,7 +18,6 @@ description: |
19
18
  ImLost helps you by analyzing function calls of objects, informing you
20
19
  about exceptions and logging your way through your code. In short, ImLost
21
20
  is your debugging helper!
22
- email:
23
21
  executables: []
24
22
  extensions: []
25
23
  extra_rdoc_files:
@@ -42,7 +40,6 @@ metadata:
42
40
  bug_tracker_uri: https://github.com/mblumtritt/im-lost/issues
43
41
  documentation_uri: https://rubydoc.info/gems/im-lost
44
42
  rubygems_mfa_required: 'true'
45
- post_install_message:
46
43
  rdoc_options: []
47
44
  require_paths:
48
45
  - lib
@@ -57,8 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
54
  - !ruby/object:Gem::Version
58
55
  version: '0'
59
56
  requirements: []
60
- rubygems_version: 3.5.13
61
- signing_key:
57
+ rubygems_version: 3.6.3
62
58
  specification_version: 4
63
59
  summary: Your debugging helper.
64
60
  test_files: []