piggly-nsd 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +170 -0
  3. data/Rakefile +33 -0
  4. data/bin/piggly +8 -0
  5. data/lib/piggly/command/base.rb +148 -0
  6. data/lib/piggly/command/report.rb +162 -0
  7. data/lib/piggly/command/trace.rb +90 -0
  8. data/lib/piggly/command/untrace.rb +78 -0
  9. data/lib/piggly/command.rb +8 -0
  10. data/lib/piggly/compiler/cache_dir.rb +119 -0
  11. data/lib/piggly/compiler/coverage_report.rb +63 -0
  12. data/lib/piggly/compiler/trace_compiler.rb +117 -0
  13. data/lib/piggly/compiler.rb +7 -0
  14. data/lib/piggly/config.rb +80 -0
  15. data/lib/piggly/dumper/index.rb +121 -0
  16. data/lib/piggly/dumper/qualified_name.rb +36 -0
  17. data/lib/piggly/dumper/qualified_type.rb +141 -0
  18. data/lib/piggly/dumper/reified_procedure.rb +172 -0
  19. data/lib/piggly/dumper/skeleton_procedure.rb +112 -0
  20. data/lib/piggly/dumper.rb +9 -0
  21. data/lib/piggly/installer.rb +137 -0
  22. data/lib/piggly/parser/grammar.tt +748 -0
  23. data/lib/piggly/parser/nodes.rb +378 -0
  24. data/lib/piggly/parser/traversal.rb +50 -0
  25. data/lib/piggly/parser/treetop_ruby19_patch.rb +21 -0
  26. data/lib/piggly/parser.rb +69 -0
  27. data/lib/piggly/profile.rb +108 -0
  28. data/lib/piggly/reporter/base.rb +106 -0
  29. data/lib/piggly/reporter/html_dsl.rb +63 -0
  30. data/lib/piggly/reporter/index.rb +114 -0
  31. data/lib/piggly/reporter/procedure.rb +129 -0
  32. data/lib/piggly/reporter/resources/highlight.js +38 -0
  33. data/lib/piggly/reporter/resources/piggly.css +515 -0
  34. data/lib/piggly/reporter/resources/sortable.js +493 -0
  35. data/lib/piggly/reporter.rb +8 -0
  36. data/lib/piggly/tags.rb +280 -0
  37. data/lib/piggly/task.rb +215 -0
  38. data/lib/piggly/util/blankslate.rb +114 -0
  39. data/lib/piggly/util/cacheable.rb +19 -0
  40. data/lib/piggly/util/enumerable.rb +44 -0
  41. data/lib/piggly/util/file.rb +17 -0
  42. data/lib/piggly/util/process_queue.rb +96 -0
  43. data/lib/piggly/util/thunk.rb +39 -0
  44. data/lib/piggly/util.rb +9 -0
  45. data/lib/piggly/version.rb +15 -0
  46. data/lib/piggly.rb +20 -0
  47. data/spec/examples/compiler/cacheable_spec.rb +190 -0
  48. data/spec/examples/compiler/report_spec.rb +25 -0
  49. data/spec/examples/compiler/trace_spec.rb +123 -0
  50. data/spec/examples/config_spec.rb +63 -0
  51. data/spec/examples/dumper/index_spec.rb +199 -0
  52. data/spec/examples/dumper/procedure_spec.rb +116 -0
  53. data/spec/examples/grammar/expression_spec.rb +302 -0
  54. data/spec/examples/grammar/statements/assignment_spec.rb +70 -0
  55. data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
  56. data/spec/examples/grammar/statements/exception_spec.rb +78 -0
  57. data/spec/examples/grammar/statements/if_spec.rb +191 -0
  58. data/spec/examples/grammar/statements/loop_spec.rb +41 -0
  59. data/spec/examples/grammar/statements/sql_spec.rb +71 -0
  60. data/spec/examples/grammar/tokens/comment_spec.rb +58 -0
  61. data/spec/examples/grammar/tokens/datatype_spec.rb +58 -0
  62. data/spec/examples/grammar/tokens/identifier_spec.rb +74 -0
  63. data/spec/examples/grammar/tokens/keyword_spec.rb +44 -0
  64. data/spec/examples/grammar/tokens/label_spec.rb +40 -0
  65. data/spec/examples/grammar/tokens/literal_spec.rb +30 -0
  66. data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
  67. data/spec/examples/grammar/tokens/number_spec.rb +34 -0
  68. data/spec/examples/grammar/tokens/sqlkeywords_spec.rb +45 -0
  69. data/spec/examples/grammar/tokens/string_spec.rb +54 -0
  70. data/spec/examples/grammar/tokens/whitespace_spec.rb +40 -0
  71. data/spec/examples/installer_spec.rb +59 -0
  72. data/spec/examples/parser/nodes_spec.rb +73 -0
  73. data/spec/examples/parser/traversal_spec.rb +14 -0
  74. data/spec/examples/parser_spec.rb +118 -0
  75. data/spec/examples/profile_spec.rb +153 -0
  76. data/spec/examples/reporter/html/dsl_spec.rb +0 -0
  77. data/spec/examples/reporter/html/index_spec.rb +0 -0
  78. data/spec/examples/reporter/html_spec.rb +1 -0
  79. data/spec/examples/reporter_spec.rb +0 -0
  80. data/spec/examples/tags_spec.rb +285 -0
  81. data/spec/examples/task_spec.rb +0 -0
  82. data/spec/examples/util/cacheable_spec.rb +41 -0
  83. data/spec/examples/util/enumerable_spec.rb +64 -0
  84. data/spec/examples/util/file_spec.rb +40 -0
  85. data/spec/examples/util/process_queue_spec.rb +16 -0
  86. data/spec/examples/util/thunk_spec.rb +59 -0
  87. data/spec/examples/version_spec.rb +0 -0
  88. data/spec/issues/007_spec.rb +25 -0
  89. data/spec/issues/008_spec.rb +73 -0
  90. data/spec/issues/018_spec.rb +25 -0
  91. data/spec/issues/028_spec.rb +48 -0
  92. data/spec/issues/032_spec.rb +98 -0
  93. data/spec/issues/036_spec.rb +41 -0
  94. data/spec/spec_helper.rb +312 -0
  95. data/spec/spec_suite.rb +5 -0
  96. metadata +162 -0
