im-lost 1.0.2 → 1.1.0

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: 8b3508c022462842507ce749828e2c74c4db9e9097496e68cedc758018017cb5
4
- data.tar.gz: 93ac54d98ae6474d4f7144b116cf0e2784c88babf57ff592a317b463caa7ee13
3
+ metadata.gz: be7b612cd1d68529fbba18b5a9a4759e60111facc52e99a20b4da140e887f8c4
4
+ data.tar.gz: c1aeff67c05a2e31c0dab72c6c5ed84bb27f9231e759de874882a082f0fac284
5
5
  SHA512:
6
- metadata.gz: 4d45f27c98a6ed477ce4e696f4c41286ca24c105de1668889bf64fc0d57d3947e04c026b40fa7f5f79e2ce5016fcc8327e3c9a8b20a1e6ae0561900efae4f5f3
7
- data.tar.gz: 1502147a826924c39988f9272d62e3c0274dc8cdb961f25c405a229cf6273142579ad136af1972c06874d13af110486b79db45907c219bd3082a91cd507220ab
6
+ metadata.gz: 9821d580f5c5e4e62133dba01fbdd9a26ec3cfd8ffe8ed6603b24760d5073184bd22bdbc6ab95317c6a39690abc56ad4e2e5afdd5df4e3d261525afc927448eb
7
+ data.tar.gz: b12a64d4ee2b7e9f1764c5bcb0e79a5f9b240b9b49359c10868abc2f763cefb8ea4a7844c0ba17239346157f516b10a6fc8d368386206cb0fea0c6cc330a7601
data/README.md CHANGED
@@ -19,6 +19,7 @@ File.open('test.txt', 'w') do |file|
19
19
  file.puts(:world!)
20
20
  end
21
21
  end
22
+
22
23
  # output will look like
23
24
  # > IO#<<(?)
24
25
  # /projects/test.rb:1
@@ -38,6 +39,7 @@ ImLost.trace_exceptions do
38
39
  rescue SystemCallError
39
40
  raise('something went wrong!')
40
41
  end
42
+
41
43
  # output will look like
42
44
  # x Errno::EEXIST: File exists @ rb_sysopen - /
43
45
  # /projects/test.rb:2
@@ -47,7 +49,7 @@ end
47
49
  # /projects/test.rb:4
48
50
  ```
49
51
 
50
- When you like to know if and when a code point is reached, `ImLost.here` will help:
52
+ When you like to know if a code point is reached, `ImLost.here` will help:
51
53
 
52
54
  ```ruby
53
55
  ImLost.here
data/examples/foo.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lib/im-lost'
3
+ require 'im-lost'
4
4
 
5
5
  class Foo
6
6
  def self.create(value:) = new(value)
data/examples/timer.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ puts <<~INFO
4
+
5
+ This is an example how to use named and anonymous timers.
6
+
7
+ INFO
8
+
9
+ require_relative '../lib/im-lost'
10
+
11
+ puts 'Create a named timer:'
12
+ ImLost.timer.create(:first)
13
+
14
+ puts 'Create an anonymous timer:'
15
+ second = ImLost.timer.create
16
+
17
+ sleep(0.5) # or whatever
18
+
19
+ puts 'print runtime for named timer:'
20
+ ImLost.timer[:first]
21
+
22
+ puts 'print runtime for anonymous timer:'
23
+ ImLost.timer[second]
24
+
25
+ puts 'delete a named timer'
26
+ ImLost.timer.delete(:first)
27
+
28
+ puts 'delete an anonymous timer'
29
+ ImLost.timer.delete(second)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ImLost
4
4
  # The version number of the gem.
5
- VERSION = '1.0.2'
5
+ VERSION = '1.1.0'
6
6
  end
data/lib/im-lost.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #
3
4
  # If you have overlooked something again and don't really understand what your
4
5
  # code is doing. If you have to maintain this application but can't really find
5
6
  # your way around and certainly can't track down that stupid error. If you feel
@@ -53,6 +54,12 @@ module ImLost
53
54
  raise(ArgumentError, "invalid output device - #{value.inspect}")
54
55
  end
55
56
 
57
+ #
58
+ # @return [TimerStore] the timer store used to estimate the runtime of
59
+ # your code
60
+ #
61
+ attr_reader :timer
62
+
56
63
  #
57
64
  # Enables/disables tracing of method calls.
58
65
  # This is enabled by default.
@@ -79,6 +86,7 @@ module ImLost
79
86
  # rescue SystemCallError
80
87
  # raise('something went wrong!')
81
88
  # end
89
+ #
82
90
  # # output will look like
83
91
  # # x Errno::EEXIST: File exists @ rb_sysopen - /
84
92
  # # /projects/test.rb:2
@@ -170,15 +178,16 @@ module ImLost
170
178
  # file.puts(:world!)
171
179
  # end
172
180
  # end
