tap 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/History +12 -0
  2. data/MIT-LICENSE +0 -2
  3. data/README +23 -32
  4. data/bin/rap +116 -0
  5. data/bin/tap +6 -9
  6. data/cgi/run.rb +67 -0
  7. data/cmd/console.rb +1 -1
  8. data/cmd/destroy.rb +4 -4
  9. data/cmd/generate.rb +4 -4
  10. data/cmd/manifest.rb +61 -53
  11. data/cmd/run.rb +8 -75
  12. data/doc/Class Reference +130 -121
  13. data/doc/Command Reference +76 -124
  14. data/doc/Syntax Reference +290 -0
  15. data/doc/Tutorial +305 -237
  16. data/lib/tap/app.rb +140 -467
  17. data/lib/tap/constants.rb +2 -2
  18. data/lib/tap/declarations.rb +211 -0
  19. data/lib/tap/env.rb +171 -193
  20. data/lib/tap/exe.rb +100 -21
  21. data/lib/tap/file_task.rb +3 -3
  22. data/lib/tap/generator/base.rb +1 -1
  23. data/lib/tap/generator/destroy.rb +10 -10
  24. data/lib/tap/generator/generate.rb +29 -18
  25. data/lib/tap/generator/generators/command/command_generator.rb +2 -2
  26. data/lib/tap/generator/generators/command/templates/command.erb +2 -2
  27. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  28. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  29. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  30. data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
  31. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  32. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  33. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  34. data/lib/tap/generator/generators/root/root_generator.rb +13 -13
  35. data/lib/tap/generator/generators/root/templates/README +0 -0
  36. data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
  37. data/lib/tap/generator/generators/root/templates/gemspec +4 -5
  38. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  39. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  40. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  41. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  42. data/lib/tap/patches/optparse/summarize.rb +62 -0
  43. data/lib/tap/root.rb +41 -29
  44. data/lib/tap/support/aggregator.rb +16 -3
  45. data/lib/tap/support/assignments.rb +10 -9
  46. data/lib/tap/support/audit.rb +58 -64
  47. data/lib/tap/support/class_configuration.rb +33 -44
  48. data/lib/tap/support/combinator.rb +125 -0
  49. data/lib/tap/support/configurable.rb +13 -14
  50. data/lib/tap/support/configurable_class.rb +21 -43
  51. data/lib/tap/support/configuration.rb +55 -9
  52. data/lib/tap/support/constant.rb +87 -13
  53. data/lib/tap/support/constant_manifest.rb +116 -0
  54. data/lib/tap/support/dependencies.rb +54 -0
  55. data/lib/tap/support/dependency.rb +44 -0
  56. data/lib/tap/support/executable.rb +247 -32
  57. data/lib/tap/support/executable_queue.rb +1 -1
  58. data/lib/tap/support/gems/rake.rb +29 -8
  59. data/lib/tap/support/gems.rb +10 -30
  60. data/lib/tap/support/instance_configuration.rb +29 -3
  61. data/lib/tap/support/intern.rb +46 -0
  62. data/lib/tap/support/join.rb +143 -0
  63. data/lib/tap/support/joins/fork.rb +19 -0
  64. data/lib/tap/support/joins/merge.rb +22 -0
  65. data/lib/tap/support/joins/sequence.rb +21 -0
  66. data/lib/tap/support/joins/switch.rb +25 -0
  67. data/lib/tap/support/joins/sync_merge.rb +63 -0
  68. data/lib/tap/support/joins.rb +15 -0
  69. data/lib/tap/support/lazy_attributes.rb +17 -2
  70. data/lib/tap/support/lazydoc/comment.rb +503 -0
  71. data/lib/tap/support/lazydoc/config.rb +17 -0
  72. data/lib/tap/support/lazydoc/definition.rb +36 -0
  73. data/lib/tap/support/lazydoc/document.rb +152 -0
  74. data/lib/tap/support/lazydoc/method.rb +24 -0
  75. data/lib/tap/support/lazydoc.rb +269 -343
  76. data/lib/tap/support/manifest.rb +121 -103
  77. data/lib/tap/support/minimap.rb +90 -0
  78. data/lib/tap/support/node.rb +56 -0
  79. data/lib/tap/support/parser.rb +436 -0
  80. data/lib/tap/support/schema.rb +359 -0
  81. data/lib/tap/support/shell_utils.rb +3 -5
  82. data/lib/tap/support/string_ext.rb +60 -0
  83. data/lib/tap/support/tdoc.rb +7 -2
  84. data/lib/tap/support/templater.rb +30 -16
  85. data/lib/tap/support/validation.rb +77 -8
  86. data/lib/tap/task.rb +431 -143
  87. data/lib/tap/tasks/dump.rb +15 -10
  88. data/lib/tap/tasks/load.rb +112 -0
  89. data/lib/tap/tasks/rake.rb +4 -41
  90. data/lib/tap/test/assertions.rb +38 -0
  91. data/lib/tap/test/env_vars.rb +1 -1
  92. data/lib/tap/test/extensions.rb +79 -0
  93. data/lib/tap/test/file_test.rb +420 -0
  94. data/lib/tap/test/file_test_class.rb +12 -0
  95. data/lib/tap/test/regexp_escape.rb +87 -0
  96. data/lib/tap/test/script_test.rb +46 -0
  97. data/lib/tap/test/script_tester.rb +115 -0
  98. data/lib/tap/test/subset_test.rb +260 -0
  99. data/lib/tap/test/subset_test_class.rb +99 -0
  100. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
  101. data/lib/tap/test/utils.rb +231 -0
  102. data/lib/tap/test.rb +53 -26
  103. data/lib/tap.rb +3 -20
  104. metadata +50 -27
  105. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  106. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  107. data/lib/tap/patches/rake/testtask.rb +0 -57
  108. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  109. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  110. data/lib/tap/support/batchable.rb +0 -47
  111. data/lib/tap/support/batchable_class.rb +0 -107
  112. data/lib/tap/support/command_line.rb +0 -98
  113. data/lib/tap/support/comment.rb +0 -270
  114. data/lib/tap/support/constant_utils.rb +0 -127
  115. data/lib/tap/support/declarations.rb +0 -111
  116. data/lib/tap/support/framework.rb +0 -83
  117. data/lib/tap/support/framework_class.rb +0 -180
  118. data/lib/tap/support/run_error.rb +0 -39
  119. data/lib/tap/support/summary.rb +0 -30
  120. data/lib/tap/test/file_methods.rb +0 -377
  121. data/lib/tap/test/script_methods/script_test.rb +0 -98
  122. data/lib/tap/test/script_methods.rb +0 -107
  123. data/lib/tap/test/subset_methods.rb +0 -420
  124. data/lib/tap/workflow.rb +0 -200
