unroller 0.0.17 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/unroller.rb +127 -39
  2. 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 "excluding_calls_made_within_unintersting_call", to detect when we get back to interesting land.
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
- @excluding_calls_made_within_unintersting_call = nil
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 @excluding_calls_made_within_unintersting_call unless event == 'return' # Until we hit a return and can break out of this uninteresting call, we don't want to do *anything*.
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 default display style
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
- puts code_for_entire_method(file, line, @klass, @id, line, 0)
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
- puts code_for_entire_method(inside_of.file, inside_of.line_num, @klass, @id, line, -1)
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.pop
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 @excluding_calls_made_within_unintersting_call and
344
- @excluding_calls_made_within_unintersting_call == @internal_depth
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
- @excluding_calls_made_within_unintersting_call = nil
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
- else
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.pop
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 @excluding_calls_made_within_unintersting_call and
439
- @excluding_calls_made_within_unintersting_call == @internal_depth
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
- @excluding_calls_made_within_unintersting_call = nil
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
- @excluding_calls_made_within_unintersting_call or
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" (1-based, for humans).
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? && @excluding_calls_made_within_unintersting_call.nil?
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
- @excluding_calls_made_within_unintersting_call = @internal_depth
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) && @excluding_calls_made_within_unintersting_call.nil?
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
- @excluding_calls_made_within_unintersting_call = @internal_depth
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
- if column_overflow =~ /chop_/
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(width, column_overflow)
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
- raise "Expected #{file}:#{line_num} to match /def.*#{method}/, but it was:\n#{first_line}" unless first_line =~ /(\s+)def/
697
- first_line =~ /(\s+)def/ # Normal case (I *think*)
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 = nil #:show_entire_method_body
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 :display_style => :show_entire_method_body, :show_filename_and_line_numbers => false'
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
@@ -3,7 +3,7 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: unroller
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.17
6
+ version: 0.0.18
7
7
  date: 2007-06-05 00:00:00 -07:00
8
8
  summary: A tool for generating human-readable "execution traces"
9
9
  require_paths: