tap 0.12.4 → 0.17.0

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 (102) hide show
  1. data/History +34 -0
  2. data/README +62 -41
  3. data/bin/tap +36 -40
  4. data/cmd/console.rb +14 -6
  5. data/cmd/manifest.rb +62 -58
  6. data/cmd/run.rb +49 -31
  7. data/doc/API +84 -0
  8. data/doc/Class Reference +83 -115
  9. data/doc/Examples/Command Line +36 -0
  10. data/doc/Examples/Workflow +40 -0
  11. data/lib/tap/app.rb +293 -214
  12. data/lib/tap/app/node.rb +43 -0
  13. data/lib/tap/app/queue.rb +77 -0
  14. data/lib/tap/app/stack.rb +16 -0
  15. data/lib/tap/app/state.rb +22 -0
  16. data/lib/tap/constants.rb +2 -2
  17. data/lib/tap/env.rb +400 -314
  18. data/lib/tap/env/constant.rb +227 -0
  19. data/lib/tap/env/gems.rb +63 -0
  20. data/lib/tap/env/manifest.rb +89 -0
  21. data/lib/tap/env/minimap.rb +292 -0
  22. data/lib/tap/{support → env}/string_ext.rb +2 -2
  23. data/lib/tap/exe.rb +113 -125
  24. data/lib/tap/join.rb +175 -0
  25. data/lib/tap/joins.rb +9 -0
  26. data/lib/tap/joins/switch.rb +44 -0
  27. data/lib/tap/joins/sync.rb +99 -0
  28. data/lib/tap/root.rb +100 -491
  29. data/lib/tap/root/utils.rb +220 -0
  30. data/lib/tap/{support → root}/versions.rb +31 -29
  31. data/lib/tap/schema.rb +248 -0
  32. data/lib/tap/schema/parser.rb +413 -0
  33. data/lib/tap/schema/utils.rb +82 -0
  34. data/lib/tap/support/intern.rb +19 -6
  35. data/lib/tap/support/templater.rb +8 -3
  36. data/lib/tap/task.rb +175 -171
  37. data/lib/tap/tasks/dump.rb +58 -0
  38. data/lib/tap/tasks/load.rb +62 -0
  39. metadata +30 -73
  40. data/cmd/destroy.rb +0 -27
  41. data/cmd/generate.rb +0 -27
  42. data/doc/Command Reference +0 -105
  43. data/doc/Syntax Reference +0 -234
  44. data/doc/Tutorial +0 -348
  45. data/lib/tap/dump.rb +0 -142
  46. data/lib/tap/file_task.rb +0 -384
  47. data/lib/tap/generator/arguments.rb +0 -13
  48. data/lib/tap/generator/base.rb +0 -176
  49. data/lib/tap/generator/destroy.rb +0 -60
  50. data/lib/tap/generator/generate.rb +0 -93
  51. data/lib/tap/generator/generators/command/command_generator.rb +0 -21
  52. data/lib/tap/generator/generators/command/templates/command.erb +0 -32
  53. data/lib/tap/generator/generators/config/config_generator.rb +0 -98
  54. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
  55. data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
  56. data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
  57. data/lib/tap/generator/generators/root/root_generator.rb +0 -84
  58. data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
  59. data/lib/tap/generator/generators/root/templates/README +0 -14
  60. data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
  61. data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
  62. data/lib/tap/generator/generators/root/templates/gemspec +0 -27
  63. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
  64. data/lib/tap/generator/generators/task/task_generator.rb +0 -25
  65. data/lib/tap/generator/generators/task/templates/task.erb +0 -14
  66. data/lib/tap/generator/generators/task/templates/test.erb +0 -19
  67. data/lib/tap/generator/manifest.rb +0 -20
  68. data/lib/tap/generator/preview.rb +0 -69
  69. data/lib/tap/load.rb +0 -64
  70. data/lib/tap/spec.rb +0 -41
  71. data/lib/tap/support/aggregator.rb +0 -65
  72. data/lib/tap/support/audit.rb +0 -333
  73. data/lib/tap/support/constant.rb +0 -143
  74. data/lib/tap/support/constant_manifest.rb +0 -126
  75. data/lib/tap/support/dependencies.rb +0 -54
  76. data/lib/tap/support/dependency.rb +0 -44
  77. data/lib/tap/support/executable.rb +0 -198
  78. data/lib/tap/support/executable_queue.rb +0 -125
  79. data/lib/tap/support/gems.rb +0 -43
  80. data/lib/tap/support/join.rb +0 -144
  81. data/lib/tap/support/joins.rb +0 -12
  82. data/lib/tap/support/joins/switch.rb +0 -27
  83. data/lib/tap/support/joins/sync_merge.rb +0 -38
  84. data/lib/tap/support/manifest.rb +0 -171
  85. data/lib/tap/support/minimap.rb +0 -90
  86. data/lib/tap/support/node.rb +0 -176
  87. data/lib/tap/support/parser.rb +0 -450
  88. data/lib/tap/support/schema.rb +0 -385
  89. data/lib/tap/support/shell_utils.rb +0 -67
  90. data/lib/tap/test.rb +0 -77
  91. data/lib/tap/test/assertions.rb +0 -38
  92. data/lib/tap/test/env_vars.rb +0 -29
  93. data/lib/tap/test/extensions.rb +0 -73
  94. data/lib/tap/test/file_test.rb +0 -362
  95. data/lib/tap/test/file_test_class.rb +0 -15
  96. data/lib/tap/test/regexp_escape.rb +0 -87
  97. data/lib/tap/test/script_test.rb +0 -46
  98. data/lib/tap/test/script_tester.rb +0 -115
  99. data/lib/tap/test/subset_test.rb +0 -260
  100. data/lib/tap/test/subset_test_class.rb +0 -99
  101. data/lib/tap/test/tap_test.rb +0 -109
  102. data/lib/tap/test/utils.rb +0 -231
