ClassThatCluttersUpOnesTraces NewClass Interesting Unroller Foo TheTest Wasabi String Test::Unit::TestCase Dependencies ActiveSupport Uninteresting Gem Kernel ActiveMongoose dot/f_2.png
Methods
Classes and Modules
Class Unroller::ClassExclusion
Class Unroller::Variables
Constants
Call = Struct.new(:file, :line_num, :klass, :name, :full_name)
Attributes
[RW] depth
display_style
[R] tracing
Public Class methods
debug(options = {}, &block)

Helper classes

     # File lib/unroller.rb, line 129
129:   def self.debug(options = {}, &block)
130:     options.reverse_merge! :interactive => true, :display_style => :show_entire_method_body
131:     self.trace options, &block
132:   end
exclude(*args, &block)
     # File lib/unroller.rb, line 299
299:   def self.exclude(*args, &block)
300:     @@instance.exclude(*args, &block) unless @@instance.nil?
301:   end
new(options = {})
     # File lib/unroller.rb, line 152
152:   def initialize(options = {})
153:     # Defaults
154:     @@display_style ||= :show_entire_method_body
155:     @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.
156:     @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.
157:     @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...
158:     @max_depth = nil                # Don't trace anything when the depth is greater than this threshold. (This is *relative* to the starting depth, so whatever level you start at is considered depth "1".)
159:     @line_matches = nil             # The source code for that line matches this regular expression
160:     @presets = []
161:     @file_match = /./
162:     @exclude_classes = []
163:     @include_classes = []           # These will override classes that have been excluded via exclude_classes. So if you both exclude and include a class, it will be included.
164:     @exclude_methods = []
165:     @include_methods = []
166:     @show_args   = true
167:     @show_locals = false
168:     @show_filename_and_line_numbers = true
169:     @include_c_calls = false        # Might be useful if you're debugging your own C extension. Otherwise, we probably don't care about C-calls because we can't see the source for them anyway...
170:     @strip_comments = true          # :todo:
171:     @use_full_path = false          # :todo:
172:     @screen_width = 150
173:     @column_widths = [70]
174:     @indent_step =       ' ' + '|'.magenta + ' '
175:     @column_separator = '  ' + '|'.yellow.bold + ' '
176:     @always_show_raise_events = false
177:     @show_file_load_errors = false
178:     @interactive = false   # Set to true to make it more like an interactive debugger.
179:     @show_menu = true      # Set to false if you don't need the hints.
180:       # (In the future, might add "break out of this call" option to stop watching anything until we return from the current method... etc.)
181:     instance_variables.each do |v|
182:       self.class.class_eval do
183:         attr_accessor v.gsub!(/^@/, '')
184:       end
185:     end
186: 
187:     # "Presets"
188:     # Experimental -- subject to change a lot before it's finalized
189:     options[:presets]       = options.delete(:debugging)       if options.has_key?(:debugging)
190:     options[:presets]       = options.delete(:preset)          if options.has_key?(:preset)
191:     options[:presets] = [options[:presets]] unless options[:presets].is_a?(Array)
192:     [:rails, :dependencies].each do |preset|
193:       if options.has_key?(preset) || options[:presets].include?(preset)
194:         options.delete(preset)
195:         case preset
196:         when :dependencies    # Debugging ActiveSupport::Dependencies
197:           @exclude_classes.concat [
198:             /Gem/
199:           ].map {|e| ClassExclusion.new(e) }
200:         when :rails
201:           @exclude_classes.concat [
202:             /Benchmark/,
203:             /Gem/,
204:             /Dependencies/,
205:             /Logger/,
206:             /MonitorMixin/,
207:             /Set/,
208:             /HashWithIndifferentAccess/,
209:             /ERB/,
210:             /ActiveRecord/,
211:             /SQLite3/,
212:             /Class/,
213:             /ActiveSupport/,
214:             /ActiveSupport::Deprecation/,
215:             /Pathname/,
216:             /Object/,
217:             /Symbol/,
218:             /Kernel/,
219:             /Inflector/,
220:             /Webrick/
221:           ].map {|e| ClassExclusion.new(e) }
222:         end
223:       end
224:     end
225: 
226:     #-----------------------------------------------------------------------------------------------
227:     # Options
228: 
229:     # Aliases
230:     options[:max_lines]       = options.delete(:head)       if options.has_key?(:head)
231:     options[:condition]       = options.delete(:if)         if options.has_key?(:if)
232:     options[:initial_depth]   = options.delete(:depth)      if options.has_key?(:depth)
233:     options[:initial_depth]   = caller(0).size              if options[:initial_depth] == :use_call_stack_depth
234:     options[:file_match]      = options.delete(:file)       if options.has_key?(:file)
235:     options[:file_match]      = options.delete(:path)       if options.has_key?(:path)
236:     options[:file_match]      = options.delete(:path_match) if options.has_key?(:path_match)
237:     options[:dir_match]       = options.delete(:dir)        if options.has_key?(:dir)
238:     options[:dir_match]       = options.delete(:dir_match)  if options.has_key?(:dir_match)
239: 
240:     if (a = options.delete(:dir_match))
241:       unless a.is_a?(Regexp)
242:         if a =~ /.*\.rb/
243:           # They probably passed in __FILE__ and wanted us to File.expand_path(File.dirname()) it for them (and who can blame them? that's a lot of junk to type!!)
244:           a = File.expand_path(File.dirname(a))
245:         end
246:         a = /^#{Regexp.escape(a)}/ # Must start with that entire directory path
247:       end
248:       options[:file_match] = a
249:     end
250:     if (a = options.delete(:file_match))
251:       # Coerce it into a Regexp
252:       unless a.is_a?(Regexp)
253:         a = /#{Regexp.escape(a)}/ 
254:       end
255:       options[:file_match] = a
256:     end
257: 
258:     if options.has_key?(:exclude_classes)
259:       # Coerce it into an array of ClassExclusions
260:       a = options.delete(:exclude_classes)
261:       a = [a] unless a.is_a?(Array)
262:       a.map! {|e| e = ClassExclusion.new(e) unless e.is_a?(ClassExclusion); e }
263:       @exclude_classes.concat a
264:     end
265:     if options.has_key?(:include_classes)
266:       # :todo:
267:     end
268:     if options.has_key?(:exclude_methods)
269:       # Coerce it into an array of Regexp's
270:       a = options.delete(:exclude_methods)
271:       a = [a] unless a.is_a?(Array)
272:       a.map! {|e| e = /^#{e}$/ unless e.is_a?(Regexp); e }
273:       @exclude_methods.concat a
274:     end
275:     options[:line_matches]       = options.delete(:line_matches)       if options.has_key?(:line_matches)
276:     populate(options)
277: 
278:     #-----------------------------------------------------------------------------------------------
279:     # Private
280:     @call_stack = []        # Used to keep track of what method we're currently in so that when we hit a 'return' event we can display something useful.
281:                             # This is useful for two reasons:
282:                             # 1. Sometimes (and I don't know why), the code that gets shown for a 'return' event doesn't even look
283:                             #    like it has anything to do with a return... Having the call stack lets us intelligently say 'returning from ...'
284:                             # 2. If we've been stuck in this method for a long time and we're really deep, the user has probably forgotten by now which method we are returning from
285:                             #    (the filename may give some clue, but not enough), so this is likely to be a welcome reminder a lot of the time.
286:                             # Also using it to store line numbers, so that we can show the entire method definition each time we process a line,
287:                             # rather than just the current line itself.
288:                             # Its members are of type Call
289:     @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.
290:     @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).
291:     @output_line = ''
292:     @column_counter = 0
293:     @tracing = false
294:     @files = {}
295:     @lines_output = 0
296:     @silent_until_return_to_this_depth = nil
297:   end
suspend(*args, &block)
     # File lib/unroller.rb, line 302
