im-lost 1.1.0 → 1.2.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: be7b612cd1d68529fbba18b5a9a4759e60111facc52e99a20b4da140e887f8c4
4
- data.tar.gz: c1aeff67c05a2e31c0dab72c6c5ed84bb27f9231e759de874882a082f0fac284
3
+ metadata.gz: 38b1a5b729438621357238f12f64f1d397ee56e18174c2cc1ec60067dd0ab15e
4
+ data.tar.gz: 157e15bcf3c9e9539467347e9cac31b5ed40adf9953bc6ab28dab6d163907288
5
5
  SHA512:
6
- metadata.gz: 9821d580f5c5e4e62133dba01fbdd9a26ec3cfd8ffe8ed6603b24760d5073184bd22bdbc6ab95317c6a39690abc56ad4e2e5afdd5df4e3d261525afc927448eb
7
- data.tar.gz: b12a64d4ee2b7e9f1764c5bcb0e79a5f9b240b9b49359c10868abc2f763cefb8ea4a7844c0ba17239346157f516b10a6fc8d368386206cb0fea0c6cc330a7601
6
+ metadata.gz: 8ad0cc365f318ef0113d6f099618204b11ecd547a84d525893cc2691a59a5159594c8606de34bad9268867751708cf98f03102a1b0feefbab6cefa41c2436449
7
+ data.tar.gz: eb0a3abf3fffc2273593741c880c9aa5b4461d2b14877e324834216327d9ec427a80eaf2b075381df601b920477b50c8b95190c34ae9cc73fb3d1b25407aa4a1
data/README.md CHANGED
@@ -22,13 +22,13 @@ end
22
22
 
23
23
  # output will look like
24
24
  # > IO#<<(?)
25
- # /projects/test.rb:1
25
+ # /examples/test.rb:1
26
26
  # > IO#write(*)
27
- # /projects/test.rb:1
27
+ # /examples/test.rb:1
28
28
  # > IO#puts(*)
29
- # /projects/test.rb:2
29
+ # /examples/test.rb:2
30
30
  # > IO#write(*)
31
- # /projects/test.rb:2
31
+ # /examples/test.rb:2
32
32
  ```
33
33
 
34
34
  When you need to know if exceptions are raised and handled you can use `ImLost.trace_exceptions`:
@@ -42,11 +42,11 @@ end
42
42
 
43
43
  # output will look like
44
44
  # x Errno::EEXIST: File exists @ rb_sysopen - /
45
- # /projects/test.rb:2
45
+ # /examples/test.rb:2
46
46
  # ! Errno::EEXIST: File exists @ rb_sysopen - /
47
- # /projects/test.rb:3
47
+ # /examples/test.rb:3
48
48
  # x RuntimeError: something went wrong!
49
- # /projects/test.rb:4
49
+ # /examples/test.rb:4
50
50
  ```
51
51
 
52
52
  When you like to know if a code point is reached, `ImLost.here` will help:
@@ -94,7 +94,6 @@ class Foo
94
94
  def bar = :bar
95
95
  end
96
96
 
97
- ImLost.trace_results = true
98
97
  ImLost.trace(Foo)
99
98
 
100
99
  my_foo = Foo.create(value: :foo!)
@@ -111,47 +110,55 @@ ImLost.vars(my_foo)
111
110
 
112
111
  # output will look like
113
112
  # > Foo.create(:foo!)
114
- # /projects/foo.rb:25
113
+ # /examples/foo.rb:24
115
114
  # > Foo.new(*)
116
- # /projects/foo.rb:6
115
+ # /examples/foo.rb:6
117
116
  # < Foo.new(*)
118
- # = #<Foo:0x00000001030810c0 @value=:foo!>
117
+ # /examples/foo.rb:6
118
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
119
119
  # < Foo.create(:foo!)
120
- # = #<Foo:0x00000001030810c0 @value=:foo!>
120
+ # /examples/foo.rb:24
121
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
121
122
  # > Foo#foo(1, *[], :none, **{}, &nil)
122
- # /projects/foo.rb:28
123
+ # /examples/foo.rb:27
123
124
  # > Foo#bar()
124
- # /projects/foo.rb:15
125
+ # /examples/foo.rb:15
125
126
  # < Foo#bar()
127
+ # /examples/foo.rb:15
126
128
  # = :bar
127
129
  # < Foo#foo(1, *[], :none, **{}, &nil)
