tap 0.12.4 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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