@@ -0,0 +1,413 @@
1
+ require 'shellwords'
2
+ require 'tap/schema'
3
+
4
+ module Tap
5
+ class Schema
6
+ class << self
7
+ def parse(argv=ARGV)
8
+ Parser.new(argv).schema
9
+ end
10
+ end
11
+
12
+ # A parser for workflow schema defined on the command line.
13
+ #
14
+ # == Syntax
15
+ #
16
+ # The command line syntax can be thought of as a series of ARGV arrays
17
+ # connected by breaks. The arrays define tasks (ie nodes) in a workflow
18
+ # while the breaks define joins. These are the available breaks:
19
+ #
20
+ # break meaning
21
+ # -- default delimiter, no join
22
+ # --: sequence join
23
+ # --[][] multi-join (sequence, fork, merge)
24
+ #
25
+ # As an example, this defines three tasks (a, b, c) and sequences the
26
+ # b and c tasks:
27
+ #
28
+ # schema = Parser.new("a -- b --: c").schema
29
+ # schema.tasks # => [["a"], ["b"], ["c"]]
30
+ # schema.joins # => [['join', [1],[2]]]
31
+ #
32
+ # In the example, the indicies of the tasks participating in the sequence
33
+ # are inferred as the last and next tasks in the schema, and obviously the
34
+ # location of the sequence break is significant. This isn't the case when
35
+ # the tasks in a join are explicitly specified. These both sequence a to
36
+ # b, and b to c.
37
+ #
38
+ # schema = Parser.new("a -- b -- c --0:1 --1:2").schema
39
+ # schema.tasks
40
+ # # => {
41
+ # # 0 => ["a"],
42
+ # # 1 => ["b"],
43
+ # # 2 => ["c"]
44
+ # # }
45
+ # schema.joins
46
+ # # => [
47
+ # # [[0],[1]],
48
+ # # [[1],[2]],
49
+ # # ]
50
+ #
51
+ # schema = Parser.new("a --1:2 --0:1 b -- c").schema
52
+ # schema.tasks
53
+ # # => {
54
+ # # 0 => ["a"],
55
+ # # 1 => ["b"],
56
+ # # 2 => ["c"]
57
+ # # }
58
+ # schema.joins
59
+ # # => [
60
+ # # [[1],[2]],
61
+ # # [[0],[1]],
62
+ # # ]
63
+ #
64
+ # ==== Multi-Join Syntax
65
+ #
66
+ # The multi-join syntax allows the specification of arbitrary joins.
67
+ # Starting with a few examples:
68
+ #
69
+ # example meaning
70
+ # --[][] last.sequence(next)
71
+ # --[1][2] 1.sequence(2)
72
+ # --[1][2,3] 1.fork(2,3)
73
+ # --[1,2][3] 3.merge(1,2)
74
+ #
75
+ # The meaning of the bracket breaks seems to be changing but note that
76
+ # the sequences, forks, and (unsynchronized) merges are all variations
77
+ # of a multi-way join. Internally the breaks are interpreted like this:
78
+ #
79
+ # join = Join.new
80
+ # join.join(inputs, outputs)
81
+ #
82
+ # To specify another class of join, or to specify join configurations,
83
+ # add a string in the format "configs.class" where the configs are the
84
+ # single-letter configuration flags and class is a lookup for the join
85
+ # class.
86
+ #
87
+ # example interpretation
88
+ # --:s Join.new(:splat => true)
89
+ # --1:2is Join.new(:iterate => true, :splat => true)
90
+ # --[][]q.sync Sync.new(:enq => true)
91
+ # --[][].sync Sync.new
92
+ #
93
+ # If you can stand the syntax, you can also specify a full argv after
94
+ # the bracket, just be sure to enclose the whole break in quotes.
95
+ #
96
+ # example interpretation
97
+ # "--1:2 join -i -s" Join.new(:iterate => true, :splat => true)
98
+ # "--[][] sync --enq" Sync.new(:enq => true)
99
+ #
100
+ # ==== Escapes and End Flags
101
+ #
102
+ # Breaks can be escaped by enclosing them in '-.' and '.-' delimiters;
103
+ # any number of arguments may be enclosed within the escape. After the
104
+ # end delimiter, breaks are active once again.
105
+ #
106
+ # schema = Parser.new("a -- b -- c").schema
107
+ # schema.tasks
108
+ # # => {
109
+ # # 0 => ["a"],
110
+ # # 1 => ["b"],
111
+ # # 2 => ["c"]
112
+ # # }
113
+ #
114
+ # schema = Parser.new("a -. -- b .- -- c").schema
115
+ # schema.tasks
116
+ # # => {
117
+ # # 0 => ["a", "--", "b"],
118
+ # # 1 => ["c"]
119
+ # # }
120
+ #
121
+ # Parsing continues until the end of argv, or a an end flag '---' is
122
+ # reached. The end flag may also be escaped.
123
+ #
124
+ # schema = Parser.new("a -- b --- c").schema
125
+ # schema.tasks
126
+ # # => {
127
+ # # 0 => ["a"],
128
+ # # 1 => ["b"]
129
+ # # }
130
+ #
131
+ class Parser
132
+
133
+ # A set of parsing routines used internally by Tap::Schema::Parser,
134
+ # modularized for ease of testing, and potential re-use. These methods
135
+ # require that <tt>current_index</tt> and <tt>previous_index</tt> be
136
+ # implemented in the including class.
137
+ module Utils
138
+ module_function
139
+
140
+ # The escape begin argument
141
+ ESCAPE_BEGIN = "-."
142
+
143
+ # The escape end argument
144
+ ESCAPE_END = ".-"
145
+
146
+ # The parser end flag
147
+ END_FLAG = "---"
148
+
149
+ # Matches any breaking arg. Examples:
150
+ #
151
+ # --
152
+ # --+
153
+ # --1:2
154
+ # --[1][2]
155
+ # --[1,2,3][4,5,6]is.join
156
+ #
157
+ # After the match:
158
+ #
159
+ # $1:: The string after the break
160
+ # (ex: '--' => '', '--:' => ':', '--[1,2][3,4]is.join' => '[1,2][3,4]is.join')
161
+ #
162
+ BREAK = /\A--(\z|[\d\:\[].*\z)/
163
+
164
+ # Matches a sequence break. Examples:
165
+ #
166
+ # :
167
+ # 1:
168
+ # :2
169
+ # 1:2:3
170
+ #
171
+ # After the match:
172
+ #
173
+ # $1:: The sequence string after the break.
174
+ # (ex: ':' => ':', '1:2' => '1:2', '1:' => '1:', ':2' => ':2')
175
+ # $2:: The modifier string.
176
+ # (ex: ':i' => 'i', '1:2is' => 'is')
177
+ #
178
+ SEQUENCE = /\A(\d*(?::\d*)+)(.*)\z/
179
+
180
+ # Matches a generic join break. Examples:
181
+ #
182
+ # "[1,2,3][4,5,6] join -i -s"
183
+ # [1,2,3][4,5,6]is.join
184
+ # [1,2][3,4]
185
+ # [1][2]
186
+ #
187
+ # After the match:
188
+ #
189
+ # $1:: The inputs string.
190
+ # (ex: '[1,2,3][4,5,6]' => '1,2,3')
191
+ # $2:: The outputs string.
192
+ # (ex: '[1,2,3][4,5,6]' => '4,5,6')
193
+ # $3:: The modifier string.
194
+ # (ex: '[][]is' => 'is')
195
+ #
196
+ JOIN = /\A\[([\d,]*)\]\[([\d,]*)\](.*)\z/
197
+
198
+ # Matches a join modifier. After the match:
199
+ #
200
+ # $1:: The modifier flag string.
201
+ # (ex: 'is.sync' => 'is')
202
+ # $2:: The class string.
203
+ # (ex: 'is.sync' => 'sync')
204
+ #
205
+ JOIN_MODIFIER = /\A([A-z]*)(?:\.(.*))?\z/
206
+
207
+ # Parses an indicies str along commas, and collects the indicies
208
+ # as integers. Ex:
209
+ #
210
+ # parse_indicies('') # => []
211
+ # parse_indicies('1') # => [1]
212
+ # parse_indicies('1,2,3') # => [1,2,3]
213
+ #
214
+ def parse_indicies(str, regexp=/,+/)
215
+ indicies = []
216
+ str.split(regexp).each do |n|
217
+ indicies << n.to_i unless n.empty?
218
+ end
219
+ indicies
220
+ end
221
+
222
+ # Parses the match of a SEQUENCE regexp an array of [input_indicies,
223
+ # output_indicies, metadata] arrays. The inputs corresponds to $1 and
224
+ # $2 for the match. The previous and current index are assumed if $1
225
+ # starts and/or ends with a semi-colon.
226
+ #
227
+ # parse_sequence("1:2:3", '')
228
+ # # => [
229
+ # # [[1], [2]],
230
+ # # [[2], [3]],
231
+ # # ]
232
+ #
233
+ # parse_sequence(":1:2:", 'is')
234
+ # # => [
235
+ # # [[:previous_index], [1], ['join', '-i', '-s']],
236
+ # # [[1], [2], ['join', '-i', '-s']]],
237
+ # # [[2], [:current_index], ['join', '-i', '-s']],
238
+ # # ]
239
+ #
240
+ def parse_sequence(one, two)
241
+ indicies = parse_indicies(one, /:+/)
242
+ indicies.unshift previous_index if one[0] == ?:
243
+ indicies << current_index if one[-1] == ?:
244
+
245
+ sequences = []
246
+ while indicies.length > 1
247
+ sequences << [[indicies.shift], [indicies[0]]]
248
+ end
249
+
250
+ if argv = parse_join_modifier(two)
251
+ sequences.each do |sequence|
252
+ sequence << argv
253
+ end
254
+ end
255
+
256
+ sequences
257
+ end
258
+
259
+ # Parses the match of a JOIN regexp into a [input_indicies,
260
+ # output_indicies, metadata] array. The inputs corresponds to $1, $2,
261
+ # and $3 for a match to a JOIN regexp. A join type of 'join' is
262
+ # assumed unless otherwise specified.
263
+ #
264
+ # parse_join("1", "2,3", "") # => [[1], [2,3]]
265
+ # parse_join("", "", "is.type") # => [[], [], ['type', '-i', '-s']]
266
+ # parse_join("", "", "type -i -s") # => [[], [], ['type', '-i', '-s']]
267
+ #
268
+ def parse_join(one, two, three)
269
+ join = [parse_indicies(one), parse_indicies(two)]
270
+
271
+ if argv = parse_join_modifier(three)
272
+ join << argv
273
+ end
274
+
275
+ join
276
+ end
277
+
278
+ # Parses a join modifier string into an argv.
279
+ def parse_join_modifier(modifier)
280
+ case modifier
281
+ when ""
282
+ nil
283
+ when JOIN_MODIFIER
284
+ argv = [$2 == nil || $2.empty? ? 'join' : $2]
285
+ $1.split("").each {|char| argv << "-#{char}"}
286
+ argv
287
+ else
288
+ Shellwords.shellwords(modifier)
289
+ end
290
+ end
291
+ end
292
+
293
+ include Utils
294
+
295
+ # The schema into which tasks are being parsed
296
+ attr_reader :schema
297
+
298
+ def initialize(argv=[])
299
+ parse(argv)
300
+ end
301
+
302
+ # Iterates through the argv splitting out task and join definitions.
303
+ # Parse is non-destructive to argv. If a string argv is provided, parse
304
+ # splits it into an array using Shellwords; if a hash argv is provided,
305
+ # parse converts it to an array using Parser::Utils#parse_argh.
306
+ def parse(argv)
307
+ parse!(argv.kind_of?(String) ? argv : argv.dup)
308
+ end
309
+
310
+ # Same as parse, but removes parsed args from argv.
311
+ def parse!(argv)
312
+ @current_index = 0
313
+ @schema = Schema.new
314
+
315
+ # prevent the addition of an empty task to schema
316
+ return if argv.empty?
317
+
318
+ argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
319
+ argv.unshift('--')
320
+
321
+ escape = false
322
+ current_task = nil
323
+ while !argv.empty?
324
+ arg = argv.shift
325
+
326
+ # if escaping, add escaped arguments
327
+ # until an escape-end argument
328
+ if escape
329
+ if arg == ESCAPE_END
330
+ escape = false
331
+ else
332
+ (current_task ||= task(current_index)) << arg
333
+ end
334
+
335
+ next
336
+ end
337
+
338
+ case arg
339
+ when ESCAPE_BEGIN
340
+ # begin escaping if indicated
341
+ escape = true
342
+
343
+ when END_FLAG
344
+ # break on an end-flag
345
+ break
346
+
347
+ when BREAK
348
+ # a breaking argument was reached:
349
+ # unless the current argv is empty,
350
+ # append and start a new definition
351
+ if current_task && !current_task.empty?
352
+ self.current_index += 1
353
+ current_task = nil
354
+ end
355
+
356
+ # parse the break string for any
357
+ # schema modifications
358
+ parse_break($1)
359
+
360
+ else
361
+ # add all other non-breaking args to
362
+ # the current argv; this includes
363
+ # both inputs and configurations
364
+ (current_task ||= task(current_index)) << arg
365
+
366
+ end
367
+ end
368
+
369
+ schema
370
+ end
371
+
372
+ protected
373
+
374
+ # The index of the task currently being parsed.
375
+ attr_accessor :current_index # :nodoc:
376
+
377
+ # helper to initialize a task at the specified index
378
+ def task(index) # :nodoc:
379
+ schema.tasks[index] ||= []
380
+ end
381
+
382
+ # returns current_index-1, or raises an error if current_index < 1.
383
+ def previous_index # :nodoc:
384
+ current_index - 1
385
+ end
386
+
387
+ # determines the type of break and modifies self appropriately
388
+ def parse_break(arg) # :nodoc:
389
+ case arg
390
+ when ""
391
+ unless schema.queue.include?(current_index)
392
+ schema.queue << current_index
393
+ end
394
+ when SEQUENCE
395
+ parse_sequence($1, $2).each {|join| set_join(join) }
396
+ when JOIN
397
+ set_join(parse_join($1, $2, $3))
398
+ else
399
+ raise ArgumentError, "invalid break argument: #{arg}"
400
+ end
401
+ end
402
+
403
+ # constructs the specified join and removes the targets of the
404
+ # join from the queue
405
+ def set_join(join) # :nodoc:
406
+ join[1].each do |output|
407
+ schema.queue.delete(output)
408
+ end
409
+ schema.joins << join
410
+ end
411
+ end
412
+ end
413
+ end
@@ -0,0 +1,82 @@
1
+ module Tap
2
+ class Schema
3
+ module Utils
4
+ module_function
5
+
6
+ def instantiate(data, app)
7
+ case data
8
+ when Hash then data['class'].instantiate(symbolize(data), app)
9
+ when Array then data.shift.parse(data, app)
10
+ end
11
+ end
12
+
13
+ def resolved?(data)
14
+ case data
15
+ when Hash then data['class'].respond_to?(:instantiate)
16
+ when Array then data[0].respond_to?(:parse)
17
+ else false
18
+ end
19
+ end
20
+
21
+ def resolve(data)
22
+ return data if resolved?(data)
23
+
24
+ case data
25
+ when Hash
26
+ data['class'] = yield(data['id'], data)
27
+ when Array
28
+ data[0] = yield(data[0], data)
29
+ end
30
+
31
+ data
32
+ end
33
+
34
+ # Symbolizes the keys of hash. Returns non-hash values directly and
35
+ # raises an error in the event of a symbolize conflict.
36
+ def symbolize(hash)
37
+ return hash unless hash.kind_of?(Hash)
38
+
39
+ result = {}
40
+ hash.each_pair do |key, value|
41
+ key = key.to_sym || key
42
+
43
+ if result.has_key?(key)
44
+ raise "symbolize conflict: #{hash.inspect} (#{key.inspect})"
45
+ end
46
+
47
+ result[key] = value
48
+ end
49
+ result
50
+ end
51
+
52
+ # Returns the values for hash sorted by key. Returns non-hash objects
53
+ # directly.
54
+ def dehashify(obj)
55
+ case obj
56
+ when nil then []
57
+ when Hash then obj.keys.sort.collect {|key| obj[key] }
58
+ else obj
59
+ end
60
+ end
61
+
62
+ # Returns obj as a hash, using the index of each element as the
63
+ # key for the element. The object must respond to each. Returns
64
+ # hashes directly.
65
+ def hashify(obj)
66
+ case obj
67
+ when nil then {}
68
+ when Hash then obj
69
+ else
70
+ index = 0
71
+ hash = {}
72
+ obj.each do |entry|
73
+ hash[index] = entry
74
+ index += 1
75
+ end
76
+ hash
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end