130
+ # /examples/foo.rb:27
128
131
  # = "1-none-[]-{}-bar"
129
- # = /projects/foo.rb:29
130
- # instance variables:
131
- # @value: "1-none-[]-{}-bar"
132
+ # * /examples/foo.rb:28
133
+ # > instance variables
134
+ # @value: "1-none-[]-{}-bar"
132
135
  # > Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
133
- # /projects/foo.rb:31
136
+ # /examples/foo.rb:30
134
137
  # > Foo#bar()
135
- # /projects/foo.rb:15
138
+ # /examples/foo.rb:15
136
139
  # < Foo#bar()
140
+ # /examples/foo.rb:15
137
141
  # = :bar
138
142
  # < Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
143
+ # /examples/foo.rb:30
139
144
  # = "2-some-[a,b,c]-{:name=>:value}-bar"
140
- # = /projects/foo.rb:32
141
- # instance variables:
142
- # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
143
- # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
144
- # /projects/foo.rb:34
145
+ # * /examples/foo.rb:31
146
+ # > instance variables
147
+ # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
148
+ # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
149
+ # /examples/foo.rb:33
145
150
  # > Foo#bar()
146
- # /projects/foo.rb:15
151
+ # /examples/foo.rb:15
147
152
  # < Foo#bar()
153
+ # /examples/foo.rb:15
148
154
  # = :bar
149
155
  # 3--[]-{}-bar
150
- # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
156
+ # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
157
+ # /examples/foo.rb:33
151
158
  # = nil
152
- # = /projects/foo.rb:35
153
- # instance variables:
154
- # @value: "3--[]-{}-bar"
159
+ # * /examples/foo.rb:34
160
+ # > instance variables
161
+ # @value: "3--[]-{}-bar"
155
162
  ```
156
163
 
157
164
  See [examples dir](./examples) for more…
data/examples/foo.rb CHANGED
@@ -19,7 +19,6 @@ class Foo
19
19
  def bar = :bar
20
20
  end
21
21
 
22
- ImLost.trace_results = true
23
22
  ImLost.trace(Foo)
24
23
 
25
24
  my_foo = Foo.create(value: :foo!)
@@ -36,44 +35,52 @@ ImLost.vars(my_foo)
36
35
 
37
36
  # output will look like
38
37
  # > Foo.create(:foo!)
39
- # /projects/foo.rb:25
38
+ # /examples/foo.rb:24
40
39
  # > Foo.new(*)
41
- # /projects/foo.rb:6
40
+ # /examples/foo.rb:6
42
41
  # < Foo.new(*)
43
- # = #<Foo:0x00000001030810c0 @value=:foo!>
42
+ # /examples/foo.rb:6
43
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
44
44
  # < Foo.create(:foo!)
45
- # = #<Foo:0x00000001030810c0 @value=:foo!>
45
+ # /examples/foo.rb:24
46
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
46
47
  # > Foo#foo(1, *[], :none, **{}, &nil)
47
- # /projects/foo.rb:28
48
+ # /examples/foo.rb:27
48
49
  # > Foo#bar()
49
- # /projects/foo.rb:15
50
+ # /examples/foo.rb:15
50
51
  # < Foo#bar()
52
+ # /examples/foo.rb:15
51
53
  # = :bar
52
54
  # < Foo#foo(1, *[], :none, **{}, &nil)
55
+ # /examples/foo.rb:27
53
56
  # = "1-none-[]-{}-bar"
54
- # = /projects/foo.rb:29
55
- # instance variables:
56
- # @value: "1-none-[]-{}-bar"
57
+ # * /examples/foo.rb:28
58
+ # > instance variables
59
+ # @value: "1-none-[]-{}-bar"
57
60
  # > Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
58
- # /projects/foo.rb:31
61
+ # /examples/foo.rb:30
59
62
  # > Foo#bar()
60
- # /projects/foo.rb:15
63
+ # /examples/foo.rb:15
61
64
  # < Foo#bar()
65
+ # /examples/foo.rb:15
62
66
  # = :bar
63
67
  # < Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
68
+ # /examples/foo.rb:30
64
69
  # = "2-some-[a,b,c]-{:name=>:value}-bar"
65
- # = /projects/foo.rb:32
66
- # instance variables:
67
- # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
68
- # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
69
- # /projects/foo.rb:34
70
+ # * /examples/foo.rb:31
71
+ # > instance variables
72
+ # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
73
+ # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
74
+ # /examples/foo.rb:33
70
75
  # > Foo#bar()
71
- # /projects/foo.rb:15
76
+ # /examples/foo.rb:15
72
77
  # < Foo#bar()
78
+ # /examples/foo.rb:15
73
79
  # = :bar
74
80
  # 3--[]-{}-bar
75
- # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
81
+ # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
82
+ # /examples/foo.rb:33
76
83
  # = nil
77
- # = /projects/foo.rb:35
78
- # instance variables:
79
- # @value: "3--[]-{}-bar"
84
+ # * /examples/foo.rb:34
85
+ # > instance variables
86
+ # @value: "3--[]-{}-bar"
@@ -9,7 +9,6 @@ INFO
9
9
 
10
10
  require 'im-lost'
11
11
 
12
- ImLost.trace_results = true
13
12
  ImLost.trace(Kernel, Object, Module, Class, self) do
14
13
  puts '=' * 79
15
14
  pp Class.new
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ImLost
4
4
  # The version number of the gem.
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
  end
data/lib/im-lost.rb CHANGED
@@ -26,9 +26,10 @@ module ImLost
26
26
 
27
27
  #
28
28
  # The output device used to write information.
29
- # This should be an `IO` device or any other object responding to `#puts`.
29
+ # This should be an `IO` device or any other object responding to `#<<`
30
+ # like a Logger.
30
31
  #