302:   def self.suspend(*args, &block)
303:     @@instance.exclude(*args, &block) unless @@instance.nil?
304:   end
trace(options = {}, &block)
     # File lib/unroller.rb, line 134
134:   def self.trace(options = {}, &block)
135:     if @@instance and @@instance.tracing
136:       # In case they try to turn tracing on when it's already on... Assume it was an accident and don't do anything.
137:       #puts "@@instance.tracing=#{@@instance.tracing}"
138:       #return if @@instance and @@instance.tracing       
139:       #yield if block_given?
140:     else
141:       self.display_style = options.delete(:display_style) if options.has_key?(:display_style)
142:       @@instance = Unroller.new(options)
143:     end
144:     @@instance.trace &block
145:   end
trace_off()
     # File lib/unroller.rb, line 643
643:   def self.trace_off
644:     if @@instance and @@instance.tracing
645:       @@instance.trace_off
646:     end
647:   end
watch_for_added_methods(mod, filter = //) {|if block_given?| ...}
     # File lib/unroller.rb, line 654
654:   def self.watch_for_added_methods(mod, filter = //, &block)
655:     mod.singleton_class.instance_eval do
656:       define_method :method_added_with_watch_for_added_methods do |name, *args|
657:         if name.to_s =~ filter
658:           puts "Method '#{name}' was defined at #{caller[0]}"
659:         end
660:       end
661:       alias_method_chain :method_added, :watch_for_added_methods, :create_target => true
662:     end
663: 
664:     yield if block_given?
665: 
666: #    mod.class.instance_eval do
667: #      alias_method :method_added, :method_added_without_watch_for_added_methods
668: #    end
669:   end
Public Instance methods
exclude() {|| ...}
     # File lib/unroller.rb, line 305
305:   def exclude(&block)
306:     old_tracing = @tracing
307:     (trace_off; puts 'Suspending tracing')
308:     yield
309:     (trace; puts 'Resuming tracing') if old_tracing
310:   end
trace() {|if block_given?| ...}
This method is also aliased as trace_on
     # File lib/unroller.rb, line 312
312:   def trace(&block)
313:     if @tracing
314:       yield if block_given?
315:       return
316:     end
317: 
318:     begin
319:       @tracing = true
320: 
321: 
322:       if @condition.call
323: 
324:         trap_chain("INT") { set_trace_func(nil) }
325: 
326: 
327: 
328: 
329: 
330: 
331: 
332: 
333: 
334:         # (This is the meat of the library right here, so let's set it off with at least 5 blank lines.)
335:         set_trace_func( proc do |event, file, line, id, binding, klass|
336:           begin # begin/rescue block
337:             @event, @file, @line, @id, @binding, @klass =
338:               event, file, line, id, binding, klass
339:             line_num = line
340:             current_call = Call.new(file, line, klass, id, fully_qualified_method)
341: 
342:             # Sometimes klass is false and id is nil. Not sure why, but that's the way it is.
343:             #printf "- (event=%8s) (klass=%10s) (id=%10s) (%s:%-2d)\n", event, klass, id, file, line #if klass.to_s == 'false'
344:             #puts 'false!!!!!!!'+klass.inspect if klass.to_s == 'false'
345: 
346:             return if ['c-call', 'c-return'].include?(event) unless include_c_calls
347:             #(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*.
348:             #return if uninteresting_class?(klass.to_s) unless (klass == false)
349: 
350:             if @only_makes_sense_if_next_event_is_call
351:               if event == 'call'
352:                 @only_makes_sense_if_next_event_is_call = nil
353:               else
354:                 # Cancel @silent_until_return_to_this_depth because it didn't make sense / wasn't necessary. They could have 
355:                 # ("should have") simply done a 'step into', because unless it's a 'call', there's nothing to step over anyway...
356:                 # Not only is it unnecessary, but it would cause confusing behavior unless we cancelled this. As in, it
357:                 # wouldn't show *any* tracing for the remainder of the method, because it's kind of looking for a "return"...
358:                 #puts "Cancelling @silent_until_return_to_this_depth..."
359:                 @silent_until_return_to_this_depth = nil
360:               end
361:             end
362: 
363:             if too_far?
364:               puts "We've read #{@max_lines} (@max_lines) lines now. Turning off tracing..."
365:               trace_off
366:               return
367:             end
368: 
369:             case @@display_style
370: 
371:             # To do: remove massive duplication with the other (:concise) display style
372:             when :show_entire_method_body
373: 
374:               case event
375: 
376: 
377:                 #zzz
378:                 when 'call'
379:                   unless skip_line?
380:                     depth_debug = '' #" (internal_depth about to be #{@internal_depth+1})"
381:                     column sprintf(' ' + '\\'.cyan + ' calling'.cyan + ' ' + '%s'.underline.cyan + depth_debug, 
382:                                    fully_qualified_method), @column_widths[0]
383:                     newline
384: 
385:                     #puts
386:                     #header_before_code_for_entire_method(file, line_num)
387:                     #do_show_locals if show_args
388:                     #ret = code_for_entire_method(file, line, klass, id, line, 0)
389:                     #puts ret unless ret.nil?
390:                     #puts
391: 
392:                     @lines_output += 1
393: 
394: 
395:                     @depth += 1
396:                   end
397: 
398:                   @call_stack.push current_call
399:                   @internal_depth += 1
400:                   #puts "(@internal_depth+=1 ... #{@internal_depth})"
401: 
402: 
403:                 when 'class'
404:                 when 'end'
405:                 when 'line'
406:                   unless skip_line?
407:                     # 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...)
408: 
409:                     inside_of = @call_stack.last
410:                     #puts "inside_of=#{inside_of.inspect}"
411:                     if inside_of
412:                       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' (?)...
413:                         puts
414:                         header_before_code_for_entire_method(file, line_num)
415:                         do_show_locals if true #show_locals
416:                         ret = code_for_entire_method(inside_of.file, inside_of.line_num, @klass, @id, line, -1)
417:                         puts ret unless ret.nil?
418:                         puts
419:                       end
420:                     else
421:                       column pretty_code_for(file, line, ' ', :bold), @column_widths[0]
422:                       file_column file, line
423:                       newline
424:                     end
425: 
426:                     # Interactive debugger!
427:                     response = nil
428:                     if @interactive && !(@last_call == current_call)
429:                       #(print '(o = Step out of | s = Skip = Step over | default = Step into > '; response = $stdin.gets) if @interactive
430: 
431:                       while response.nil? do
432:                         print "Debugger (" +
433:                           "Step out".menu_item(:red, 'u') + ' | ' +
434:                           "Step over (or Enter key)".menu_item(:cyan, 'v') + ' | ' +
435:                           "Step into (Space)".menu_item(:green, 'i') + ' | ' + 
436:                           "show Locals".menu_item(:yellow, 'l') + ' | ' + 
437:                           "Run".menu_item(:blue) + 
438:                           ') > '
439:                         response = $stdin.getc.chr.downcase
440:                         puts unless response == "\n"
441: 
442:                         case response
443:                         when 'l'
444:                           do_show_locals_verbosely
445:                           response = nil
446:                         end
447:                       end
448:                     end
449:                     if response
450:                       case response
451:                       when 'r' # Run
452:                         @interactive = false
453:                       when 'u' # Step out
454:                         @silent_until_return_to_this_depth = @internal_depth - 1
455:                         #puts "Setting @silent_until_return_to_this_depth = #{@silent_until_return_to_this_depth}"
456:                       when 'v', "\n"  # Step over = Ignore anything with a greater depth.
457:                         @only_makes_sense_if_next_event_is_call = true
458:                         @silent_until_return_to_this_depth = @internal_depth
459:                       else
460:                         # 'i', ' ', or any other key will simply do the default, which is to keep right on tracing...
461:                       end
462:                     end
463: 
464:                     @last_call = current_call
465:                     @lines_output += 1
466:                   end # unless skip_line?
467: 
468: 
469:                 when 'return'
470: 
471:                   @internal_depth -= 1
472:                   #puts "(@internal_depth-=1 ... #{@internal_depth})"
473: 
474:                   # Did we just get out of an uninteresting call?? Are we back in interesting land again??
475:                   if  @silent_until_return_to_this_depth and 
476:                       @silent_until_return_to_this_depth == @internal_depth
477:                     #puts "Yay, we're back in interesting land! (@internal_depth = #{@internal_depth})"
478:                     @silent_until_return_to_this_depth = nil
479:                   end
480: 
481: 
482:                   unless skip_line?
483:                     puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth-1 < 0
484:                     @depth -= 1 unless @depth == 0
485:                     #puts "-- Decreased depth to #{depth}"
486:                     returning_from = @call_stack.last
487: 
488:                     depth_debug = '' #" (internal_depth was #{@internal_depth+1})"
489:                     column sprintf(' ' + '/'.cyan + ' returning from'.cyan + ' ' + '%s'.cyan + depth_debug,
490:                                    returning_from && returning_from.full_name), @column_widths[0]
491:                     newline
492: 
493:                     @lines_output += 1
494:                   end
495: 
496:                   @call_stack.pop
497: 
498: 
499:                 when 'raise'
500:                   if !skip_line? or @always_show_raise_events
501:                     # We probably always want to see these (?)... Never skip displaying them, even if we are "too deep".
502:                     column "Raising an error (#{$!}) from #{klass}".red.bold, @column_widths[0]
503:                     newline
504: 
505:                     column pretty_code_for(file, line, ' ').red, @column_widths[0]
506:                     file_column file, line
507:                     newline
508:                   end
509: 
510:                 else
511:                   column sprintf("- (%8s) %10s %10s (%s:%-2d)", event, klass, id, file, line)
512:                   newline
513:               end # case event
514: 
515:             # End when :show_entire_method_body
516: 
517:             when :concise
518:               case event
519: 
520: 
521:                 when 'call'
522:                   unless skip_line?
523:                     # :todo: use # instead of :: if klass.constantize.instance_methods.include?(id)
524:                     column sprintf(' ' + '+'.cyan + ' calling'.cyan + ' ' + '%s'.underline.cyan, fully_qualified_method), @column_widths[0]
525:                     newline
526: 
527:                     column pretty_code_for(file, line, '/'.magenta, :green), @column_widths[0]
528:                     file_column file, line
529:                     newline
530: 
531:                     @lines_output += 1
532: 
533:                     @call_stack.push Call.new(file, line, klass, id, fully_qualified_method)
534: 
535:                     @depth += 1
536:                     #puts "++ Increased depth to #{depth}"
537: 
538:                     # The locals at this point will be simply be the arguments that were passed in to this method.
539:                     do_show_locals if show_args
540:                   end
541:                   @internal_depth += 1
542: 
543: 
544:                 when 'class'
545:                 when 'end'
546:                 when 'line'
547:                   unless skip_line?
548:                     # 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...)
549:                     do_show_locals if show_locals
550: 
551:                     column pretty_code_for(file, line, ' ', :bold), @column_widths[0]
552:                     file_column file, line
553:                     newline
554: 
555:                     @lines_output += 1
556:                   end
557: 
558: 
559: 
560:                 when 'return'
561: 
562:                   @internal_depth -= 1
563:                   unless skip_line?
564:                     puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth-1 < 0
565:                     @depth -= 1 unless @depth == 0
566:                     #puts "-- Decreased depth to #{depth}"
567:                     returning_from = @call_stack.last
568: 
569:                     code = pretty_code_for(file, line, '\\'.magenta, :green, suffix = " (returning from #{returning_from && returning_from.full_name})".green)
570:                     code = pretty_code_for(file, line, '\\'.magenta + " (returning from #{returning_from && returning_from.full_name})".green, :green) unless code =~ /return|end/
571:                       # I've seen some really weird statements listed as "return" statements.
572:                       # I'm not really sure *why* it thinks these are returns, but let's at least identify those lines for the user. Examples:
573:                       # * must_be_open!
574:                       # * @db = db
575:                       # * stmt = @statement_factory.new( self, sql )
576:                       # I think some of the time this happens it might be because people pass the wrong line number to eval (__LINE__ instead of __LINE__ + 1, for example), so the line number is just not accurate.
577:                       # But I don't know if that explains all such cases or not...
578:                     column code, @column_widths[0]
579:                     file_column file, line
580:                     newline
581: 
582:                     @lines_output += 1
583:                   end
584: 
585:                   # Did we just get out of an uninteresting call?? Are we back in interesting land again??
586:                   if  @silent_until_return_to_this_depth and 
587:                       @silent_until_return_to_this_depth == @internal_depth
588:                     puts "Yay, we're back in interesting land!"
589:                     @silent_until_return_to_this_depth = nil
590:                   end
591:                   @call_stack.pop
592: 
593: 
594:                 when 'raise'
595:                   if !skip_line? or @always_show_raise_events
596:                     # We probably always want to see these (?)... Never skip displaying them, even if we are "too deep".
597:                     column "Raising an error (#{$!}) from #{klass}".red.bold, @column_widths[0]
598:                     newline
599: 
600:                     column pretty_code_for(file, line, ' ').red, @column_widths[0]
601:                     file_column file, line
602:                     newline
603:                   end
604: 
605:                 else
606:                   column sprintf("- (%8s) %10s %10s (%s:%-2d)", event, klass, id, file, line)
607:                   newline
608:               end # case event
609: 
610:             # End default display style
611:   
612:             end # case @@display_style
613: 
614: 
615: 
616:           rescue Exception => exception
617:             puts exception.inspect
618:             raise
619:           end # begin/rescue block
620:         end) # set_trace_func
621: 
622: 
623: 
624: 
625: 
626: 
627: 
628: 
629:       end # if @condition.call
630: 
631:       if block_given?
632:         yield
633:       end
634: 
635:     ensure
636:       trace_off if block_given?
637:     end # rescue/ensure block
638:   end
trace_off()
     # File lib/unroller.rb, line 648
648:   def trace_off
649:     @tracing = false
650:     set_trace_func(nil)
651:   end
trace_on(&block)

Alias for trace

Protected Instance methods
calling_interesting_line?()
     # File lib/unroller.rb, line 722
722:   def calling_interesting_line?
723:     path = File.expand_path(@file)  # rescue @file
724:     #puts "Checking #{path} !~ #{@file_match}"
725:     return false if path !~ @file_match
726:     return true if @line_matches.nil?   # No filter to apply
727:     line = code_for(@file, @line) or return false
728:     (line =~ @line_matches)
729:   end
calling_interesting_method?(name)
     # File lib/unroller.rb, line 712
712:   def calling_interesting_method?(name)
713:     !( @exclude_methods ).any? do |regexp|
714:       returning(name =~ regexp) do |is_uninteresting|
715:         if is_uninteresting && (recursive = implemented = false) && @silent_until_return_to_this_depth.nil?
716:           puts "Turning tracing off until we get back to internal_depth #{@internal_depth} again because we're calling uninteresting #{@klass}:#{@id}"
717:           @silent_until_return_to_this_depth = @internal_depth
718:         end
719:       end
720:     end
721:   end
calling_method_in_an_interesting_class?(class_name)
     # File lib/unroller.rb, line 702
702:   def calling_method_in_an_interesting_class?(class_name)
703:     !( @exclude_classes + [ClassExclusion.new(self.class.name)] ).any? do |class_exclusion|
704:       returning(class_name =~ class_exclusion.regexp) do |is_uninteresting|
705:         if is_uninteresting && class_exclusion.recursive? && @silent_until_return_to_this_depth.nil?
706:           puts "Turning tracing off until we get back to internal_depth #{@internal_depth} again because we're calling uninteresting #{class_name}:#{@id}"
707:           @silent_until_return_to_this_depth = @internal_depth
708:         end
709:       end
710:     end
711:   end
code_for(file, line_num)
     # File lib/unroller.rb, line 817
817:   def code_for(file, line_num)
818:     code_for_file = code_for_file(file)
819:     return nil if code_for_file.nil?
820: 
821:     begin
822:       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...
823:       line = code_for_file[line_num].strip
824:     rescue Exception => exception
825:       puts "Error while getting code for #{file}:#{line_num}:"
826:       puts exception.inspect
827:     end
828:   end
code_for_entire_method(file, line_num, klass, method, line_to_highlight = nil, indent_adjustment = 0)
     # File lib/unroller.rb, line 841
841:   def code_for_entire_method(file, line_num, klass, method, line_to_highlight = nil, indent_adjustment = 0)
842:     code_for_file = code_for_file(file)
843:     return nil if code_for_file.nil?
844: 
845:     output = ''
846:     begin
847: 
848:       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...
849:       first_line = code_for_file[line_num]
850: 
851:       if first_line =~ /^(\s+)\S?.*def/
852:         # About the regexp:
853:         #   It would just be as simple as /^(\s+)def/ except that we want to allow for the (admittedly rare) case where they have it
854:         #   *indented* correctly, but they have other junk at the beginning of the line. For example: '  obj.define_method :foo'.
855:         #   The ^(\s+) is to capture the initial whitespace. The \S marks the end of the whitespace (the true "beginning of the line").
856:         #   The .* is any other junk they want to throw in before we get to the good sweet def that we are looking for!
857: 
858:         # This is the normal case (I *think*).
859:         # $1 will be set to the whitespace at the beginning of the line.
860:       else
861:         # Hmm... Try plan B. Sometimes it is defined like this:
862:         #          define_method(reflection.name) do |*params|
863:         #            force_reload = params.first unless params.empty?   # <-- And this is the line they claim we started on. (Liars.)
864:         # So we have to back up one line and try again...
865:         line_num -= 1
866:         first_line = code_for_file[line_num]
867:         if first_line =~ /^(\s+)\S?.*def/   # (includes define_method)
868:           # Good
869:         else
870:           puts "Warning: Expected #{file}:#{line_num} =~ /def.*#{method}/, but was:" #, but it was:\n#{first_line}"
871:           column pretty_code_for(@file, @line, ' ', :bold)
872:           newline
873:           return
874:         end
875:       end
876:       leading_whitespace = $1
877: 
878:       # Look for ending 'end'
879:       lines_containing_method =
880:         (line_num .. [code_for_file.size - 1, line_num+30].min).
881:         map {|i| [i, code_for_file[i]]}.
882:         select_until(inclusive = true) do |line_num, line|
883:           line =~ /^#{leading_whitespace}end/
884:       end
885: 
886:       common_indentation = lines_containing_method.map { |i, line|
887:         line =~ /^((  )*)/; $1 ? $1.size : 0
888:       }.min
889:       lines_containing_method.map! do |line_num, line|
890:         [line_num, line.sub(/^#{' ' * common_indentation}/, '')]
891:       end
892: 
893:       if line_to_highlight
894:         lines_containing_method.map! do |line_num, line|
895:           line_to_highlight == line_num ?
896:             [line_num, plain_indent(indent_adjustment) + '  ' + ' -> '.green + line.bold] :
897:             [line_num, plain_indent(indent_adjustment) + '  ' + '    ' + line]  
898:         end
899:       end
900: 
901:       output = lines_containing_method.
902:         map {|line_num, line| line}.join
903: 
904:       output
905:     end
906:   end
code_for_file(file)
     # File lib/unroller.rb, line 801
801:   def code_for_file(file)
802:     return nil if file ==  '(eval)'   # Can't really read the source from the 'eval' file, unfortunately!
803:     begin
804:       lines = File.readlines(file)
805:       lines.unshift ''                # Adjust for the fact that readlines line is 0-based, but the line numbers we'll be dealing with are 1-based.
806:       @files[file] ||= lines
807:       @files[file]
808:     rescue Errno::ENOENT
809:       $stderr.puts( message = "Error: Could not open #{file}" ) if @show_file_load_errors
810:       message
811:     rescue Exception => exception
812:       puts "Error in code_for_file(#{file}):"
813:       puts exception.inspect
814:     end
815:   end
column(string, width = nil, column_overflow = :allow, color = nil)

width is the minimum width for this column. It‘s also a maximum if column_overflow is :chop_left or :chop_right color is only needed if you plan on doing some chopping, because if you apply the color before the chopping, the color code might get chopped off.

     # File lib/unroller.rb, line 748
748:   def column(string, width = nil, column_overflow = :allow, color = nil)
749:     raise ArgumentError if ![:allow, :chop_left, :chop_right].include?(column_overflow)
750:     raise ArgumentError if width and !(width == :remainder or width.is_a?(Fixnum))
751: 
752:     if width == :remainder
753:       width = remaining_width()
754:     end
755: 
756:     if @column_counter == 0
757:       @output_line << indent
758:       width -= indent.length_without_color if width
759:     else
760:       @output_line << @column_separator      # So the columns won't be squashed up against each other
761:     end
762: 
763:     if width
764:       # Handles maximum width
765:       max_for_column = width
766:       max_for_screen = remaining_width
767:       if column_overflow =~ /chop_/   # The column only *has* a max with if column_overflow is chop_ (not if it's allow, obviously)
768:         #puts "width = #{width}"
769:         string = string.code_unroller.make_it_fit(max_for_column, column_overflow)
770:       else
771:         string = string.code_unroller.make_it_fit(max_for_screen)
772:       end
773: 
774:       string = string.ljust_without_color(width)    # Handles minimum width
775:     end
776: 
777:     @output_line << string.send_if_not_nil(color)
778: 
779:     @column_counter += 1
780:   end
do_show_locals()
     # File lib/unroller.rb, line 680
680:   def do_show_locals
681:     variables = Variables.new(:local, @binding)
682:     # puts "In do_show_locals at depth #{depth}; event = #{@event}"
683:     if variables.any?
684:       column variables.to_s, nil, :allow, :yellow
685:       newline
686:     end
687:   end
do_show_locals_verbosely()
     # File lib/unroller.rb, line 912
912:   def do_show_locals_verbosely
913:     variables = Variables.new(:local, @binding)
914:     if variables.any?
915:       puts variables.verbose_to_s.yellow
916:     end
917:   end
file_column(file, line)
     # File lib/unroller.rb, line 793
793:   def file_column(file, line)
794:     if show_filename_and_line_numbers
795:       column "#{file}:#{line}", :remainder, :chop_left, :magenta
796:     end
797:   end
fully_qualified_method()
     # File lib/unroller.rb, line 677
677:   def fully_qualified_method
678:     "#{@klass}::#{@id}"
679:   end
header_before_code_for_entire_method(file, line_num)
     # File lib/unroller.rb, line 908
908:   def header_before_code_for_entire_method(file, line_num)
909:     puts '' + "#{fully_qualified_method} (#{file}:#{line_num}) (#{@event}):".magenta if show_filename_and_line_numbers
910:   end
indent(indent_adjustment = 0)
     # File lib/unroller.rb, line 734
734:   def indent(indent_adjustment = 0)
735:     @indent_step * (@depth + indent_adjustment)
736:   end
newline()
     # File lib/unroller.rb, line 782
782:   def newline
783:     unless @output_line.strip.length_without_color == 0 or @output_line == @last_line_printed
784:       Kernel.print @output_line
785:       Kernel.puts
786:       @last_line_printed = @output_line
787:     end
788: 
789:     @output_line = ''
790:     @column_counter = 0
791:   end
plain_indent(indent_adjustment = 0)

The same thing, only just using whitespace.

     # File lib/unroller.rb, line 738
738:   def plain_indent(indent_adjustment = 0)
739:     (' '*@indent_step.length_without_color) * [(@depth + indent_adjustment), 0].max
740:   end
pretty_code_for(file, line_num, prefix, color = nil, suffix = '')
     # File lib/unroller.rb, line 829
829:   def pretty_code_for(file, line_num, prefix, color = nil, suffix = '')
830:     if file == '(eval)'
831:       return ' ' + prefix + ' ' + file.send_if_not_nil(color) + ''
832:     end
833: 
834:     ' ' + prefix + ' ' + 
835:       code_for(file, line_num).to_s.send_if_not_nil(color) +
836:       suffix
837:   end
remaining_width()
     # File lib/unroller.rb, line 742
742:   def remaining_width
743:     @screen_width - @output_line.length_without_color
744:   end
skip_line?()
     # File lib/unroller.rb, line 688
688:   def skip_line?
689:     @silent_until_return_to_this_depth or 
690:       !calling_method_in_an_interesting_class?(@klass.to_s) or 
691:       !calling_interesting_method?(@id.to_s) or 
692:       too_deep? or 
693:       !calling_interesting_line?
694:   end
too_deep?()
     # File lib/unroller.rb, line 695
695:   def too_deep?
696:     # 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".
697:     @max_depth and (@depth - @initial_depth + 1 > @max_depth)
698:   end
too_far?()
     # File lib/unroller.rb, line 699
699:   def too_far?
700:     @max_lines and (@lines_output > @max_lines)
701:   end