unroller 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/unroller.rb +127 -39
- metadata +1 -1
data/lib/unroller.rb
CHANGED
@@ -99,6 +99,7 @@ class Unroller
|
|
99
99
|
|
100
100
|
def initialize(options = {})
|
101
101
|
# Defaults
|
102
|
+
@@display_style ||= :show_entire_method_body
|
102
103
|
@condition = Proc.new { true } # Only trace if this condition is true. Useful if the place where you put your trace {} statement gets called a lot and you only want it to actually trace for some of those calls.
|
103
104
|
@initial_depth = 1 # ("Call stack") depth to start at. Actually, you'll probably want this set considerably lower than the current call stack depth, so that the indentation isn't way off the screen.
|
104
105
|
@max_lines = nil # Stop tracing (permanently) after we have produced @max_lines lines of output. If you don't know where to place the trace(false) and you just want it to stop on its own after so many lines, you could use this...
|
@@ -121,6 +122,8 @@ class Unroller
|
|
121
122
|
@column_separator = ' ' + '|'.yellow.bold + ' '
|
122
123
|
@always_show_raise_events = false
|
123
124
|
@show_file_load_errors = false
|
125
|
+
@interactive = false # Set to true to make it more like an interactive debugger.
|
126
|
+
# (In the future, might add "break out of this call" option to stop watching anything until we return from the current method... etc.)
|
124
127
|
instance_variables.each do |v|
|
125
128
|
self.class.class_eval do
|
126
129
|
attr_accessor v.gsub!(/^@/, '')
|
@@ -201,14 +204,14 @@ class Unroller
|
|
201
204
|
# Also using it to store line numbers, so that we can show the entire method definition each time we process a line,
|
202
205
|
# rather than just the current line itself.
|
203
206
|
# Its members are of type Call
|
204
|
-
@internal_depth = 0 # This is the "true" depth. It is incremented/decremented for *every* call/return, even those that we're not displaying. It is necessary for the implementation of "
|
207
|
+
@internal_depth = 0 # This is the "true" depth. It is incremented/decremented for *every* call/return, even those that we're not displaying. It is necessary for the implementation of "silent_until_return_to_this_depth", to detect when we get back to interesting land.
|
205
208
|
@depth = @initial_depth # This is the user-friendly depth. It only counts calls/returns that we *display*; it does not change when we enter into a call that we're not displaying (a "hidden" call).
|
206
209
|
@output_line = ''
|
207
210
|
@column_counter = 0
|
208
211
|
@tracing = false
|
209
212
|
@files = {}
|
210
213
|
@lines_output = 0
|
211
|
-
@
|
214
|
+
@silent_until_return_to_this_depth = nil
|
212
215
|
end
|
213
216
|
|
214
217
|
def self.exclude(*args, &block)
|
@@ -259,7 +262,7 @@ class Unroller
|
|
259
262
|
#puts 'false!!!!!!!'+klass.inspect if klass.to_s == 'false'
|
260
263
|
|
261
264
|
return if ['c-call', 'c-return'].include? event unless include_c_calls
|
262
|
-
#(puts 'exclude') if @
|
265
|
+
#(puts 'exclude') if @silent_until_return_to_this_depth unless event == 'return' # Until we hit a return and can break out of this uninteresting call, we don't want to do *anything*.
|
263
266
|
#return if uninteresting_class?(klass.to_s) unless (klass == false)
|
264
267
|
|
265
268
|
if too_far?
|
@@ -270,7 +273,7 @@ class Unroller
|
|
270
273
|
|
271
274
|
case @@display_style
|
272
275
|
|
273
|
-
# To do: remove massive duplication with
|
276
|
+
# To do: remove massive duplication with the other (:concise) display style
|
274
277
|
when :show_entire_method_body
|
275
278
|
|
276
279
|
case event
|
@@ -285,7 +288,8 @@ class Unroller
|
|
285
288
|
puts
|
286
289
|
header_before_code_for_entire_method(file, line_num)
|
287
290
|
do_show_locals if show_args
|
288
|
-
|
291
|
+
ret = code_for_entire_method(file, line, klass, id, line, 0)
|
292
|
+
puts ret unless ret.nil?
|
289
293
|
puts
|
290
294
|
|
291
295
|
@lines_output += 1
|
@@ -302,14 +306,16 @@ class Unroller
|
|
302
306
|
when 'line'
|
303
307
|
unless skip_line?
|
304
308
|
# Show the state of the locals *before* executing the current line. (I might add the option to show it after instead/as well, but I don't think that would be easy...)
|
305
|
-
do_show_locals if show_locals
|
306
309
|
|
307
310
|
inside_of = @call_stack.last
|
311
|
+
#puts "inside_of=#{inside_of.inspect}"
|
308
312
|
if inside_of
|
309
313
|
unless @last_call == current_call # Without this, I was seeing two consecutive events for the exact same line. This seems to be necessary because 'if foo()' is treated as two 'line' events: one for 'foo()' and one for 'if' (?)...
|
310
314
|
puts
|
311
315
|
header_before_code_for_entire_method(file, line_num)
|
312
|
-
|
316
|
+
do_show_locals if show_locals
|
317
|
+
ret = code_for_entire_method(inside_of.file, inside_of.line_num, @klass, @id, line, -1)
|
318
|
+
puts ret unless ret.nil?
|
313
319
|
puts
|
314
320
|
end
|
315
321
|
else
|
@@ -318,10 +324,22 @@ class Unroller
|
|
318
324
|
newline
|
319
325
|
end
|
320
326
|
|
327
|
+
# Interactive debugger!
|
328
|
+
response = nil
|
329
|
+
unless @last_call == current_call
|
330
|
+
(print '(o = Step out of | s = Skip = Step over | default = Step into > '; response = $stdin.gets) if @interactive
|
331
|
+
end
|
332
|
+
if response
|
333
|
+
if response[0].chr == 'out'[0].chr
|
334
|
+
@silent_until_return_to_this_depth = @internal_depth - 1
|
335
|
+
elsif response[0].chr == 'skip'[0].chr # Step over = Ignore anything with a greater depth.
|
336
|
+
@silent_until_return_to_this_depth = @internal_depth
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
321
340
|
@last_call = current_call
|
322
341
|
@lines_output += 1
|
323
|
-
end
|
324
|
-
|
342
|
+
end # unless skip_line?
|
325
343
|
|
326
344
|
|
327
345
|
when 'return'
|
@@ -331,7 +349,7 @@ class Unroller
|
|
331
349
|
puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth-1 < 0
|
332
350
|
@depth -= 1 unless @depth == 0
|
333
351
|
#puts "-- Decreased depth to #{depth}"
|
334
|
-
returning_from = @call_stack.
|
352
|
+
returning_from = @call_stack.last
|
335
353
|
|
336
354
|
column sprintf(' ' + '/'.cyan + ' returning from'.cyan + ' ' + '%s'.cyan, returning_from && returning_from.full_name), @column_widths[0]
|
337
355
|
newline
|
@@ -340,12 +358,14 @@ class Unroller
|
|
340
358
|
end
|
341
359
|
|
342
360
|
# Did we just get out of an uninteresting call?? Are we back in interesting land again??
|
343
|
-
if @
|
344
|
-
@
|
361
|
+
if @silent_until_return_to_this_depth and
|
362
|
+
@silent_until_return_to_this_depth == @internal_depth
|
345
363
|
puts "Yay, we're back in interesting land!"
|
346
|
-
@
|
364
|
+
@silent_until_return_to_this_depth = nil
|
347
365
|
end
|
348
366
|
|
367
|
+
@call_stack.pop
|
368
|
+
|
349
369
|
|
350
370
|
|
351
371
|
when 'raise'
|
@@ -366,7 +386,7 @@ class Unroller
|
|
366
386
|
|
367
387
|
# End when :show_entire_method_body
|
368
388
|
|
369
|
-
|
389
|
+
when :concise
|
370
390
|
case event
|
371
391
|
|
372
392
|
|
@@ -416,7 +436,7 @@ class Unroller
|
|
416
436
|
puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth-1 < 0
|
417
437
|
@depth -= 1 unless @depth == 0
|
418
438
|
#puts "-- Decreased depth to #{depth}"
|
419
|
-
returning_from = @call_stack.
|
439
|
+
returning_from = @call_stack.last
|
420
440
|
|
421
441
|
code = pretty_code_for(file, line, '\\'.magenta, :green, suffix = " (returning from #{returning_from && returning_from.full_name})".green)
|
422
442
|
code = pretty_code_for(file, line, '\\'.magenta + " (returning from #{returning_from && returning_from.full_name})".green, :green) unless code =~ /return|end/
|
@@ -435,12 +455,12 @@ class Unroller
|
|
435
455
|
end
|
436
456
|
|
437
457
|
# Did we just get out of an uninteresting call?? Are we back in interesting land again??
|
438
|
-
if @
|
439
|
-
@
|
458
|
+
if @silent_until_return_to_this_depth and
|
459
|
+
@silent_until_return_to_this_depth == @internal_depth
|
440
460
|
puts "Yay, we're back in interesting land!"
|
441
|
-
@
|
461
|
+
@silent_until_return_to_this_depth = nil
|
442
462
|
end
|
443
|
-
|
463
|
+
@call_stack.pop
|
444
464
|
|
445
465
|
|
446
466
|
when 'raise'
|
@@ -538,14 +558,14 @@ protected
|
|
538
558
|
end
|
539
559
|
end
|
540
560
|
def skip_line?
|
541
|
-
@
|
561
|
+
@silent_until_return_to_this_depth or
|
542
562
|
!calling_method_in_an_interesting_class?(@klass.to_s) or
|
543
563
|
!calling_interesting_method?(@id.to_s) or
|
544
564
|
too_deep? or
|
545
565
|
!calling_interesting_line?
|
546
566
|
end
|
547
567
|
def too_deep?
|
548
|
-
# The + 1 is because if they're still at the initial depth (@depth - @initial_depth == 0), we want it treated as "depth 1"
|
568
|
+
# The + 1 is because we want it to be 1-based, for humans: if they're still at the initial depth (@depth - @initial_depth == 0), we want it treated as "depth 1".
|
549
569
|
@max_depth and (@depth - @initial_depth + 1 > @max_depth)
|
550
570
|
end
|
551
571
|
def too_far?
|
@@ -554,9 +574,9 @@ protected
|
|
554
574
|
def calling_method_in_an_interesting_class?(class_name)
|
555
575
|
!( @exclude_classes + [ClassExclusion.new(self.class.name)] ).any? do |class_exclusion|
|
556
576
|
returning(class_name =~ class_exclusion.regexp) do |is_uninteresting|
|
557
|
-
if is_uninteresting && class_exclusion.recursive? && @
|
577
|
+
if is_uninteresting && class_exclusion.recursive? && @silent_until_return_to_this_depth.nil?
|
558
578
|
puts "Turning tracing off until we get back to internal_depth #{@internal_depth} again because we're calling uninteresting #{class_name}:#{@id}"
|
559
|
-
@
|
579
|
+
@silent_until_return_to_this_depth = @internal_depth
|
560
580
|
end
|
561
581
|
end
|
562
582
|
end
|
@@ -564,9 +584,9 @@ protected
|
|
564
584
|
def calling_interesting_method?(name)
|
565
585
|
!( @exclude_methods ).any? do |regexp|
|
566
586
|
returning(name =~ regexp) do |is_uninteresting|
|
567
|
-
if is_uninteresting && (recursive = implemented = false) && @
|
587
|
+
if is_uninteresting && (recursive = implemented = false) && @silent_until_return_to_this_depth.nil?
|
568
588
|
puts "Turning tracing off until we get back to internal_depth #{@internal_depth} again because we're calling uninteresting #{@klass}:#{@id}"
|
569
|
-
@
|
589
|
+
@silent_until_return_to_this_depth = @internal_depth
|
570
590
|
end
|
571
591
|
end
|
572
592
|
end
|
@@ -610,11 +630,15 @@ protected
|
|
610
630
|
end
|
611
631
|
|
612
632
|
if width
|
613
|
-
|
633
|
+
# Handles maximum width
|
634
|
+
max_for_column = width
|
635
|
+
max_for_screen = remaining_width
|
636
|
+
if column_overflow =~ /chop_/ # The column only *has* a max with if column_overflow is chop_ (not if it's allow, obviously)
|
614
637
|
#puts "width = #{width}"
|
615
|
-
string = string.code_unroller.make_it_fit(
|
638
|
+
string = string.code_unroller.make_it_fit(max_for_column, column_overflow)
|
639
|
+
else
|
640
|
+
string = string.code_unroller.make_it_fit(max_for_screen)
|
616
641
|
end
|
617
|
-
string = string.code_unroller.make_it_fit(remaining_width) # Handles maximum width
|
618
642
|
|
619
643
|
string = string.ljust_without_color(width) # Handles minimum width
|
620
644
|
end
|
@@ -693,13 +717,36 @@ protected
|
|
693
717
|
line_num = [code_for_file.size - 1, line_num].min # :todo: We should probably just return an error if it's out of range, rather than doing this min junk...
|
694
718
|
first_line = code_for_file[line_num]
|
695
719
|
|
696
|
-
|
697
|
-
|
720
|
+
if first_line =~ /^(\s+)\S?.*def/
|
721
|
+
# About the regexp:
|
722
|
+
# It would just be as simple as /^(\s+)def/ except that we want to allow for the (admittedly rare) case where they have it
|
723
|
+
# *indented* correctly, but they have other junk at the beginning of the line. For example: ' obj.define_method :foo'.
|
724
|
+
# The ^(\s+) is to capture the initial whitespace. The \S marks the end of the whitespace (the true "beginning of the line").
|
725
|
+
# The .* is any other junk they want to throw in before we get to the good sweet def that we are looking for!
|
726
|
+
|
727
|
+
# This is the normal case (I *think*).
|
728
|
+
# $1 will be set to the whitespace at the beginning of the line.
|
729
|
+
else
|
730
|
+
# Hmm... Try plan B. Sometimes it is defined like this:
|
731
|
+
# define_method(reflection.name) do |*params|
|
732
|
+
# force_reload = params.first unless params.empty? # <-- And this is the line they claim we started on. (Liars.)
|
733
|
+
# So we have to back up one line and try again...
|
734
|
+
line_num -= 1
|
735
|
+
first_line = code_for_file[line_num]
|
736
|
+
if first_line =~ /^(\s+)\S?.*def/ # (includes define_method)
|
737
|
+
# Good
|
738
|
+
else
|
739
|
+
puts "Warning: Expected #{file}:#{line_num} =~ /def.*#{method}/, but was:" #, but it was:\n#{first_line}"
|
740
|
+
column pretty_code_for(@file, @line, ' ', :bold)
|
741
|
+
newline
|
742
|
+
return
|
743
|
+
end
|
744
|
+
end
|
698
745
|
leading_whitespace = $1
|
699
746
|
|
700
747
|
# Look for ending 'end'
|
701
748
|
lines_containing_method =
|
702
|
-
(line_num .. line_num+30).
|
749
|
+
(line_num .. [code_for_file.size - 1, line_num+30].min).
|
703
750
|
map {|i| [i, code_for_file[i]]}.
|
704
751
|
select_until(inclusive = true) do |line_num, line|
|
705
752
|
line =~ /^#{leading_whitespace}end/
|
@@ -728,7 +775,7 @@ protected
|
|
728
775
|
end # code_for_entire_method
|
729
776
|
|
730
777
|
def header_before_code_for_entire_method(file, line_num)
|
731
|
-
puts '' + "#{fully_qualified_method} (#{file}:#{line_num}):".magenta if show_filename_and_line_numbers
|
778
|
+
puts '' + "#{fully_qualified_method} (#{file}:#{line_num}) (#{@event}):".magenta if show_filename_and_line_numbers
|
732
779
|
end
|
733
780
|
end # class Unroller
|
734
781
|
|
@@ -739,13 +786,15 @@ class String
|
|
739
786
|
def make_it_fit(max_width, overflow = :chop_right)
|
740
787
|
with(string = self) do
|
741
788
|
if string.length_without_color > max_width # Wider than desired column width; Needs to be chopped.
|
742
|
-
unless max_width < 4 # Is there even enough room for it if it *is* chopped?
|
789
|
+
unless max_width < 4 # Is there even enough room for it if it *is* chopped? If not, then don't even bother.
|
790
|
+
#Kernel.p overflow
|
743
791
|
if overflow == :chop_left
|
744
|
-
#puts "making string (#{string.length_without_color}) fit within #{max_width}"
|
792
|
+
#puts "making string (#{string.length_without_color}) fit within #{max_width} by doing a :chop_left"
|
745
793
|
#puts "chopping '#{string}' at -(#{max_width} - 3) .. -1!"
|
746
794
|
chopped_part = string[-(max_width - 3) .. -1]
|
747
795
|
string.replace '...' + chopped_part
|
748
796
|
elsif overflow == :chop_right
|
797
|
+
#puts "making string (#{string.length_without_color}) fit within #{max_width} by doing a :chop_right"
|
749
798
|
chopped_part = string[0 .. (max_width - 3)]
|
750
799
|
string.replace chopped_part + '...'
|
751
800
|
end
|
@@ -793,6 +842,9 @@ end
|
|
793
842
|
# |_|\___||___/\__|
|
794
843
|
#
|
795
844
|
# :todo: These tests seriously need to be converted into automated tests. But I was lazy/in a hurry when I wrote this...
|
845
|
+
# It would be kind of cool if we could simultaneously *capture* the output (for asserting things against in automated tests) and
|
846
|
+
# *display* the output (so that we can manually check for *visual*/stylistic/color regression problems that automated tests
|
847
|
+
# couldn't really judge ... and because it's just pretty to look at :) ).
|
796
848
|
|
797
849
|
if $0 == __FILE__
|
798
850
|
require 'test/unit'
|
@@ -801,16 +853,14 @@ if $0 == __FILE__
|
|
801
853
|
puts message.ljust(130).bold.white.on_magenta
|
802
854
|
end
|
803
855
|
|
804
|
-
Unroller::display_style =
|
856
|
+
Unroller::display_style = :concise #:show_entire_method_body
|
805
857
|
|
806
858
|
class TheTest < Test::Unit::TestCase
|
807
859
|
def test_1
|
808
860
|
end
|
809
|
-
|
810
861
|
end
|
811
862
|
|
812
863
|
|
813
|
-
|
814
864
|
herald '-----------------------------------------------------------'
|
815
865
|
herald 'Can call trace_off even if not tracing'
|
816
866
|
herald '(Should see no output)'
|
@@ -1231,14 +1281,52 @@ if $0 == __FILE__
|
|
1231
1281
|
end
|
1232
1282
|
|
1233
1283
|
herald '-----------------------------------------------------------'
|
1234
|
-
herald 'Testing :
|
1235
|
-
def bagel
|
1284
|
+
herald 'Testing :show_filename_and_line_numbers => false'
|
1285
|
+
'foo'; def bagel
|
1236
1286
|
'...'
|
1237
1287
|
end
|
1238
1288
|
Unroller::trace :display_style => :show_entire_method_body, :show_filename_and_line_numbers => false do
|
1239
1289
|
bagel
|
1240
1290
|
end
|
1241
1291
|
|
1292
|
+
herald '-----------------------------------------------------------'
|
1293
|
+
herald 'Testing that it finds the definition block even when we use define_method instead of the normal def'
|
1294
|
+
(class << self; self; end).send :define_method, :big_ol_gaggle do
|
1295
|
+
'...'
|
1296
|
+
end
|
1297
|
+
Unroller::trace :display_style => :show_entire_method_body do
|
1298
|
+
big_ol_gaggle
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
herald '-----------------------------------------------------------'
|
1302
|
+
herald 'Testing :interactive => true / interactive debugger'
|
1303
|
+
def subway
|
1304
|
+
question = 'What kind of bread would you like?'
|
1305
|
+
bread!
|
1306
|
+
question = 'Would you like any cheese?'
|
1307
|
+
cheese!
|
1308
|
+
question = 'Would you like pickles on that?'
|
1309
|
+
pickles!
|
1310
|
+
end
|
1311
|
+
def bread!
|
1312
|
+
_do = nil
|
1313
|
+
_do = nil
|
1314
|
+
_do = nil
|
1315
|
+
end
|
1316
|
+
def cheese!
|
1317
|
+
_do = nil
|
1318
|
+
_do = nil
|
1319
|
+
_do = nil
|
1320
|
+
end
|
1321
|
+
def pickles!
|
1322
|
+
_do = nil
|
1323
|
+
_do = nil
|
1324
|
+
_do = nil
|
1325
|
+
end
|
1326
|
+
Unroller::trace :display_style => :show_entire_method_body, :interactive => true do
|
1327
|
+
subway
|
1328
|
+
end
|
1329
|
+
|
1242
1330
|
herald 'End of non-automated tests'
|
1243
1331
|
herald '-----------------------------------------------------------'
|
1244
1332
|
|
metadata
CHANGED