31
- # `$stderr` is configured by default.
32
+ # `STDERR` is configured by default.
32
33
  #
33
34
  # @example Write to a file
34
35
  # ImLost.output = File.new('./trace', 'w')
@@ -50,8 +51,13 @@ module ImLost
50
51
  attr_reader :output
51
52
 
52
53
  def output=(value)
53
- return @output = value if value.respond_to?(:puts)
54
- raise(ArgumentError, "invalid output device - #{value.inspect}")
54
+ return @output = value if defined?(value.<<)
55
+ raise(
56
+ NoMethodError,
57
+ "undefined method `<<' for an instance of #{
58
+ Kernel.instance_method(:class).bind(value).call
59
+ }"
60
+ )
55
61
  end
56
62
 
57
63
  #
@@ -89,11 +95,11 @@ module ImLost
89
95
  #
90
96
  # # output will look like
91
97
  # # x Errno::EEXIST: File exists @ rb_sysopen - /
92
- # # /projects/test.rb:2
98
+ # # /examples/test.rb:2
93
99
  # # ! Errno::EEXIST: File exists @ rb_sysopen - /
94
- # # /projects/test.rb:3
100
+ # # /examples/test.rb:3
95
101
  # # x RuntimeError: something went wrong!
96
- # # /projects/test.rb:4
102
+ # # /examples/test.rb:4
97
103
  #
98
104
  # @param with_locations [Boolean] wheter the locations should be included
99
105
  # into the exception trace information
@@ -113,7 +119,7 @@ module ImLost
113
119
 
114
120
  #
115
121
  # Enables/disables tracing of returned valuess of method calls.
116
- # This is disabled by default.
122
+ # This is enabled by default.
117
123
  #
118
124
  # @attribute [r] trace_results
119
125
  # @return [Boolean] whether return values will be traced
@@ -131,33 +137,33 @@ module ImLost
131
137
  #
132
138
  # Print the call location conditionally.
133
139
  #
134
- # @example simply print location
140
+ # @example Print current location
135
141
  # ImLost.here
136
142
  #
137
- # @example print location when instance variable is empty
143
+ # @example Print current location when instance variable is empty
138
144
  # ImLost.here(@name.empty?)
139
145
  #
140
- # @example print location when instance variable is nil or empty
146
+ # @example Print current location when instance variable is nil or empty
141
147
  # ImLost.here { @name.nil? || @name.empty? }
142
148
  #
143
149
  # @overload here
144
- # Prints the caller location.
150
+ # Prints the call location.
145
151
  # @return [true]
146
152
  #
147
153
  # @overload here(test)
148
- # Prints the caller location when given argument is truthy.
154
+ # Prints the call location when given argument is truthy.
149
155
  # @param test [Object]
150
156
  # @return [Object] test
151
157
  #
152
158
  # @overload here
153
- # Prints the caller location when given block returns a truthy result.
159
+ # Prints the call location when given block returns a truthy result.
154
160
  # @yield When the block returns a truthy result the location will be print
155
161
  # @yieldreturn [Object] return result
156
162
  #