@@ -0,0 +1,378 @@
1
+ NodeClass = Treetop::Runtime::SyntaxNode
2
+
3
+ class NodeClass
4
+ include Piggly::Parser::Traversal
5
+
6
+ # The 'text_value' method can be used to read the parse tree as Treetop
7
+ # originally read it. The 'source_text' method returns redefined value or falls
8
+ # back to original text_value if none was set.
9
+
10
+ attr_accessor :source_text
11
+
12
+ def source_text
13
+ @source_text || text_value
14
+ end
15
+
16
+ #
17
+ # Return a newly created Tag value, but only the tag.id is attached to the tree. The
18
+ # reason that is we maintain the Tags in a separate collection (to avoid a full traversal
19
+ # just to get the list of tags later), and we can retrieve the Tag associated with this
20
+ # node by its tag_id.
21
+ #
22
+ def tag(prefix = nil, id = nil)
23
+ unless defined? @tag_id
24
+ if named?(:body)
25
+ Piggly::Tags::BlockTag.new(prefix, id)
26
+ else
27
+ Piggly::Tags::EvaluationTag.new(prefix, id)
28
+ end.tap{|tag| @tag_id = tag.id }
29
+ end
30
+ end
31
+
32
+ def tag_id
33
+ @tag_id or raise RuntimeError, "Node is not tagged"
34
+ end
35
+
36
+ def tagged?
37
+ defined? @tag_id
38
+ end
39
+
40
+ # overridden in subclasses
41
+ def expression?; false end
42
+ def branch?; false end
43
+ def block?; false end
44
+ def stub?; false end
45
+ def loop?; false end
46
+ def for?; false end
47
+ def while?; false end
48
+ def style; nil end
49
+ def comment?; false end
50
+ def whitespace?; false end
51
+ def token?; false end
52
+ def string?; false end
53
+ def datatype?; false end
54
+ def identifier?; false end
55
+ def assignment?; false end
56
+ def sql?; false end
57
+ def statement?; false; end
58
+ def if?; false; end
59
+ def else?; false; end
60
+ def label?; false; end
61
+ def keyword?; false; end
62
+
63
+ def indent(method = nil)
64
+ if method and respond_to?(method)
65
+ send(method).text_value[/\n[\t ]*\z/]
66
+ else
67
+ text_value[/\n[\t ]*\z/]
68
+ end || ""
69
+ end
70
+
71
+ # True if node is called `label` by the parent node
72
+ def named?(label)
73
+ if p = parent
74
+ p.respond_to?(label) and p.send(label).equal?(self)
75
+ end
76
+ end
77
+ end
78
+
79
+ module Piggly
80
+ module Parser
81
+ module Nodes
82
+
83
+ # ...;
84
+ class Statement < NodeClass
85
+ def statement?
86
+ true
87
+ end
88
+
89
+ def terminal?
90
+ false
91
+ end
92
+ end
93
+
94
+ class Expression < NodeClass
95
+ def expression?
96
+ true
97
+ end
98
+
99
+ def tag(prefix = nil, id = nil)
100
+ unless defined? @tag_id
101
+ if named?(:cond)
102
+ if parent.while?
103
+ # This node is the conditional in a WHILE loop
104
+ Tags::ConditionalLoopTag.new(prefix, id)
105
+ elsif parent.loop?
106
+ # This node is the conditional in a loop
107
+ Tags::UnconditionalLoopTag.new(prefix, id)
108
+ elsif parent.branch?
109
+ # This node is a conditional in a branch
110
+ Tags::ConditionalBranchTag.new(prefix, id)
111
+ else
112
+ Tags::EvaluationTag.new(prefix, id)
113
+ end
114
+ else
115
+ Tags::EvaluationTag.new(prefix, id)
116
+ end.tap{|tag| @tag_id = tag.id }
117
+ end
118
+ end
119
+
120
+ def terminal?
121
+ false
122
+ end
123
+ end
124
+
125
+ # DECLARE declaration BEGIN body END;
126
+ class Block < Statement
127
+ def block?
128
+ true
129
+ end
130
+ end
131
+
132
+ # Branches with child 'cond' (Expression) will get a ConditionalBranchTag
133
+ class Branch < Statement
134
+ def branch?
135
+ true
136
+ end
137
+ end
138
+
139
+ # IF boolean-cond THEN body
140
+ class If < Branch
141
+ def if?
142
+ true
143
+ end
144
+
145
+ def terminal?
146
+ false
147
+ end
148
+ end
149
+
150
+ # ELSE body END
151
+ class Else < NodeClass
152
+ def else?
153
+ true
154
+ end
155
+
156
+ def terminal?
157
+ false
158
+ end
159
+ end
160
+
161
+ # EXCEPTION WHEN boolean-cond THEN body
162
+ class Catch < Branch
163
+ end
164
+
165
+ # WHEN match-expr THEN body
166
+ class CaseWhen < Branch
167
+ end
168
+
169
+ # WHEN boolean-cond THEN body
170
+ class CondWhen < Branch
171
+ end
172
+
173
+ # CONTINUE label WHEN boolean-cond;
174
+ class ContinueWhen < Branch
175
+ end
176
+
177
+ # EXIT label WHEN boolean-cond;
178
+ class ExitWhen < Branch
179
+ end
180
+
181
+ class UnconditionalBranch < Statement
182
+ end
183
+
184
+ # RETURN expr
185
+ class Return < UnconditionalBranch
186
+ end
187
+
188
+ # EXIT label
189
+ class Exit < UnconditionalBranch
190
+ end
191
+
192
+ # CONTINUE label
193
+ class Continue < UnconditionalBranch
194
+ end
195
+
196
+ # RAISE EXCEPTION expr
197
+ class Throw < UnconditionalBranch
198
+ end
199
+
200
+ # Loops that have a child named :cond (which should be either an Expression
201
+ # or Sql node) will get a LoopCondTag from the #tag method
202
+ class Loop < Statement
203
+ def loop?
204
+ true
205
+ end
206
+ end
207
+
208
+ # FOR var IN expr LOOP body END
209
+ class ForLoop < Loop
210
+ def for?
211
+ true
212
+ end
213
+ end
214
+
215
+ # FOREACH var IN ARRAY expr LOOP body END
216
+ class ForEachLoop < Loop
217
+ def for?
218
+ true
219
+ end
220
+ end
221
+
222
+ # WHILE boolean-cond LOOP body END
223
+ class WhileLoop < Loop
224
+ def while?
225
+ true
226
+ end
227
+ end
228
+
229
+
230
+ # RAISE NOTICE expr
231
+ class Raise < Statement
232
+ end
233
+
234
+ # CASE search-expr WHEN ...
235
+ class Case < Statement
236
+ end
237
+
238
+ # CASE WHEN ...
239
+ class Cond < Statement
240
+ end
241
+
242
+ # lval := rval
243
+ class Assignment < Statement
244
+ def assignment?
245
+ true
246
+ end
247
+ end
248
+
249
+ # Lval of assignment (rval is an Expression)
250
+ class Assignable < NodeClass
251
+ end
252
+
253
+ class Sql < Expression
254
+ def style; "tQ"; end
255
+
256
+ def sql?
257
+ true
258
+ end
259
+
260
+ def tag(prefix = nil, id = nil)
261
+ unless defined? @tag_id
262
+ if named?(:cond) and parent.for?
263
+ # This node is the conditional in a FOR loop
264
+ Tags::UnconditionalLoopTag.new(prefix, id)
265
+ else
266
+ Tags::EvaluationTag.new(prefix, id)
267
+ end.tap{|tag| @tag_id = tag.id }
268
+ end
269
+ end
270
+ end
271
+
272
+ # Terminals have no children
273
+ class Terminal < NodeClass
274
+ def initialize(input, interval, elements = nil)
275
+ # Third argument nil prevents children from being assigned
276
+ super(input, interval, nil)
277
+ end
278
+
279
+ def terminal?
280
+ true
281
+ end
282
+ end
283
+
284
+ # This seems like it should be a Token, but it may contain TComment children
285
+ # that should be highlighted differently than the enclosing whitespace
286
+ class TWhitespace < NodeClass
287
+ def terminal?
288
+ false
289
+ end
290
+
291
+ def whitespace?
292
+ true
293
+ end
294
+ end
295
+
296
+ class Token < Terminal
297
+ def token?
298
+ true
299
+ end
300
+ end
301
+
302
+ class TKeyword < Token
303
+ def style; "tK"; end
304
+
305
+ def keyword?
306
+ true
307
+ end
308
+
309
+ def tag(prefix = nil, id = nil)
310
+ unless defined? @tag_id
311
+ if named?(:cond) and parent.loop?
312
+ Tags::UnconditionalLoopTag.new(prefix, id)
313
+ else
314
+ Tags::EvaluationTag.new(prefix, id)
315
+ end
316
+ end.tap{|tag| @tag_id = tag.id }
317
+ end
318
+ end
319
+
320
+ class TIdentifier < Token
321
+ def style; "tI"; end
322
+ def identifier?
323
+ true
324
+ end
325
+ end
326
+
327
+ class TDatatype < Token
328
+ def style; "tD"; end
329
+ def datatype?
330
+ true
331
+ end
332
+ end
333
+
334
+ class TString < Token
335
+ def style; "tS"; end
336
+ def string?
337
+ true
338
+ end
339
+ end
340
+
341
+ class TDollarQuoteMarker < Token
342
+ def style; "tM"; end
343
+ end
344
+
345
+ class TComment < Token
346
+ def style; "tC"; end
347
+ def comment?
348
+ true
349
+ end
350
+ end
351
+
352
+ class TLabel < Token
353
+ def style; "tL"; end
354
+ def label?
355
+ true
356
+ end
357
+ end
358
+
359
+ class TextNode < Terminal
360
+ end
361
+
362
+ # Stub nodes have no children, or content
363
+ class StubNode < Terminal
364
+ def stub?
365
+ true
366
+ end
367
+ end
368
+
369
+ class NotImplemented < NodeClass
370
+ def parent=(object)
371
+ # this would go in the constructor, but parent is set from outside
372
+ raise Failure, "Grammar does not implement #{object.source_text} at line #{input.line_of(object.interval.first)}"
373
+ end
374
+ end
375
+
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,50 @@
1
+ module Piggly
2
+ module Parser
3
+
4
+ #
5
+ # Routines for traversing a tree; assumes base class defines elements
6
+ # as a method that returns a list of child nodes
7
+ #
8
+ module Traversal
9
+ def inject(init) # :yields: NodeClass => init
10
+ if elements
11
+ elements.inject(yield(init, self)) do |state, e|
12
+ e.inject(state){|succ, n| yield(succ, n) }
13
+ end
14
+ else
15
+ yield(init, self)
16
+ end
17
+ end
18
+
19
+ def count # :yields: NodeClass => boolean
20
+ inject(0){|sum, e| yield(e) ? sum + 1 : sum }
21
+ end
22
+
23
+ def find # :yields: NodeClass => boolean
24
+ found = false
25
+ catch :done do
26
+ inject(nil) do |_,e|
27
+ if yield(e)
28
+ found = e
29
+ throw :done
30
+ end
31
+ end
32
+ end
33
+ found
34
+ end
35
+
36
+ def select # :yields: NodeClass => boolean
37
+ inject([]){|list,e| yield(e) ? list << e : list }
38
+ end
39
+
40
+ def flatten # :yields: NodeClass
41
+ if block_given?
42
+ inject([]){|list,e| list << yield(e) }
43
+ else
44
+ inject([]){|list,e| list << e }
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+ # UTF-8 encoding support for Treetop parser
2
+ # This patch ensures that Treetop parsers properly handle UTF-8 encoded strings
3
+
4
+ require 'treetop/runtime'
5
+
6
+ module Treetop
7
+ module Runtime
8
+ # Patch CompiledParser to handle UTF-8 strings properly
9
+ class CompiledParser
10
+ alias_method :original_prepare_to_parse, :prepare_to_parse
11
+
12
+ def prepare_to_parse(input)
13
+ # Ensure input is treated as UTF-8
14
+ if input.respond_to?(:force_encoding) && input.encoding != Encoding::UTF_8
15
+ input = input.dup.force_encoding('UTF-8')
16
+ end
17
+ original_prepare_to_parse(input)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,69 @@
1
+ module Piggly
2
+
3
+ #
4
+ # Pl/pgSQL Parser, returns a tree of NodeClass values (see nodes.rb)
5
+ #
6
+ module Parser
7
+
8
+ autoload :Nodes, "piggly/parser/nodes"
9
+ autoload :Traversal, "piggly/parser/traversal"
10
+
11
+ class Failure < RuntimeError; end
12
+
13
+ class << self
14
+ # Returns lazy parse tree (only parsed when the value is needed)
15
+ def parse(string)
16
+ Util::Thunk.new do
17
+ p = parser
18
+
19
+ begin
20
+ # Ensure input is UTF-8 encoded
21
+ input = string.dup.force_encoding('UTF-8')
22
+ # Downcase input for case-insensitive parsing
23
+ input = input.downcase
24
+ tree = p.parse(input)
25
+ tree or raise Failure, "#{p.failure_reason}"
26
+ ensure
27
+ # Restore the original string after parsing
28
+ input.replace(string)
29
+ end
30
+ end
31
+ end
32
+
33
+ def parser_path; "#{File.dirname(__FILE__)}/parser/parser.rb" end
34
+ def grammar_path; "#{File.dirname(__FILE__)}/parser/grammar.tt" end
35
+ def nodes_path; "#{File.dirname(__FILE__)}/parser/nodes.rb" end
36
+
37
+ # Returns treetop parser (recompiled as needed)
38
+ def parser
39
+ return @parser if @parser
40
+
41
+ load_support
42
+
43
+ # @todo: Compare with the version of treetop
44
+ if Util::File.stale?(parser_path, grammar_path)
45
+ # Regenerate the parser when the grammar is updated
46
+ Treetop::Compiler::GrammarCompiler.new.compile(grammar_path, parser_path)
47
+ # Remove existing constant before loading to avoid warnings
48
+ Object.send(:remove_const, :PigglyParser) if Object.const_defined?(:PigglyParser)
49
+ Object.send(:remove_const, :PigglyParserParser) if Object.const_defined?(:PigglyParserParser)
50
+ load parser_path
51
+ @parser = nil # Clear cache when regenerating
52
+ else
53
+ require parser_path unless defined?(::PigglyParser::Parser)
54
+ end
55
+
56
+ @parser ||= ::PigglyParser::Parser.new
57
+ end
58
+
59
+ private
60
+
61
+ def load_support
62
+ require "treetop"
63
+ require "piggly/parser/treetop_ruby19_patch"
64
+ require "piggly/parser/nodes"
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,108 @@
1
+ module Piggly
2
+
3
+ #
4
+ # Collection of all Tags
5
+ #
6
+ class Profile
7
+
8
+ def initialize
9
+ @by_id = {}
10
+ @by_cache = {}
11
+ @by_procedure = {}
12
+ end
13
+
14
+ # Register a procedure and its list of tags
15
+ def add(procedure, tags, cache = nil)
16
+ tags.each{|t| @by_id[t.id] = t }
17
+ @by_cache[cache] = tags unless cache.nil?
18
+ @by_procedure[procedure.oid] = tags
19
+ end
20
+
21
+ def [](object)
22
+ case object
23
+ when String
24
+ @by_id[object] or
25
+ raise "No tag with id #{object}"
26
+ when Dumper::ReifiedProcedure,
27
+ Dumper::SkeletonProcedure
28
+ @by_procedure[object.oid] or
29
+ raise "No tags for procedure #{object.signature}"
30
+ end
31
+ end
32
+
33
+ # Record the execution of a coverage tag
34
+ def ping(tag_id, value=nil)
35
+ self[tag_id].ping(value)
36
+ end
37
+
38
+ # Summarizes coverage for each type of tag (branch, block, loop)
39
+ # @return [Hash<Symbol, Hash[:count => Integer, :percent => Float]>]
40
+ def summary(procedure = nil)
41
+ tags =
42
+ if procedure
43
+ if @by_procedure.include?(procedure.oid)
44
+ @by_procedure[procedure.oid]
45
+ else
46
+ []
47
+ end
48
+ else
49
+ @by_id.values
50
+ end
51
+
52
+ grouped = Util::Enumerable.group_by(tags){|x| x.type }
53
+
54
+ summary = Hash.new{|h,k| h[k] = Hash.new }
55
+ grouped.each do |type, ts|
56
+ summary[type][:count] = ts.size
57
+ summary[type][:percent] = Util::Enumerable.sum(ts){|x| x.to_f } / ts.size
58
+ end
59
+
60
+ summary
61
+ end
62
+
63
+ # Resets each tag's coverage stats
64
+ def clear
65
+ @by_id.values.each{|x| x.clear }
66
+ end
67
+
68
+ # Write coverage stats to the disk cache
69
+ def store
70
+ @by_cache.each{|cache, tags| cache[:tags] = tags }
71
+ end
72
+
73
+ def empty?(tags)
74
+ tags.all?{|t| t.to_f.zero? }
75
+ end
76
+
77
+ # @return [String]
78
+ def difference(procedure, tags)
79
+ current = Util::Enumerable.group_by(@by_procedure[procedure.oid]){|x| x.type }
80
+ previous = Util::Enumerable.group_by(tags){|x| x.type }
81
+
82
+ current.default = []
83
+ previous.default = []
84
+
85
+ (current.keys | previous.keys).map do |type|
86
+ pct = Util::Enumerable.sum(current[type]){|x| x.to_f } / current[type].size -
87
+ Util::Enumerable.sum(previous[type]){|x| x.to_f } / previous[type].size
88
+
89
+ "#{"%+0.1f" % pct}% #{type}"
90
+ end.join(", ")
91
+ end
92
+
93
+ # Build a notice processor function that records each tag execution
94
+ # @return [Proc]
95
+ def notice_processor(config, stderr = $stderr)
96
+ pattern = /#{config.trace_prefix} (#{Tags::AbstractTag::PATTERN})(?: (.))?/
97
+
98
+ lambda do |message|
99
+ if m = pattern.match(message)
100
+ ping(m.captures[0], m.captures[1])
101
+ else
102
+ stderr.puts("unknown trace: #{message}")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ end