unroller 0.0.23 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme +26 -7
- data/lib/troff.rb +4 -0
- data/lib/tron.rb +2 -0
- data/lib/unroller.rb +135 -40
- data/test/other_file.rb +6 -0
- metadata +11 -17
data/Readme
CHANGED
@@ -28,7 +28,6 @@ It's a great tool for exploring 3rd-party source code that you aren't familiar w
|
|
28
28
|
|
29
29
|
Just insert these line before the point you want to start tracing:
|
30
30
|
|
31
|
-
require 'rubygems'
|
32
31
|
require 'unroller'
|
33
32
|
Unroller::trace
|
34
33
|
|
@@ -42,6 +41,11 @@ You can also pass a block to <tt>Unroller::trace</tt> and it will automatically
|
|
42
41
|
...
|
43
42
|
end
|
44
43
|
|
44
|
+
If you want really quick and dirty:
|
45
|
+
require 'tron'
|
46
|
+
...
|
47
|
+
troff
|
48
|
+
|
45
49
|
===Example
|
46
50
|
|
47
51
|
Say I have an ActiveRecord model and want to know exactly what actually goes on behind the scenes when I call model.save! . All I have to do is wrap the method call in a "trace" block, like this:
|
@@ -109,9 +113,7 @@ And the somewhat trickier but arguably more elegant way that still uses a block
|
|
109
113
|
code_that_may_or_may_not_be_traced
|
110
114
|
end
|
111
115
|
|
112
|
-
The other reason that the latter way is preferred is that the return value of the code-being-traced is preserved. With the first method, you could end up breaking things if the trace_off happens to be the last value in your method (because then the value of trace_off will be used as the return
|
113
|
-
|
114
|
-
|
116
|
+
The other reason that the latter way is preferred is that the return value of the code-being-traced is preserved. With the first method, you could end up breaking things if the trace_off happens to be the last value in your method (because then the value of trace_off will be used as the return value).
|
115
117
|
|
116
118
|
Note: The actual application code (the code in the block passed to Unroller::trace, if using the block version) will _always_ get executed, with either of these methods. It is only the tracing that we are toggling, not the execution of the code within the trace(d) block.
|
117
119
|
|
@@ -123,6 +125,10 @@ A couple options are available to help things under control. The two main approa
|
|
123
125
|
|
124
126
|
You may find that your trace is cluttered/dominated by calls to a small set of methods and classes that you don't care about. These options help you to exclude the worst offenders in a hurry:
|
125
127
|
|
128
|
+
<tt>:file_match => file_match</tt> ::
|
129
|
+
Unroller will only show a trace for code whose filename matches <tt>file_match</tt>. If <tt>file_match</tt> is not already a regular expression it will be escaped and turned into one (<tt>/file_match/</tt>).
|
130
|
+
<tt>:dir_match => dir_match</tt> ::
|
131
|
+
Unroller will only show a trace for code whose filename matches <tt>dir_match</tt>. If <tt>dir_match</tt> is not already a regular expression it will be escaped and turned into one (<tt>/^dir_match/</tt>). Tip: You can simply pass in __FILE__ and it will automatically turn that into File.expand_path(File.dirname(__FILE__)) for you.
|
126
132
|
<tt>:exclude_classes</tt> ::
|
127
133
|
Unroller won't show a trace for any calls to methods from the given class or classes (regular expressions).
|
128
134
|
Pass [/class_name/, :recursive] to also not show the trace for any calls made *from* those uninteresting methods.
|
@@ -191,7 +197,9 @@ Here's one way you could try to answer that question...
|
|
191
197
|
|
192
198
|
===Usage in Rails
|
193
199
|
|
194
|
-
Unroller can be useful for debugging in development mode as well as in tests.
|
200
|
+
Unroller can be useful for debugging in development mode as well as in tests.
|
201
|
+
|
202
|
+
<b>Requires a terminal</p>. Keep in mind that it requires a terminal onto which to output, so it will work if you've started your server with <tt>./script/server</tt>; it will _not_ work if the server is detached from the terminal using <tt>./script/server -d</tt>, for example, or if it's being executed via FastCGI... Don't worry, it will gently remind you of this with a happy <tt>can't get terminal parameters (Inappropriate ioctl for device)</tt> error if you forget about it.
|
195
203
|
|
196
204
|
Rails has a lot of levels of abstractions, so even a seemingly simple call can generate many, many method calls. You may want to try using some of the filtering options, such as <tt>:exclude_classes</tt>, to reduce the verbosity of the output.
|
197
205
|
|
@@ -209,7 +217,7 @@ If you want to see all code that gets executed within a certain action in your c
|
|
209
217
|
|
210
218
|
sudo gem install unroller --include-dependencies
|
211
219
|
|
212
|
-
The dependencies include: facets,
|
220
|
+
The dependencies include: facets, quality_extensions, colored, and extensions
|
213
221
|
|
214
222
|
==Status
|
215
223
|
|
@@ -268,6 +276,9 @@ You're welcome to submit comments, ideas, and/or patches.
|
|
268
276
|
|
269
277
|
* Make a GUI interface that lets you quickly collapse/nodes nodes of the tree.
|
270
278
|
* :include_classes option in addition to :exclude_classes?
|
279
|
+
|
280
|
+
===Presets
|
281
|
+
|
271
282
|
* Have some "presets" for what you might want to exclude if tracing an ActiveRecord request for example. In that case, you probably don't want to see the internals of any support code, like any methods from ActiveSupport.
|
272
283
|
* :rails => true
|
273
284
|
* :preset => :Rails : Exclude ActiveSupport, Dependencies, etc.
|
@@ -275,7 +286,14 @@ You're welcome to submit comments, ideas, and/or patches.
|
|
275
286
|
* :preset => :'ActiveRecord high level' : excludes the lowel-level database stuff (like the individual adapter (SQLite, MySQL, ...).
|
276
287
|
* :preset => :'ActiveRecord low level'
|
277
288
|
|
278
|
-
|
289
|
+
Might be more intuitive to say :exclude => :boring_rails_stuff
|
290
|
+
(If it's a symbol passed in instead of a string or regexp, we'll assume it's a preset, which will be turned into the strings/regexp's that it defines)
|
291
|
+
|
292
|
+
Also :include => :something.
|
293
|
+
|
294
|
+
Let them store presets in a ~/.unroller file so they're not limited to my ideas, and don't have to type out a long list of exclusions every time they want to reuse a common set of exclusions.
|
295
|
+
|
296
|
+
==="Watch for" conditions
|
279
297
|
|
280
298
|
Only traces / shows you when/if a certain condition is met.
|
281
299
|
|
@@ -298,3 +316,4 @@ end
|
|
298
316
|
|
299
317
|
Other name ideas: :match_code, :match_event, :event_match, :only_events
|
300
318
|
|
319
|
+
|
data/lib/troff.rb
ADDED
data/lib/tron.rb
ADDED
data/lib/unroller.rb
CHANGED
@@ -1,29 +1,31 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
gem 'facets'
|
3
|
-
require 'facets
|
4
|
-
require 'facets/
|
5
|
-
require 'facets/
|
6
|
-
require 'facets/
|
7
|
-
require 'facets/
|
8
|
-
require 'facets/
|
9
|
-
require 'facets/
|
10
|
-
require 'facets/
|
11
|
-
|
12
|
-
|
13
|
-
require '
|
14
|
-
require '
|
15
|
-
require '
|
16
|
-
require '
|
17
|
-
require '
|
18
|
-
require '
|
19
|
-
require '
|
20
|
-
require '
|
21
|
-
require '
|
22
|
-
require '
|
3
|
+
require 'facets'
|
4
|
+
require 'facets/methodspace'
|
5
|
+
require 'facets/kernel/populate'
|
6
|
+
require 'facets/kernel/returning'
|
7
|
+
#require 'facets/kernel/singleton_class'
|
8
|
+
#require 'facets/symbol/to_proc'
|
9
|
+
#require 'facets/string/bracket'
|
10
|
+
#require 'facets/string/index_all'
|
11
|
+
#require 'facets/hash/reverse_merge'
|
12
|
+
gem 'quality_extensions'
|
13
|
+
require 'quality_extensions/object/send_if_not_nil'
|
14
|
+
require 'quality_extensions/kernel/trap_chain'
|
15
|
+
require 'quality_extensions/kernel/capture_output'
|
16
|
+
require 'quality_extensions/string/with_knowledge_of_color'
|
17
|
+
require 'quality_extensions/exception/inspect_with_backtrace'
|
18
|
+
require 'quality_extensions/regexp/join'
|
19
|
+
require 'quality_extensions/symbol/match'
|
20
|
+
require 'quality_extensions/module/alias_method_chain'
|
21
|
+
require 'quality_extensions/module/malias_method_chain'
|
22
|
+
require 'quality_extensions/module/attribute_accessors'
|
23
|
+
require 'quality_extensions/enumerable/select_until'
|
24
|
+
require 'quality_extensions/module/bool_attr_accessor'
|
23
25
|
gem 'colored'
|
24
26
|
require 'colored'
|
25
|
-
gem 'extensions'
|
26
|
-
require 'extensions/object'
|
27
|
+
#gem 'extensions'
|
28
|
+
#require 'extensions/object'
|
27
29
|
|
28
30
|
require 'English'
|
29
31
|
require 'pp'
|
@@ -156,6 +158,7 @@ class Unroller
|
|
156
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".)
|
157
159
|
@line_matches = nil # The source code for that line matches this regular expression
|
158
160
|
@presets = []
|
161
|
+
@file_match = /./
|
159
162
|
@exclude_classes = []
|
160
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.
|
161
164
|
@exclude_methods = []
|
@@ -220,11 +223,38 @@ class Unroller
|
|
220
223
|
end
|
221
224
|
end
|
222
225
|
|
226
|
+
#-----------------------------------------------------------------------------------------------
|
223
227
|
# Options
|
224
|
-
|
225
|
-
|
226
|
-
options[:
|
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
|
+
|
228
258
|
if options.has_key?(:exclude_classes)
|
229
259
|
# Coerce it into an array of ClassExclusions
|
230
260
|
a = options.delete(:exclude_classes)
|
@@ -243,8 +273,9 @@ class Unroller
|
|
243
273
|
@exclude_methods.concat a
|
244
274
|
end
|
245
275
|
options[:line_matches] = options.delete(:line_matches) if options.has_key?(:line_matches)
|
246
|
-
|
276
|
+
populate(options)
|
247
277
|
|
278
|
+
#-----------------------------------------------------------------------------------------------
|
248
279
|
# Private
|
249
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.
|
250
281
|
# This is useful for two reasons:
|
@@ -689,6 +720,9 @@ protected
|
|
689
720
|
end
|
690
721
|
end
|
691
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
|
692
726
|
return true if @line_matches.nil? # No filter to apply
|
693
727
|
line = code_for(@file, @line) or return false
|
694
728
|
(line =~ @line_matches)
|
@@ -702,7 +736,7 @@ protected
|
|
702
736
|
end
|
703
737
|
# The same thing, only just using whitespace.
|
704
738
|
def plain_indent(indent_adjustment = 0)
|
705
|
-
(' '*@indent_step.length_without_color) * (@depth + indent_adjustment)
|
739
|
+
(' '*@indent_step.length_without_color) * [(@depth + indent_adjustment), 0].max
|
706
740
|
end
|
707
741
|
|
708
742
|
def remaining_width
|
@@ -799,7 +833,7 @@ protected
|
|
799
833
|
|
800
834
|
' ' + prefix + ' ' +
|
801
835
|
code_for(file, line_num).to_s.send_if_not_nil(color) +
|
802
|
-
|
836
|
+
suffix
|
803
837
|
end
|
804
838
|
|
805
839
|
#----------------------------------------------------------
|
@@ -887,10 +921,10 @@ end # class Unroller
|
|
887
921
|
|
888
922
|
|
889
923
|
class String
|
890
|
-
|
924
|
+
method_space :code_unroller do
|
891
925
|
|
892
926
|
def make_it_fit(max_width, overflow = :chop_right)
|
893
|
-
|
927
|
+
returning(string = self) do
|
894
928
|
if string.length_without_color > max_width # Wider than desired column width; Needs to be chopped.
|
895
929
|
unless max_width < 4 # Is there even enough room for it if it *is* chopped? If not, then don't even bother.
|
896
930
|
#Kernel.p overflow
|
@@ -1149,7 +1183,7 @@ if $0 == __FILE__
|
|
1149
1183
|
herald '-----------------------------------------------------------'
|
1150
1184
|
herald 'Test :line_matches'
|
1151
1185
|
herald 'Should only see lines matching "$a_global"'
|
1152
|
-
require 'facets/
|
1186
|
+
require 'facets/string/to_re'
|
1153
1187
|
Unroller::trace :line_matches => '$a_global'.to_re do
|
1154
1188
|
# This won't print anything, because for evals that are missing the __FILE__, __LINE__ arguments, we can't even read the source code (unfortunately)
|
1155
1189
|
eval %(
|
@@ -1168,6 +1202,29 @@ if $0 == __FILE__
|
|
1168
1202
|
blah = 'blah'
|
1169
1203
|
end
|
1170
1204
|
|
1205
|
+
herald '-----------------------------------------------------------'
|
1206
|
+
herald 'Test :file_match'
|
1207
|
+
herald 'Should only see calls to in_this_file'
|
1208
|
+
require_local '../test/other_file'
|
1209
|
+
def in_this_file
|
1210
|
+
a = 'a'
|
1211
|
+
b = 'b'
|
1212
|
+
c = 'c'
|
1213
|
+
etc = 'etc.'
|
1214
|
+
end
|
1215
|
+
Unroller::trace(:file_match => __FILE__) do
|
1216
|
+
in_this_file()
|
1217
|
+
in_other_file()
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
herald '-----------------------------------------------------------'
|
1221
|
+
herald 'Test :dir_match'
|
1222
|
+
herald 'Should only see calls to in_this_file'
|
1223
|
+
Unroller::trace(:dir_match => __FILE__) do
|
1224
|
+
in_this_file()
|
1225
|
+
in_other_file()
|
1226
|
+
end
|
1227
|
+
|
1171
1228
|
herald '-----------------------------------------------------------'
|
1172
1229
|
herald 'Test @exclude_methods'
|
1173
1230
|
herald 'Should only see calls to green, strong'
|
@@ -1404,17 +1461,55 @@ if $0 == __FILE__
|
|
1404
1461
|
big_ol_gaggle
|
1405
1462
|
end
|
1406
1463
|
|
1464
|
+
# herald '-----------------------------------------------------------'
|
1465
|
+
# herald 'Testing :interactive => true / interactive debugger'
|
1466
|
+
# def factorial(x)
|
1467
|
+
# if x == 1
|
1468
|
+
# x
|
1469
|
+
# else
|
1470
|
+
# x * factorial(x - 1)
|
1471
|
+
# end
|
1472
|
+
# end
|
1473
|
+
# Unroller::debug do
|
1474
|
+
# factorial(4)
|
1475
|
+
# end
|
1476
|
+
|
1407
1477
|
herald '-----------------------------------------------------------'
|
1408
|
-
herald 'Testing :interactive => true / interactive debugger'
|
1409
|
-
def
|
1410
|
-
|
1411
|
-
x
|
1412
|
-
else
|
1413
|
-
x * factorial(x - 1)
|
1414
|
-
end
|
1478
|
+
herald 'Testing :interactive => true / interactive debugger: two calls on the same line'
|
1479
|
+
def method_1
|
1480
|
+
'stuff'
|
1415
1481
|
end
|
1482
|
+
def method_2
|
1483
|
+
'stuff'
|
1484
|
+
end
|
1485
|
+
# Step Over will step over the call to method_1 but will immediately step into method_2, without coming back to this line to ask you what you want to do about the call to method_2 ...
|
1486
|
+
# In other words, there is no intermediate 'line' event between the 2 'call' events (right?)
|
1487
|
+
# This could be confusing to some users... Should we hack it to somehow detect that there are two calls in a row for the same
|
1488
|
+
# line and artificially inject a pseudo-line event in between so that we have a chance to show the menu again??
|
1416
1489
|
Unroller::debug do
|
1417
|
-
|
1490
|
+
sum = method_1 + method_2
|
1491
|
+
puts sum
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
herald '-----------------------------------------------------------'
|
1495
|
+
herald 'Testing :interactive => true / interactive debugger: with blocks'
|
1496
|
+
def block_taker(&block)
|
1497
|
+
puts "I'm the block taker. I take blocks."
|
1498
|
+
puts "Please Step Over the next line."
|
1499
|
+
yield # buggy!
|
1500
|
+
# false:: (unroller.rb:1433) (line): ??
|
1501
|
+
puts "Done yielding to the block"
|
1502
|
+
end
|
1503
|
+
Unroller::debug do
|
1504
|
+
puts "If you do a Step Over, will it go into the block or not? Nope. Because we skipped tracing the yield."
|
1505
|
+
# to do: have a separate "step over method body but into block" option??
|
1506
|
+
# that way, it you just want to stay in this local file and avoid seeing the code that *wraps* the call to the block, you could do so...
|
1507
|
+
block_taker do
|
1508
|
+
puts 'Pick me! Pick me!'
|
1509
|
+
puts 'I say! When will I ever be executed?'
|
1510
|
+
puts 'Oh dear, will they just skip right over me?'
|
1511
|
+
puts 'Or will they Step In here and take a look?'
|
1512
|
+
end
|
1418
1513
|
end
|
1419
1514
|
|
1420
1515
|
herald '-----------------------------------------------------------'
|
data/test/other_file.rb
ADDED
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.4
|
3
3
|
specification_version: 1
|
4
4
|
name: unroller
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0
|
7
|
-
date:
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2008-06-20 00:00:00 -07:00
|
8
8
|
summary: "Ruby Code Unroller: A tool for generating human-readable \"execution traces\""
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,10 +29,13 @@ post_install_message:
|
|
29
29
|
authors:
|
30
30
|
- Tyler Rick
|
31
31
|
files:
|
32
|
+
- lib/tron.rb
|
32
33
|
- lib/unroller.rb
|
34
|
+
- lib/troff.rb
|
35
|
+
- test/other_file.rb
|
33
36
|
- Readme
|
34
|
-
test_files:
|
35
|
-
|
37
|
+
test_files:
|
38
|
+
- test/other_file.rb
|
36
39
|
rdoc_options:
|
37
40
|
- --title
|
38
41
|
- unroller
|
@@ -55,16 +58,16 @@ dependencies:
|
|
55
58
|
requirements:
|
56
59
|
- - ">="
|
57
60
|
- !ruby/object:Gem::Version
|
58
|
-
version:
|
61
|
+
version: 2.4.1
|
59
62
|
version:
|
60
63
|
- !ruby/object:Gem::Dependency
|
61
|
-
name:
|
64
|
+
name: quality_extensions
|
62
65
|
version_requirement:
|
63
66
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
64
67
|
requirements:
|
65
68
|
- - ">="
|
66
69
|
- !ruby/object:Gem::Version
|
67
|
-
version: 0.
|
70
|
+
version: 0.1.3
|
68
71
|
version:
|
69
72
|
- !ruby/object:Gem::Dependency
|
70
73
|
name: colored
|
@@ -84,12 +87,3 @@ dependencies:
|
|
84
87
|
- !ruby/object:Gem::Version
|
85
88
|
version: 0.0.0
|
86
89
|
version:
|
87
|
-
- !ruby/object:Gem::Dependency
|
88
|
-
name: extensions
|
89
|
-
version_requirement:
|
90
|
-
version_requirements: !ruby/object:Gem::Version::Requirement
|
91
|
-
requirements:
|
92
|
-
- - ">"
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: 0.0.0
|
95
|
-
version:
|