157
163
  def here(test = true)
158
164
  return test if !test || (block_given? && !(test = yield))
159
165
  loc = Kernel.caller_locations(1, 1)[0]
160
- @output.puts(": #{loc.path}:#{loc.lineno}")
166
+ @output << "* #{loc.path}:#{loc.lineno}\n"
161
167
  test
162
168
  end
163
169
 
@@ -166,12 +172,12 @@ module ImLost
166
172
  #
167
173
  # The given arguments can be any object instance or module or class.
168
174
  #
169
- # @example trace method calls of an instance variable for a while
175
+ # @example Trace method calls of an instance variable for a while
170
176
  # ImLost.trace(@file)
171
177
  # # ...
172
178
  # ImLost.untrace(@file)
173
179
  #
174
- # @example temporary trace method calls
180
+ # @example Temporary trace method calls
175
181
  # File.open('test.txt', 'w') do |file|
176
182
  # ImLost.trace(file) do
177
183
  # file << 'hello '
@@ -181,17 +187,17 @@ module ImLost
181
187
  #
182
188
  # # output will look like
183
189
  # # > IO#<<(?)
184
- # # /projects/test.rb:1
190
+ # # /examples/test.rb:1
185
191
  # # > IO#write(*)
186
- # # /projects/test.rb:1
192
+ # # /examples/test.rb:1
187
193
  # # > IO#puts(*)
188
- # # /projects/test.rb:2
194
+ # # /examples/test.rb:2
189
195
  # # > IO#write(*)
190
- # # /projects/test.rb:2
196
+ # # /examples/test.rb:2
191
197
  #
192
198
  # @overload trace(*args)
193
199
  # @param args [[Object]] one or more objects to be traced
194
- # @return [[Object]] the traced object(s)
200
+ # @return [Array<Object>] the traced object(s)
195
201
  # Start tracing the given objects.
196
202
  # @see untrace
197
203
  # @see untrace_all!
@@ -209,30 +215,37 @@ module ImLost
209
215
  args.size == 1 ? _trace_b(args[0], &block) : _trace_all_b(args, &block)
210
216
  end
211
217
 
218
+ #
219
+ # Test if a given object is currently traced.
220
+ #
221
+ # @param arg [Object] object to be tested
222
+ # @return [Boolean] wheter the object is beeing traced
223
+ #
224
+ def traced?(obj) = @trace.key?(obj)
225
+
212
226
  #
213
227
  # Stop tracing objects.
214
228
  #
215
- # @example trace some objects for some code lines
216
- # traced_vars = ImLost.trace(@file, @client)
229
+ # @example Trace some objects for some code lines
230
+ # traced_obj = ImLost.trace(@file, @client)
217
231
  # # ...
218
- # ImLost.untrace(*traced_vars)
232
+ # ImLost.untrace(*traced_obj)
219
233
  #
220
234
  # @see trace
221
235
  #
222
- # @param args [[Object]] one or more objects which should not longer be
236
+ # @param args [[]Object]] one or more objects which should not longer be
223
237
  # traced
224
- # @return [[Object]] the object(s) which are not longer be traced
238
+ # @return [Array<Object>] the object(s) which are not longer be traced
225
239
  # @return [nil] when none of the objects was traced before
226
240
  #
227
241
  def untrace(*args)
228
- ret = args.filter_map { @trace.delete(_1.__id__) ? _1 : nil }
229
- args.size == 1 ? ret[0] : ret
242
+ args = args.filter_map { @trace.delete(_1) }
243
+ args.size < 2 ? args[0] : args
230
244
  end
231
245
 
232
246
  #
233
- # Stop tracing any object.
234
- # (When you are really lost and just like to stop tracing of all your
235
- # objects.)
247
+ # Stop tracing any object. When you are really lost and just like to stop
248
+ # tracing of all your objects.
236
249
  #
237
250
  # @see trace
238
251
  #
@@ -244,101 +257,147 @@ module ImLost
244
257
  end
245
258
 
246
259
  #