@@ -0,0 +1,436 @@
1
+ require 'tap/support/schema'
2
+ autoload(:Shellwords, 'shellwords')
3
+
4
+ module Tap
5
+ module Support
6
+
7
+ # == Syntax
8
+ #
9
+ # ==== Round Assignment
10
+ # Tasks can be defined and set to a round using the following:
11
+ #
12
+ # break assigns task(s) to round
13
+ # -- next 0
14
+ # --+ next 1
15
+ # --++ next 2
16
+ # --+2 next 2
17
+ # --+2[1,2,3] 1,2,3 2
18
+ #
19
+ # Here all task (except c) are parsed into round 0, then the
20
+ # final argument reassigns e to round 3.
21
+ #
22
+ # schema = Parser.new("a -- b --+ c -- d -- e --+3[4]").schema
23
+ # schema.rounds(true) # => [[0,1,3],[2], nil, [4]]
24
+ #
25
+ # ==== Workflow Assignment
26
+ # All simple workflow patterns except switch can be specified within
27
+ # the parse syntax (switch is the exception because there is no good
28
+ # way to define the switch block).
29
+ #
30
+ # break pattern source(s) target(s)
31
+ # --: sequence last next
32
+ # --[] fork last next
33
+ # --{} merge next last
34
+ # --() sync_merge next last
35
+ #
36
+ # example meaning
37
+ # --1:2 1.sequence(2)
38
+ # --1:2:3 1.sequence(2,3)
39
+ # --:2: last.sequence(2,next)
40
+ # --[] last.fork(next)
41
+ # --1{2,3,4} 1.merge(2,3,4)
42
+ # --(2,3,4) last.sync_merge(2,3,4)
43
+ #
44
+ # Note how all of the bracketed styles behave similarly; they are
45
+ # parsed with essentially the same code, but reverse the source
46
+ # and target in the case of merges.
47
+ #
48
+ # Here a and b are sequenced inline. Task c is assigned to no
49
+ # workflow until the final argument which sequenced b and c.
50
+ #
51
+ # schema = Parser.new("a --: b -- c --1:2i").schema
52
+ # schema.argvs # => [["a"], ["b"], ["c"], []]
53
+ # schema.joins(true) # => [[:sequence,0,[1],{}], [:sequence,1,[2],{:iterate => true}]]
54
+ #
55
+ # ==== Globals
56
+ # Global instances of task (used, for example, by dependencies) may
57
+ # be assigned in the parse syntax as well. The break for a global
58
+ # is '--*'.
59
+ #
60
+ # schema = Parser.new("a -- b --* global_name --config for --global").schema
61
+ # schema.globals(true) # => [2]
62
+ #
63
+ # ==== Escapes and End Flags
64
+ # Breaks can be escaped by enclosing them in '-.' and '.-' delimiters;
65
+ # any number of arguments may be enclosed within the escape. After the
66
+ # end delimiter, breaks are active once again.
67
+ #
68
+ # schema = Parser.new("a -- b -- c").schema
69
+ # schema.argvs # => [["a"], ["b"], ["c"]]
70
+ #
71
+ # schema = Parser.new("a -. -- b .- -- c").schema
72
+ # schema.argvs # => [["a", "--", "b"], ["c"]]
73
+ #
74
+ # Parsing continues until the end of argv, or a an end flag '---' is
75
+ # reached. The end flag may also be escaped.
76
+ #
77
+ # schema = Parser.new("a -- b --- c").schema
78
+ # schema.argvs # => [["a"], ["b"]]
79
+ #
80
+ class Parser
81
+
82
+ # A set of parsing routines used internally by Tap::Support::Parser,
83
+ # modularized for ease of testing, and potential re-use. These methods
84
+ # require that <tt>current_index</tt> and <tt>previous_index</tt> be
85
+ # implemented in the including class.
86
+ module Utils
87
+ module_function
88
+
89
+ # Defines a break regexp that matches a bracketed-pairs
90
+ # break. The left and right brackets are specified as
91
+ # inputs. After a match:
92
+ #
93
+ # $1:: The source string after the break.
94
+ # (ex: '[]' => '', '1[]' => '1')
95
+ # $2:: The target string.
96
+ # (ex: '[]' => '', '1[1,2,3]' => '1,2,3')
97
+ # $3:: The modifier string.
98
+ # (ex: '[]i' => 'i', '1[1,2,3]is' => 'is')
99
+ #
100
+ def bracket_regexp(l, r)
101
+ /\A(\d*)#{Regexp.escape(l)}([\d,]*)#{Regexp.escape(r)}([A-z]*)\z/
102
+ end
103
+
104
+ # The escape begin argument
105
+ ESCAPE_BEGIN = "-."
106
+
107
+ # The escape end argument
108
+ ESCAPE_END = ".-"
109
+
110
+ # The parser end flag
111
+ END_FLAG = "---"
112
+
113
+ # Matches any breaking arg (ex: '--', '--+', '--1:2')
114
+ # After the match:
115
+ #
116
+ # $1:: The string after the break
117
+ # (ex: '--' => '', '--++' => '++', '--1(2,3)' => '1(2,3)')
118
+ #
119
+ BREAK = /\A--(\z|[\+\d\:\*\[\{\(].*\z)/
120
+
121
+ # Matches an execution-round break. After the match:
122
+ #
123
+ # $2:: The round string, or nil.
124
+ # (ex: '' => nil, '++' => '++', '+1' => '+1')
125
+ # $5:: The target string, or nil.
126
+ # (ex: '+' => nil, '+[1,2,3]' => '1,2,3')
127
+ #
128
+ ROUND = /\A((\+(\d*|\+*))(\[([\d,]*)\])?)?\z/
129
+
130
+ # Matches a sequence break. After the match:
131
+ #
132
+ # $1:: The sequence string after the break.
133
+ # (ex: ':' => ':', '1:2' => '1:2', '1:' => '1:', ':2' => ':2')
134
+ # $3:: The modifier string.
135
+ # (ex: ':i' => 'i', '1:2is' => 'is')
136
+ #
137
+ SEQUENCE = /\A(\d*(:\d*)+)([A-z]*)\z/
138
+
139
+ # Matches an instance break. After the match:
140
+ #
141
+ # $1:: The index string after the break.
142
+ # (ex: '*' => '', '*1' => '1')
143
+ #
144
+ INSTANCE = /\A\*(\d*)\z/
145
+
146
+ # A break regexp using "[]"
147
+ FORK = bracket_regexp("[", "]")
148
+
149
+ # A break regexp using "{}"
150
+ MERGE = bracket_regexp("{", "}")
151
+
152
+ # A break regexp using "()"
153
+ SYNC_MERGE = bracket_regexp("(", ")")
154
+
155
+ # Parses an indicies str along commas, and collects the indicies
156
+ # as integers. Ex:
157
+ #
158
+ # parse_indicies('') # => []
159
+ # parse_indicies('1') # => [1]
160
+ # parse_indicies('1,2,3') # => [1,2,3]
161
+ #
162
+ def parse_indicies(str, regexp=/,+/)
163
+ indicies = []
164
+ str.split(regexp).each do |n|
165
+ indicies << n.to_i unless n.empty?
166
+ end
167
+ indicies
168
+ end
169
+
170
+ # Parses the match of a ROUND regexp into a round index
171
+ # and an array of task indicies that should be added to the
172
+ # round. The inputs correspond to $2 and $5 for the match.
173
+ #
174
+ # If $2 is nil, a round index of zero is assumed; if $5 is
175
+ # nil or empty, then indicies of [:current_index] are assumed.
176
+ #
177
+ # parse_round("+", "") # => [1, [:current_index]]
178
+ # parse_round("+2", "1,2,3") # => [2, [1,2,3]]
179
+ # parse_round(nil, nil) # => [0, [:current_index]]
180
+ #
181
+ def parse_round(two, five)
182
+ index = case two
183
+ when nil then 0
184
+ when /\d/ then two[1, two.length-1].to_i
185
+ else two.length
186
+ end
187
+ [index, five.to_s.empty? ? [current_index] : parse_indicies(five)]
188
+ end
189
+
190
+ # Parses the match of a SEQUENCE regexp into an [indicies, options]
191
+ # array. The inputs corresponds to $1 and $3 for the match. The
192
+ # previous and current index are assumed if $1 starts and/or ends
193
+ # with a semi-colon.
194
+ #
195
+ # parse_sequence("1:2:3", '') # => [[1,2,3], {}]
196
+ # parse_sequence(":1:2:", '') # => [[:previous_index,1,2,:current_index], {}]
197
+ #
198
+ def parse_sequence(one, three)
199
+ seq = parse_indicies(one, /:+/)
200
+ seq.unshift previous_index if one[0] == ?:
201
+ seq << current_index if one[-1] == ?:
202
+ [seq, parse_options(three)]
203
+ end
204
+
205
+ # Parses the match of an INSTANCE regexp into an index.
206
+ # The input corresponds to $1 for the match. The current
207
+ # index is assumed if $1 is empty.
208
+ #
209
+ # parse_instance("1") # => 1
210
+ # parse_instance("") # => :current_index
211
+ #
212
+ def parse_instance(one)
213
+ one.empty? ? current_index : one.to_i
214
+ end
215
+
216
+ # Parses the match of an bracket_regexp into a [source_index,
217
+ # target_indicies, options] array. The inputs corresponds to $1,
218
+ # $2, and $3 for the match. The previous and current index are
219
+ # assumed if $1 and/or $2 is empty.
220
+ #
221
+ # parse_bracket("1", "2,3", "") # => [1, [2,3], {}]
222
+ # parse_bracket("", "", "") # => [:previous_index, [:current_index], {}]
223
+ # parse_bracket("1", "", "") # => [1, [:current_index], {}]
224
+ # parse_bracket("", "2,3", "") # => [:previous_index, [2,3], {}]
225
+ #
226
+ def parse_bracket(one, two, three)
227
+ targets = parse_indicies(two)
228
+ targets << current_index if targets.empty?
229
+ [one.empty? ? previous_index : one.to_i, targets, parse_options(three)]
230
+ end
231
+
232
+ # Parses an options string into a hash. The input corresponds
233
+ # to $3 in a SEQUENCE or bracket_regexp match. Raises an error
234
+ # if the options string contains unknown options.
235
+ #
236
+ # parse_options("") # => {}
237
+ # parse_options("is") # => {:iterate => true, :stack => true}
238
+ #
239
+ def parse_options(three)
240
+ options = {}
241
+ 0.upto(three.length - 1) do |char_index|
242
+ char = three[char_index, 1]
243
+ unless index = Join::SHORT_FLAGS.index(char)
244
+ raise "unknown option in: #{three} (#{char})"
245
+ end
246
+
247
+ options[Join::FLAGS[index]] = true
248
+ end
249
+ options
250
+ end
251
+
252
+ # Parses an arg hash into a schema argv. An arg hash is a hash
253
+ # using numeric keys to specify the [row][col] in a two-dimensional
254
+ # array where a set of values should go. Breaks are added between
255
+ # rows (if necessary) and the array is collapsed to yield the
256
+ # argv:
257
+ #
258
+ # argh = {
259
+ # 0 => {
260
+ # 0 => 'a',
261
+ # 1 => ['b', 'c']},
262
+ # 1 => 'z'
263
+ # }
264
+ # parse_argh(argh) # => ['--', 'a', 'b', 'c', '--', 'z']
265
+ #
266
+ # Non-numeric keys are converted to integers using to_i and
267
+ # existing breaks (such as workflow breaks) occuring at the
268
+ # start of a row are preseved.
269
+ #
270
+ # argh = {
271
+ # '0' => {
272
+ # '0' => 'a',
273
+ # '1' => ['b', 'c']},
274
+ # '1' => ['--:', 'z']
275
+ # }
276
+ # parse_argh(argh) # => ['--', 'a', 'b', 'c', '--:', 'z']
277
+ #
278
+ def parse_argh(argh)
279
+ rows = []
280
+ argh.each_pair do |row, values|
281
+ if values.kind_of?(Hash)
282
+ arry = []
283
+ values.each_pair {|col, value| arry[col.to_i] = value }
284
+ values = arry
285
+ end
286
+
287
+ rows[row.to_i] = values
288
+ end
289
+
290
+ argv = []
291
+ rows.each do |row|
292
+ row = [row].flatten.compact
293
+ if row.empty? || row[0] !~ BREAK
294
+ argv << '--'
295
+ end
296
+ argv.concat row
297
+ end
298
+ argv
299
+ end
300
+ end
301
+
302
+ include Utils
303
+
304
+ # The schema into which nodes are parsed
305
+ attr_reader :schema
306
+
307
+ def initialize(argv=[])
308
+ @current_index = 0
309
+ @schema = Schema.new
310
+ parse(argv)
311
+ end
312
+
313
+ # Iterates through the argv splitting out task and workflow definitions.
314
+ # Task definitions are split out (with configurations) along round and/or
315
+ # workflow break lines. Rounds and workflows are dynamically parsed;
316
+ # tasks may be reassigned to different rounds or workflows by later
317
+ # arguments.
318
+ #
319
+ # Parse is non-destructive to argv. If a string argv is provided, parse
320
+ # splits it into an array using Shellwords; if a hash argv is provided,
321
+ # parse converts it to an array using Parser::Utils#parse_argh.
322
+ #
323
+ def parse(argv)
324
+ parse!(argv.kind_of?(String) ? argv : argv.dup)
325
+ end
326
+
327
+ # Same as parse, but removes parsed args from argv.
328
+ def parse!(argv)
329
+ argv = case argv
330
+ when Array then argv
331
+ when String then Shellwords.shellwords(argv)
332
+ when Hash then parse_argh(argv)
333
+ else argv
334
+ end
335
+ argv.unshift('--')
336
+
337
+ escape = false
338
+ current_argv = schema[current_index].argv
339
+
340
+ while !argv.empty?
341
+ arg = argv.shift
342
+
343
+ # if escaping, add escaped arguments
344
+ # until an escape-end argument
345
+ if escape
346
+ if arg == ESCAPE_END
347
+ escape = false
348
+ else
349
+ current_argv << arg
350
+ end
351
+
352
+ next
353
+ end
354
+
355
+ case arg
356
+ when ESCAPE_BEGIN
357
+ # begin escaping if indicated
358
+ escape = true
359
+
360
+ when END_FLAG
361
+ # break on an end-flag
362
+ break
363
+
364
+ when BREAK
365
+ # a breaking argument was reached:
366
+ # unless the current argv is empty,
367
+ # append and start a new definition
368
+ unless current_argv.empty?
369
+ self.current_index += 1
370
+ current_argv = schema[current_index].argv
371
+ end
372
+
373
+ # parse the break string for any
374
+ # schema modifications
375
+ parse_break($1)
376
+
377
+ else
378
+ # add all other non-breaking args to
379
+ # the current argv; this includes
380
+ # both inputs and configurations
381
+ current_argv << arg
382
+
383
+ end
384
+ end
385
+
386
+ schema
387
+ end
388
+
389
+ def load(argv)
390
+ argv.each do |arg|
391
+ case arg
392
+ when Array
393
+ schema.nodes << arg
394
+ self.current_index += 1
395
+ else
396
+ parse_break(arg)
397
+ end
398
+ end
399
+
400
+ schema
401
+ end
402
+
403
+ protected
404
+
405
+ # The index of the node currently being parsed.
406
+ attr_accessor :current_index # :nodoc:
407
+
408
+ # Returns current_index-1, or raises an error if current_index < 1.
409
+ def previous_index # :nodoc:
410
+ raise ArgumentError, 'there is no previous index' if current_index < 1
411
+ current_index - 1
412
+ end
413
+
414
+ # determines the type of break and modifies self appropriately
415
+ def parse_break(arg) # :nodoc:
416
+ case arg
417
+ when ROUND
418
+ round, indicies = parse_round($2, $5)
419
+ indicies.each {|index| schema[index].round = round }
420
+
421
+ when SEQUENCE
422
+ indicies, options = parse_sequence($1, $3)
423
+ while indicies.length > 1
424
+ schema.set(Joins::Sequence, indicies.shift, indicies[0], options)
425
+ end
426
+
427
+ when INSTANCE then schema[parse_instance($1)].globalize
428
+ when FORK then schema.set(Joins::Fork, *parse_bracket($1, $2, $3))
429
+ when MERGE then schema.set(Joins::Merge, *parse_bracket($1, $2, $3))
430
+ when SYNC_MERGE then schema.set(Joins::SyncMerge, *parse_bracket($1, $2, $3))
431
+ else raise ArgumentError, "invalid break argument: #{arg}"
432
+ end
433
+ end
434
+ end
435
+ end
436
+ end