173
- # output will look like
174
- # > IO#<<(?)
175
- # /projects/test.rb:1
176
- # > IO#write(*)
177
- # /projects/test.rb:1
178
- # > IO#puts(*)
179
- # /projects/test.rb:2
180
- # > IO#write(*)
181
- # /projects/test.rb:2
181
+ #
182
+ # # output will look like
183
+ # # > IO#<<(?)
184
+ # # /projects/test.rb:1
185
+ # # > IO#write(*)
186
+ # # /projects/test.rb:1
187
+ # # > IO#puts(*)
188
+ # # /projects/test.rb:2
189
+ # # > IO#write(*)
190
+ # # /projects/test.rb:2
182
191
  #
183
192
  # @overload trace(*args)
184
193
  # @param args [[Object]] one or more objects to be traced
@@ -187,7 +196,6 @@ module ImLost
187
196
  # @see untrace
188
197
  # @see untrace_all!
189
198
  #
190
- #
191
199
  # @overload trace(*args)
192
200
  # @param args [[Object]] one or more objects to be traced
193
201
  # @yieldparam args [Object] the traced object(s)
@@ -257,7 +265,7 @@ module ImLost
257
265
  @trace[traced] = traced if traced
258
266
  end
259
267
 
260
- protected
268
+ private
261
269
 
262
270
  def as_sig(prefix, info, args)
263
271
  args = args.join(', ')
@@ -269,8 +277,6 @@ module ImLost
269
277
  end
270
278
  end
271
279
 
272
- private
273
-
274
280
  def _trace(arg)
275
281
  id = arg.__id__
276
282
  @trace[id] = id if __id__ != id && @output.__id__ != id
@@ -312,11 +318,11 @@ module ImLost
312
318
  vars = obj.instance_variables
313
319
  if vars.empty?
314
320
  @output.puts(' <no instance variables defined>')
315
- else
316
- @output.puts(' instance variables:')
317
- vars.sort!.each do |name|
318
- @output.puts(" #{name}: #{obj.instance_variable_get(name).inspect}")
319
- end
321
+ return obj
322
+ end
323
+ @output.puts(' instance variables:')
324
+ vars.sort!.each do |name|
325
+ @output.puts(" #{name}: #{obj.instance_variable_get(name).inspect}")
320
326
  end
321
327
  obj
322
328
  end
@@ -326,16 +332,150 @@ module ImLost
326
332
  vars = binding.local_variables
327
333
  if vars.empty?
328
334
  @output.puts(' <no local variables>')
329
- else
330
- @output.puts(' local variables:')
331
- vars.sort!.each do |name|
332
- @output.puts(" #{name}: #{binding.local_variable_get(name).inspect}")
333
- end
335
+ return self
336
+ end
337
+ @output.puts(' local variables:')
338
+ vars.sort!.each do |name|
339
+ @output.puts(" #{name}: #{binding.local_variable_get(name).inspect}")
334
340
  end
335
341
  self
336
342
  end
337
343
  end
338
344
 