247
- # Inspect internal variables.
260
+ # Inspect internal variables of a given object.
261
+ #
262
+ # @note The dedictaed handling of `Fiber` is platform dependend!
263
+ #
264
+ # @example Inspect current instance variables
265
+ # @a = 22
266
+ # b = 20
267
+ # c = @a + b
268
+ # ImLost.vars(self)
269
+ # # => print value of `@a`
270
+ #
271
+ # @example Inspect local variables
272
+ # @a = 22
273
+ # b = 20
274
+ # c = @a + b
275
+ # ImLost.vars(binding)
276
+ # # => print values of `b` and 'c'
277
+ #
278
+ # @example Inspect a thread's variables
279
+ # th = Thread.new { th[:var1] += 20 }
280
+ # th[:var1] = 22
281
+ # ImLost.vars(th)
282
+ # # => print value of `var1`
283
+ # th.join
284
+ # ImLost.vars(th)
285
+ #
286
+ # @example Inspect the current fiber's storage
287
+ # Fiber[:var1] = 22
288
+ # Fiber[:var2] = 20
289
+ # Fiber[:var3] = Fiber[:var1] + Fiber[:var2]
290
+ # ImLost.vars(Fiber.current)
291
+ #
292
+ # When the given object is
293
+ #
294
+ # - a `Binding` it prints the local variables of the binding
295
+ # - a `Thread` it prints the fiber-local and thread variables
296
+ # - the current `Fiber` it prints the fibers' storage
248
297
  #
249
- # @overload vars(binding)
250
- # Inspect local variables of given Binding.
251
- # @param binding [Binding] which local variables should be print
252
- # @return [self] itself
298
+ # Be aware that only the current fiber can be inspected.
253
299
  #
254
- # @overload vars(object)
255
- # Inspect instance variables of given object.
256
- # @param object [Object] which instance variables should be print
257
- # @return [Object] the given object
300
+ # When the given object can not be inspected it prints an error message.
301
+ #
302
+ # @param object [Object] which instance variables should be print
303
+ # @return [Object] the given object
258
304
  #
259
305
  def vars(object)
260
- traced = @trace.delete(object.__id__)
261
- return _local_vars(object) if object.is_a?(Binding)
262
- return unless object.respond_to?(:instance_variables)
263
- _vars(object, Kernel.caller_locations(1, 1)[0])
306
+ out = Out.new
307
+ traced = @trace.delete(object)
308
+ return _local_vars(out, object) if Binding === object
309
+ location = Kernel.caller_locations(1, 1)[0]
310
+ out << "* #{location.path}:#{location.lineno}"
311
+ return _thread_vars(out, object) if Thread === object
312
+ return _fiber_vars(out, object) if @fiber_supported && Fiber === object
313
+ return _instance_vars(out, object) if defined?(object.instance_variables)
314
+ out << ' !!! unable to retrieve vars'
315
+ object
264
316
  ensure
265
317
  @trace[traced] = traced if traced
318
+ out.flush(@output)
266
319
  end
267
320
 
268
321
  private
269
322
 
270
- def as_sig(prefix, info, args)
271
- args = args.join(', ')
272
- case info.self
273
- when Class, Module
274
- "#{prefix} #{info.self}.#{info.method_id}(#{args})"
275
- else
276
- "#{prefix} #{info.defined_class}##{info.method_id}(#{args})"
277
- end
323
+ def _can_trace?(arg)
324
+ (id = arg.__id__) != __id__ && id != @output.__id__
278
325
  end
279
326
 
280
327
  def _trace(arg)
281
- id = arg.__id__
282
- @trace[id] = id if __id__ != id && @output.__id__ != id
328
+ @trace[arg] = arg if _can_trace?(arg)
283
329
  arg
284
330
  end
285
331
 
286
332
  def _trace_all(args)
287
- args.each do |arg|
288
- arg = arg.__id__
289
- @trace[arg] = arg if __id__ != arg && @output.__id__ != arg
290
- end
333
+ args.each { |arg| @trace[arg] = arg if _can_trace?(arg) }
291
334
  args
292
335
  end
293
336
 
294
337
  def _trace_b(arg)
295
- id = arg.__id__
296
- return yield(arg) if __id__ == id || @output.__id__ == id
338
+ return yield(arg) if @trace.key?(arg) || !_can_trace?(arg)
297
339
  begin
298
- @trace[id] = id
340
+ @trace[arg] = arg
299
341
  yield(arg)
300
342
  ensure
301
- @trace.delete(id) if id
343
+ @trace.delete(arg)
302
344
  end
303
345
  end
304
346
 
305
347
  def _trace_all_b(args)
306
- ids =
348
+ temp =
307
349
  args.filter_map do |arg|
308
- arg = arg.__id__
309
- @trace[arg] = arg if __id__ != arg && @output.__id__ != arg
350
+ @trace[arg] = arg if !@trace.key?(arg) && _can_trace?(arg)
310
351
  end
311
352
  yield(args)
312
353
  ensure
313
- ids.each { @trace.delete(_1) }
354
+ temp.each { @trace.delete(_1) }
314
355
  end
315
356
 
316
- def _vars(obj, location)
317
- @output.puts("= #{location.path}:#{location.lineno}")
318
- vars = obj.instance_variables
319
- if vars.empty?
320
- @output.puts(' <no instance variables defined>')
321
- return obj
357
+ def _local_vars(out, binding)
358
+ out << "* #{binding.source_location.join(':')}"
359
+ out.vars('local variables', binding.local_variables) do |name|
360
+ binding.local_variable_get(name)
322
361
  end
323
- @output.puts(' instance variables:')
324
- vars.sort!.each do |name|
325
- @output.puts(" #{name}: #{obj.instance_variable_get(name).inspect}")
362
+ binding
363
+ end
364
+
365
+ def _thread_vars(out, thread)
366
+ out << " #{_thread_identifier(thread)}"
367
+ flv = thread.keys
368
+ out.vars('fiber-local variables', flv) { thread[_1] } unless flv.empty?
369
+ out.vars('thread variables', thread.thread_variables) do |name|
370
+ thread.thread_variable_get(name)
326
371
  end
327
- obj
372
+ thread
328
373
  end
329
374
 
330
- def _local_vars(binding)
331
- @output.puts("= #{binding.source_location.join(':')}")
332
- vars = binding.local_variables
333
- if vars.empty?
334
- @output.puts(' <no local variables>')
335
- return self
375
+ def _fiber_vars(out, fiber)
376
+ if Fiber.current == fiber
377
+ storage = fiber.storage || {}
378
+ out.vars('fiber storage', storage.keys) { storage[_1] }
379
+ else
380
+ out << ' !!! given Fiber is not the current Fiber' <<
381
+ " #{fiber.inspect}"
336
382
  end
337
- @output.puts(' local variables:')
338
- vars.sort!.each do |name|
339
- @output.puts(" #{name}: #{binding.local_variable_get(name).inspect}")
383
+ fiber
384
+ end
385
+
386
+ def _instance_vars(out, object)
387
+ out.vars('instance variables', object.instance_variables) do |n|
388
+ object.instance_variable_get(n)
340
389
  end
341
- self
390
+ object
391
+ end
392
+
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
342
401
  end
343
402
  end
344
403
 
@@ -365,7 +424,7 @@ module ImLost
365
424
  # # the timer with name 'my_test' is not longer valid now
366
425
  #
367
426
  #
368
- # @example Use an anonymous timer (identified by ID)
427
+ # @example Use an anonymous timer
369
428
  # tmr = ImLost.timer.create
370
429
  #
371
430
  # # ...your code here...
@@ -440,7 +499,7 @@ module ImLost
440
499
  end
441
500
 
442
501
  #
443
- # Print the ID or name and the runtime since timer was created.
502
+ # Print the ID or name and the runtime since a timer was created.
444
503
  # It includes the location.
445
504
  #
446
505
  # @param id_or_name [Integer, #to_s] the identifier or the name of the timer
@@ -476,80 +535,118 @@ module ImLost
476
535
  end
477
536
  end
478
537
 
538
+ class Out
539
+ def initialize(*lines) = (@lines = lines)
540
+ def <<(str) = @lines << str
541
+ def location(loc) = @lines << " #{loc.path}:#{loc.lineno}"
542
+ def flush(dev) = dev << (@lines << nil).join("\n")
543
+
544
+ def sig(prefix, info, args)
545
+ args = args.join(', ')
546
+ @lines << case info.self
547
+ when Class, Module
548
+ "#{prefix} #{info.self}.#{info.method_id}(#{args})"
549
+ else
550
+ "#{prefix} #{info.defined_class}##{info.method_id}(#{args})"
551
+ end
552
+ end
553
+
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}" }
558
+ end
559
+ end
560
+ private_constant :Out
561
+
479
562
  ARG_SIG = { rest: '*', keyrest: '**', block: '&' }.compare_by_identity.freeze
480
563
  NO_NAME = { :* => 1, :** => 1, :& => 1 }.compare_by_identity.freeze