345
+ #
346
+ # A store to create and register timers you can use to estimate the runtime of
347
+ # some code.
348
+ #
349
+ # All timers are identified by an unique ID or a name.
350
+ #
351
+ # @example Use a named timer
352
+ # ImLost.timer.create('my_test')
353
+ #
354
+ # # ...your code here...
355
+ #
356
+ # ImLost.timer['my_test']
357
+ # # => prints the timer name, this location and runtime so far
358
+ #
359
+ # # ...more code here...
360
+ #
361
+ # ImLost.timer['my_test']
362
+ # # => prints the timer name, this location and runtime since the timer was created
363
+ #
364
+ # ImLost.timer.delete('my_test')
365
+ # # the timer with name 'my_test' is not longer valid now
366
+ #
367
+ #
368
+ # @example Use an anonymous timer (identified by ID)
369
+ # tmr = ImLost.timer.create
370
+ #
371
+ # # ...your code here...
372
+ #
373
+ # ImLost.timer[tmr]
374
+ # # => prints the timer ID, this location and runtime so far
375
+ #
376
+ # # ...more code here...
377
+ #
378
+ # ImLost.timer[tmr]
379
+ # # => prints the timer ID, this location and runtime since the timer was created
380
+ #
381
+ # ImLost.timer.delete(tmr)
382
+ # # the timer with the ID `tmr` is not longer valid now
383
+ #
384
+ # @see ImLost.timer
385
+ #
386
+ class TimerStore
387
+ if defined?(Process::CLOCK_MONOTONIC)
388
+ # @return [Float] current time
389
+ def self.now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
390
+ else
391
+ # @return [Float] current time
392
+ def self.now = ::Time.now
393
+ end
394
+
395
+ # @attribute [r] count
396
+ # @return [Integer] the number of registered timers
397
+ def count = ids.size
398
+
399
+ # @attribute [r] empty?
400
+ # @return [Boolean] wheter the timer store is empty or not
401
+ def empty? = ids.empty?
402
+
403
+ # @attribute [r] ids
404
+ # @return [Array<Integer>] IDs of all registered timers
405
+ def ids = (@ll.keys.keep_if { _1.is_a?(Integer) })
406
+
407
+ #
408
+ # Create and register a new named or anonymous timer.
409
+ # It print the ID or name of the created timer and includes the location.
410
+ #
411
+ # @param name [#to_s] optional timer name
412
+ # @return [Integer] timer ID
413
+ #
414
+ def create(name = nil)
415
+ timer = []
416
+ @ll[id = timer.__id__] = timer
417
+ name ? @ll[name = name.to_s] = timer : name = id
418
+ @cb[name, Kernel.caller_locations(1, 1)[0]]
419
+ timer << name << self.class.now
420
+ id
421
+ end
422
+
423
+ #
424
+ # Delete and unregister timers.
425
+ #
426
+ # @param id_or_names [Array<Integer, #to_s>] the IDs or the names
427
+ # @return [nil]
428
+ #
429
+ def delete(*id_or_names)
430
+ id_or_names.flatten.each do |id|
431
+ if id.is_a?(Integer)
432
+ del = @ll.delete(id)
433
+ @ll.delete(del[0]) if del
434
+ else
435
+ del = @ll.delete(id.to_s)
436
+ @ll.delete(del.__id__) if del
437
+ end
438
+ end
439
+ nil
440
+ end
441
+
442
+ #
443
+ # Print the ID or name and the runtime since timer was created.
444
+ # It includes the location.
445
+ #
446
+ # @param id_or_name [Integer, #to_s] the identifier or the name of the timer
447
+ # @return [Integer] timer ID
448
+ # @raise [ArgumentError] when the given id or name is not a registered timer
449
+ # identifier or name
450
+ #
451
+ def [](id_or_name)
452
+ time = self.class.now
453
+ timer = @ll[id_or_name.is_a?(Integer) ? id_or_name : id_or_name.to_s]
454
+ raise(ArgumentError, "not a timer - #{id_or_name.inspect}") unless timer
455
+ @cb[timer[0], Kernel.caller_locations(1, 1)[0], time - timer[1]]
456
+ timer.__id__
457
+ end
458
+
459
+ #
460
+ # Print the ID or name and the runtime of all active timers.
461
+ # It includes the location.
462
+ #
463
+ # @return [nil]
464
+ #
465
+ def all
466
+ now = self.class.now
467
+ loc = Kernel.caller_locations(1, 1)[0]
468
+ @ll.values.uniq.reverse_each { |name, start| @cb[name, loc, now - start] }
469
+ nil
470
+ end
471
+
472
+ # @!visibility private
473
+ def initialize(&block)
474
+ @cb = block
475
+ @ll = {}
476
+ end
477
+ end
478
+
339
479
  ARG_SIG = { rest: '*', keyrest: '**', block: '&' }.compare_by_identity.freeze
340
480
  NO_NAME = { :* => 1, :** => 1, :& => 1 }.compare_by_identity.freeze
341
481
  private_constant :ARG_SIG, :NO_NAME
@@ -344,6 +484,15 @@ module ImLost
344
484
  @caller_locations = true
345
485
  @output = $stderr.respond_to?(:puts) ? $stderr : STDERR
346
486
 
487
+ @timer =
488
+ TimerStore.new do |title, location, time|
489
+ @output.puts(
490
+ "T #{title}: #{time ? "#{time} sec." : 'created'}",
491
+ " #{location.path}:#{location.lineno}"
492
+ )
493
+ end
494
+ TimerStore.private_class_method(:new)
495
+
347
496
  @trace_calls = [
348
497
  TracePoint.new(:c_call) do |tp|
349
498
  next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
@@ -383,7 +532,7 @@ module ImLost
383
532
  '<',
384
533
  tp,
385
534
  tp.parameters.map do |kind, name|
386
- next name if %i[* ** &].include?(name)
535
+ next name if NO_NAME.key?(name)
387
536
  "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
388
537
  end
389
538
  )
@@ -392,7 +541,7 @@ module ImLost
392
541
  end
393
542
  ]
394
543
 
395
- supported = RUBY_VERSION >= '3.3.0' ? %i[raise rescue] : %i[raise]
544
+ supported = RUBY_VERSION.to_f < 3.3 ? %i[raise] : %i[raise rescue]
396
545
  @trace_exceptions =
397
546
  TracePoint.new(*supported) do |tp|
398
547
  ex = tp.raised_exception.inspect
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: im-lost
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-13 00:00:00.000000000 Z
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  If you have overlooked something again and don't really understand what
@@ -30,6 +30,7 @@ files:
30
30
  - README.md
31
31
  - examples/foo.rb
32
32
  - examples/kernel_calls.rb
33
+ - examples/timer.rb
33
34
  - lib/im-lost.rb
34
35
  - lib/im-lost/version.rb
35
36
  - lib/im_lost.rb
@@ -56,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
57
  - !ruby/object:Gem::Version
57
58
  version: '0'
58
59
  requirements: []
59
- rubygems_version: 3.5.10
60
+ rubygems_version: 3.5.11
60
61
  signing_key:
61
62
  specification_version: 4
62
63
  summary: Your debugging helper.