481
- private_constant :ARG_SIG, :NO_NAME
564
+ THREAD_STATE = {
565
+ false => 'terminated',
566
+ nil => 'aborted'
567
+ }.compare_by_identity.freeze
568
+ private_constant :ARG_SIG, :NO_NAME, :THREAD_STATE
482
569
 
483
570
  @trace = {}.compare_by_identity
484
- @caller_locations = true
485
- @output = $stderr.respond_to?(:puts) ? $stderr : STDERR
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
571
+ @caller_locations = @exception_locations = true
572
+ @output = STDERR
573
+
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
494
578
  TimerStore.private_class_method(:new)
495
579
 
496
580
  @trace_calls = [
497
581
  TracePoint.new(:c_call) do |tp|
498
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
499
- @output.puts(as_sig('>', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' }))
500
- @output.puts(" #{tp.path}:#{tp.lineno}") if @caller_locations
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)
501
587
  end,
502
588
  TracePoint.new(:call) do |tp|
503
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
589
+ next if !@trace.key?(tp.self) || tp.path == __FILE__
504
590
  ctx = tp.binding
505
- @output.puts(
506
- as_sig(
507
- '>',
508
- tp,
509
- tp.parameters.map do |kind, name|
510
- next name if NO_NAME.key?(name)
511
- "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
512
- end
513
- )
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
514
599
  )
515
- next unless @caller_locations
516
- loc = ctx.eval('caller_locations(4,1)')[0]
517
- @output.puts(" #{loc.path}:#{loc.lineno}")
600
+ out.location(ctx.eval('caller_locations(4,1)')[0]) if @caller_locations
601
+ out.flush(@output)
518
602
  end
519
603
  ]
520
604
 
521
605
  @trace_results = [
522
606
  TracePoint.new(:c_return) do |tp|
523
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
524
- @output.puts(as_sig('<', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' }))
525
- @output.puts(" = #{tp.return_value.inspect}")
607
+ 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
611
+ out << " = #{tp.return_value.inspect}"
612
+ out.flush(@output)
526
613
  end,
527
614
  TracePoint.new(:return) do |tp|
528
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
615
+ next if !@trace.key?(tp.self) || tp.path == __FILE__
529
616
  ctx = tp.binding
530
- @output.puts(
531
- as_sig(
532
- '<',
533
- tp,
534
- tp.parameters.map do |kind, name|
535
- next name if NO_NAME.key?(name)
536
- "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
537
- end
538
- )
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
539
625
  )
540
- @output.puts(" = #{tp.return_value.inspect}")
626
+ out.location(ctx.eval('caller_locations(4,1)')[0]) if @caller_locations
627
+ out << " = #{tp.return_value.inspect}"
628
+ out.flush(@output)
541
629
  end
542
630
  ]
543
631
 
544
632
  supported = RUBY_VERSION.to_f < 3.3 ? %i[raise] : %i[raise rescue]
545
633
  @trace_exceptions =
546
634
  TracePoint.new(*supported) do |tp|
547
- ex = tp.raised_exception.inspect
548
- @output.puts(
549
- "#{tp.event == :raise ? 'x' : '!'} #{ex[0] == '#' ? ex[2..-2] : ex}"
550
- )
551
- @output.puts(" #{tp.path}:#{tp.lineno}") if @exception_locations
635
+ ex = tp.raised_exception
636
+ mark, parent = tp.event == :rescue ? ['!', ex.cause] : 'x'
637
+ ex = ex.inspect
638
+ out = Out.new("#{mark} #{ex[0] == '#' ? ex[2..-2] : ex}")
639
+ while parent
640
+ ex = parent.inspect
641
+ out << " [#{ex[0] == '#' ? ex[2..-2] : ex}]"
642
+ parent = parent.cause
643
+ end
644
+ out.location(tp) if @exception_locations
645
+ out.flush(@output)
552
646
  end
553
647
 
554
- self.trace_calls = true
648
+ @fiber_supported =
649
+ !!(defined?(Fiber.current) && defined?(Fiber.current.storage))
650
+
651
+ self.trace_calls = self.trace_results = true
555
652
  end
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.1.0
4
+ version: 1.2.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-29 00:00:00.000000000 Z
11
+ date: 2024-06-18 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
@@ -57,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
57
  - !ruby/object:Gem::Version
58
58
  version: '0'
59
59
  requirements: []
60
- rubygems_version: 3.5.11
60
+ rubygems_version: 3.5.13
61
61
  signing_key:
62
62
  specification_version: 4
63
63
  summary: Your debugging helper.