tap 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/History +35 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README +16 -15
  4. data/bin/tap +1 -1
  5. data/cmd/console.rb +4 -3
  6. data/cmd/manifest.rb +2 -2
  7. data/cmd/run.rb +12 -15
  8. data/doc/Class Reference +120 -117
  9. data/doc/Command Reference +27 -27
  10. data/doc/Syntax Reference +55 -111
  11. data/doc/Tutorial +69 -26
  12. data/lib/tap.rb +3 -8
  13. data/lib/tap/app.rb +122 -146
  14. data/lib/tap/constants.rb +2 -2
  15. data/lib/tap/env.rb +178 -252
  16. data/lib/tap/exe.rb +67 -30
  17. data/lib/tap/file_task.rb +224 -411
  18. data/lib/tap/generator/arguments.rb +13 -0
  19. data/lib/tap/generator/base.rb +112 -30
  20. data/lib/tap/generator/destroy.rb +36 -13
  21. data/lib/tap/generator/generate.rb +69 -48
  22. data/lib/tap/generator/generators/command/templates/command.erb +3 -3
  23. data/lib/tap/generator/generators/config/config_generator.rb +82 -10
  24. data/lib/tap/generator/generators/generator/generator_generator.rb +16 -6
  25. data/lib/tap/generator/generators/generator/templates/task.erb +2 -2
  26. data/lib/tap/generator/generators/generator/templates/test.erb +26 -0
  27. data/lib/tap/generator/generators/root/root_generator.rb +24 -13
  28. data/lib/tap/generator/generators/root/templates/Rakefile +4 -4
  29. data/lib/tap/generator/generators/root/templates/{tapfile → Rapfile} +6 -6
  30. data/lib/tap/generator/generators/root/templates/gemspec +0 -1
  31. data/lib/tap/generator/generators/task/task_generator.rb +3 -3
  32. data/lib/tap/generator/generators/task/templates/test.erb +1 -1
  33. data/lib/tap/generator/manifest.rb +7 -1
  34. data/lib/tap/generator/preview.rb +76 -0
  35. data/lib/tap/root.rb +222 -156
  36. data/lib/tap/spec.rb +41 -0
  37. data/lib/tap/support/aggregator.rb +25 -28
  38. data/lib/tap/support/audit.rb +278 -357
  39. data/lib/tap/support/constant.rb +2 -1
  40. data/lib/tap/support/constant_manifest.rb +28 -25
  41. data/lib/tap/support/dependency.rb +1 -1
  42. data/lib/tap/support/executable.rb +52 -183
  43. data/lib/tap/support/executable_queue.rb +50 -20
  44. data/lib/tap/support/gems.rb +1 -1
  45. data/lib/tap/support/intern.rb +0 -6
  46. data/lib/tap/support/join.rb +49 -83
  47. data/lib/tap/support/joins.rb +0 -3
  48. data/lib/tap/support/joins/switch.rb +13 -11
  49. data/lib/tap/support/joins/sync_merge.rb +25 -50
  50. data/lib/tap/support/manifest.rb +1 -0
  51. data/lib/tap/support/node.rb +140 -20
  52. data/lib/tap/support/parser.rb +56 -42
  53. data/lib/tap/support/schema.rb +183 -157
  54. data/lib/tap/support/templater.rb +9 -1
  55. data/lib/tap/support/versions.rb +39 -0
  56. data/lib/tap/task.rb +150 -177
  57. data/lib/tap/tasks/dump.rb +4 -4
  58. data/lib/tap/tasks/load.rb +29 -29
  59. data/lib/tap/test.rb +66 -53
  60. data/lib/tap/test/env_vars.rb +3 -3
  61. data/lib/tap/test/extensions.rb +11 -17
  62. data/lib/tap/test/file_test.rb +74 -132
  63. data/lib/tap/test/file_test_class.rb +4 -1
  64. data/lib/tap/test/regexp_escape.rb +2 -2
  65. data/lib/tap/test/script_test.rb +2 -2
  66. data/lib/tap/test/subset_test.rb +6 -6
  67. data/lib/tap/test/tap_test.rb +28 -154
  68. metadata +30 -51
  69. data/bin/rap +0 -118
  70. data/cgi/run.rb +0 -97
  71. data/lib/tap/declarations.rb +0 -229
  72. data/lib/tap/generator/generators/config/templates/doc.erb +0 -12
  73. data/lib/tap/generator/generators/config/templates/nodoc.erb +0 -8
  74. data/lib/tap/generator/generators/file_task/file_task_generator.rb +0 -27
  75. data/lib/tap/generator/generators/file_task/templates/file.txt +0 -11
  76. data/lib/tap/generator/generators/file_task/templates/result.yml +0 -6
  77. data/lib/tap/generator/generators/file_task/templates/task.erb +0 -33
  78. data/lib/tap/generator/generators/file_task/templates/test.erb +0 -29
  79. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +0 -5
  80. data/lib/tap/patches/optparse/summarize.rb +0 -62
  81. data/lib/tap/support/assignments.rb +0 -173
  82. data/lib/tap/support/class_configuration.rb +0 -182
  83. data/lib/tap/support/combinator.rb +0 -125
  84. data/lib/tap/support/configurable.rb +0 -113
  85. data/lib/tap/support/configurable_class.rb +0 -271
  86. data/lib/tap/support/configuration.rb +0 -170
  87. data/lib/tap/support/gems/rake.rb +0 -111
  88. data/lib/tap/support/instance_configuration.rb +0 -173
  89. data/lib/tap/support/joins/fork.rb +0 -19
  90. data/lib/tap/support/joins/merge.rb +0 -22
  91. data/lib/tap/support/joins/sequence.rb +0 -21
  92. data/lib/tap/support/lazy_attributes.rb +0 -45
  93. data/lib/tap/support/lazydoc.rb +0 -386
  94. data/lib/tap/support/lazydoc/comment.rb +0 -503
  95. data/lib/tap/support/lazydoc/config.rb +0 -17
  96. data/lib/tap/support/lazydoc/definition.rb +0 -36
  97. data/lib/tap/support/lazydoc/document.rb +0 -152
  98. data/lib/tap/support/lazydoc/method.rb +0 -24
  99. data/lib/tap/support/tdoc.rb +0 -409
  100. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -38
  101. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -42
  102. data/lib/tap/support/validation.rb +0 -479
  103. data/lib/tap/tasks/rake.rb +0 -57
@@ -1,6 +1,3 @@
1
- require 'tap/support/schema'
2
- autoload(:Shellwords, 'shellwords')
3
-
4
1
  module Tap
5
2
  module Support
6
3
 
@@ -20,7 +17,8 @@ module Tap
20
17
  # final argument reassigns e to round 3.
21
18
  #
22
19
  # schema = Parser.new("a -- b --+ c -- d -- e --+3[4]").schema
23
- # schema.rounds(true) # => [[0,1,3],[2], nil, [4]]
20
+ # a, b, c, d, e = schema.nodes
21
+ # schema.rounds # => [[a,b,d],[c], nil, [e]]
24
22
  #
25
23
  # ==== Workflow Assignment
26
24
  # All simple workflow patterns except switch can be specified within
@@ -49,16 +47,19 @@ module Tap
49
47
  # workflow until the final argument which sequenced b and c.
50
48
  #
51
49
  # 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}]]
50
+ # a, b, c = schema.nodes
51
+ # schema.joins.collect do |join, inputs, outputs|
52
+ # [join.options, inputs, outputs]
53
+ # end # => [[{},[a],[b]], [{:iterate => true},[b],[c]]]
54
54
  #
55
55
  # ==== Globals
56
56
  # Global instances of task (used, for example, by dependencies) may
57
57
  # be assigned in the parse syntax as well. The break for a global
58
58
  # is '--*'.
59
59
  #
60
- # schema = Parser.new("a -- b --* global_name --config for --global").schema
61
- # schema.globals(true) # => [2]
60
+ # schema = Parser.new("a -- b --* c").schema
61
+ # a, b, c = schema.nodes
62
+ # schema.globals # => [c]
62
63
  #
63
64
  # ==== Escapes and End Flags
64
65
  # Breaks can be escaped by enclosing them in '-.' and '.-' delimiters;
@@ -167,12 +168,12 @@ module Tap
167
168
  indicies
168
169
  end
169
170
 
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.
171
+ # Parses the match of a ROUND regexp into a round index and an array
172
+ # of task indicies that should be added to the round. The inputs
173
+ # correspond to $2 and $5 for the match.
173
174
  #
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.
175
+ # If $2 is nil, a round index of zero is assumed; if $5 is nil or
176
+ # empty, then indicies of [:current_index] are assumed.
176
177
  #
177
178
  # parse_round("+", "") # => [1, [:current_index]]
178
179
  # parse_round("+2", "1,2,3") # => [2, [1,2,3]]
@@ -213,20 +214,26 @@ module Tap
213
214
  one.empty? ? current_index : one.to_i
214
215
  end
215
216
 
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.
217
+ # Parses the match of an bracket_regexp into a [input_indicies,
218
+ # output_indicies, options] array. The inputs corresponds to $1,
219
+ # $2, and $3 for a match to a bracket regexp. The previous and
220
+ # current index are assumed if $1 and/or $2 is empty.
220
221
  #
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], {}]
222
+ # parse_bracket("1", "2,3", "") # => [[1], [2,3], {}]
223
+ # parse_bracket("", "", "") # => [[:previous_index], [:current_index], {}]
224
+ # parse_bracket("1", "", "") # => [[1], [:current_index], {}]
225
+ # parse_bracket("", "2,3", "") # => [[:previous_index], [2,3], {}]
225
226
  #
226
227
  def parse_bracket(one, two, three)
227
228
  targets = parse_indicies(two)
228
229
  targets << current_index if targets.empty?
229
- [one.empty? ? previous_index : one.to_i, targets, parse_options(three)]
230
+ [[one.empty? ? previous_index : one.to_i], targets, parse_options(three)]
231
+ end
232
+
233
+ # Same as parse_bracket but reverses the input and output indicies.
234
+ def parse_reverse_bracket(one, two, three)
235
+ inputs, outputs, options = parse_bracket(one, two, three)
236
+ [outputs, inputs, options]
230
237
  end
231
238
 
232
239
  # Parses an options string into a hash. The input corresponds
@@ -234,17 +241,20 @@ module Tap
234
241
  # if the options string contains unknown options.
235
242
  #
236
243
  # parse_options("") # => {}
237
- # parse_options("is") # => {:iterate => true, :stack => true}
244
+ # parse_options("ik") # => {:iterate => true, :stack => true}
238
245
  #
239
246
  def parse_options(three)
240
247
  options = {}
241
248
  0.upto(three.length - 1) do |char_index|
242
249
  char = three[char_index, 1]
243
- unless index = Join::SHORT_FLAGS.index(char)
244
- raise "unknown option in: #{three} (#{char})"
250
+
251
+ entry = Join.configurations.find do |key, config|
252
+ config.attributes[:short] == char
245
253
  end
254
+ key, config = entry
246
255
 
247
- options[Join::FLAGS[index]] = true
256
+ raise "unknown option in: #{three} (#{char})" unless key
257
+ options[key] = true
248
258
  end
249
259
  options
250
260
  end
@@ -326,6 +336,9 @@ module Tap
326
336
 
327
337
  # Same as parse, but removes parsed args from argv.
328
338
  def parse!(argv)
339
+ # prevent the addition of an empty node to schema
340
+ return if argv.empty?
341
+
329
342
  argv = case argv
330
343
  when Array then argv
331
344
  when String then Shellwords.shellwords(argv)
@@ -335,8 +348,7 @@ module Tap
335
348
  argv.unshift('--')
336
349
 
337
350
  escape = false
338
- current_argv = schema[current_index].argv
339
-
351
+ current_argv = nil
340
352
  while !argv.empty?
341
353
  arg = argv.shift
342
354
 
@@ -346,7 +358,7 @@ module Tap
346
358
  if arg == ESCAPE_END
347
359
  escape = false
348
360
  else
349
- current_argv << arg
361
+ (current_argv ||= schema[current_index].argv) << arg
350
362
  end
351
363
 
352
364
  next
@@ -365,9 +377,9 @@ module Tap
365
377
  # a breaking argument was reached:
366
378
  # unless the current argv is empty,
367
379
  # append and start a new definition
368
- unless current_argv.empty?
380
+ if current_argv && !current_argv.empty?
369
381
  self.current_index += 1
370
- current_argv = schema[current_index].argv
382
+ current_argv = nil
371
383
  end
372
384
 
373
385
  # parse the break string for any
@@ -378,26 +390,28 @@ module Tap
378
390
  # add all other non-breaking args to
379
391
  # the current argv; this includes
380
392
  # both inputs and configurations
381
- current_argv << arg
393
+ (current_argv ||= schema[current_index].argv) << arg
382
394
 
383
395
  end
384
396
  end
385
-
397
+
386
398
  schema
387
399
  end
388
400
 
389
401
  def load(argv)
390
- argv.each do |arg|
391
- case arg
402
+ argv.each do |args|
403
+ case args
392
404
  when Array
393
- schema.nodes << arg
405
+ schema.nodes << Node.new(args, 0)
394
406
  self.current_index += 1
395
407
  else
396
- parse_break(arg)
408
+ parse_break(args)
397
409
  end
398
410
  end
399
411
 
400
- schema
412
+ # cleanup is currently required if terminal joins like
413
+ # --0[] are allowed (since it's interpreted as --0[next])
414
+ schema.cleanup
401
415
  end
402
416
 
403
417
  protected
@@ -421,13 +435,13 @@ module Tap
421
435
  when SEQUENCE
422
436
  indicies, options = parse_sequence($1, $3)
423
437
  while indicies.length > 1
424
- schema.set(Joins::Sequence, indicies.shift, indicies[0], options)
438
+ schema.set(Join, [indicies.shift], [indicies[0]], options)
425
439
  end
426
440
 
427
441
  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))
442
+ when FORK then schema.set(Join, *parse_bracket($1, $2, $3))
443
+ when MERGE then schema.set(Join, *parse_reverse_bracket($1, $2, $3))
444
+ when SYNC_MERGE then schema.set(Joins::SyncMerge, *parse_reverse_bracket($1, $2, $3))
431
445
  else raise ArgumentError, "invalid break argument: #{arg}"
432
446
  end
433
447
  end
@@ -42,8 +42,8 @@ module Tap
42
42
  #
43
43
  # format_sequence(1, [2,3], {}) # => "1:2:3"
44
44
  #
45
- def format_sequence(source_index, target_indicies, options)
46
- ([source_index] + target_indicies).join(":") + format_options(options)
45
+ def format_sequence(source_index, outputs, options)
46
+ ([source_index] + outputs).join(":") + format_options(options)
47
47
  end
48
48
 
49
49
  # Formats a global instance string.
@@ -58,8 +58,8 @@ module Tap
58
58
  #
59
59
  # format_fork(1, [2,3],{}) # => "1[2,3]"
60
60
  #
61
- def format_fork(source_index, target_indicies, options)
62
- "#{source_index}[#{target_indicies.join(',')}]#{format_options(options)}"
61
+ def format_fork(source_index, outputs, options)
62
+ "#{source_index[0]}[#{outputs.join(',')}]#{format_options(options)}"
63
63
  end
64
64
 
65
65
  # Formats a merge string (note the target index is
@@ -67,8 +67,8 @@ module Tap
67
67
  #
68
68
  # format_merge(1, [2,3],{}) # => "1{2,3}"
69
69
  #
70
- def format_merge(target_index, source_indicies, options)
71
- "#{target_index}{#{source_indicies.join(',')}}#{format_options(options)}"
70
+ def format_merge(target_index, inputs, options)
71
+ "#{target_index[0]}{#{inputs.join(',')}}#{format_options(options)}"
72
72
  end
73
73
 
74
74
  # Formats a sync_merge string (note the target index
@@ -76,8 +76,8 @@ module Tap
76
76
  #
77
77
  # format_sync_merge(1, [2,3],{}) # => "1(2,3)"
78
78
  #
79
- def format_sync_merge(target_index, source_indicies, options)
80
- "#{target_index}(#{source_indicies.join(',')})#{format_options(options)}"
79
+ def format_sync_merge(target_index, inputs, options)
80
+ "#{target_index[0]}(#{inputs.join(',')})#{format_options(options)}"
81
81
  end
82
82
 
83
83
  # Formats an options hash into a string. Raises an error
@@ -88,12 +88,12 @@ module Tap
88
88
  def format_options(options)
89
89
  options_str = []
90
90
  options.each_pair do |key, value|
91
- unless index = Join::FLAGS.index(key)
91
+ unless config = Join.configurations[key]
92
92
  raise "unknown key in: #{options} (#{key})"
93
93
  end
94
94
 
95
95
  if value
96
- options_str << Join::SHORT_FLAGS[index]
96
+ options_str << config.attributes[:short]
97
97
  end
98
98
  end
99
99
  options_str.sort.join
@@ -112,59 +112,39 @@ module Tap
112
112
  parser.load(argv)
113
113
  parser.schema
114
114
  end
115
-
115
+
116
+ # Loads a schema from the specified path. Raises an error if no such
117
+ # file existts.
116
118
  def load_file(path)
117
119
  argv = YAML.load_file(path)
118
- load(argv)
120
+ argv ? load(argv) : new
119
121
  end
120
122
  end
121
123
 
122
124
  # An array of the nodes registered in self.
123
125
  attr_reader :nodes
124
-
126
+
125
127
  def initialize(nodes=[])
126
128
  @nodes = nodes
127
- @current_index = 1
128
129
  end
129
130
 
130
- # Retrieves the node at index, or instantiates
131
- # a new Node if one does not already exists.
131
+ # Retrieves the node at index, or instantiates a new Node if one does
132
+ # not already exists.
132
133
  def [](index)
133
134
  nodes[index] ||= Node.new
134
135
  end
135
136
 
136
- # Sets a join between the source and targets.
137
- # Returns the new join.
138
- def set(join_class, source_indicies, target_indicies, options={})
139
- join = join_class.new(options)
140
-
141
- [*source_indicies].each {|source_index| self[source_index].output = join }
142
- [*target_indicies].each {|target_index| self[target_index].input = join }
143
-
144
- join
137
+ # Returns the index of the node in nodes.
138
+ def index(node)
139
+ nodes.index(node)
145
140
  end
146
141
 
147
- # Removes all nil nodes, and nodes with empty argvs.
148
- # Additionally reassigns rounds by shifting later
149
- # rounds up to fill any nils in the rounds array.
150
- #
151
- # Returns self.
152
- def compact
153
- # remove nil and empty nodes
154
- nodes.delete_if do |node|
155
- node == nil || node.argv.empty?
156
- end
157
-
158
- # reassign rounds
159
- index = 0
160
- rounds.compact.each do |round|
161
- round.each {|node| node.round = index }
162
- index += 1
163
- end
164
-
165
- self
142
+ # Shortcut to collect the indicies of each node in nodes. Returns nil if
143
+ # nodes is nil.
144
+ def indicies(nodes)
145
+ nodes ? nodes.collect {|node| index(node) } : nodes
166
146
  end
167
-
147
+
168
148
  # Returns an array of the argvs for each nodes.
169
149
  def argvs
170
150
  nodes.collect do |node|
@@ -172,72 +152,126 @@ module Tap
172
152
  end
173
153
  end
174
154
 
175
- # Returns a collection of nodes sorted
176
- # into arrays by node.round.
177
- def rounds(as_indicies=false)
155
+ # Returns a collection of nodes sorted into arrays by round.
156
+ def rounds
178
157
  rounds = []
179
158
  nodes.each do |node|
180
- (rounds[node.round] ||= []) << node if node && node.round
159
+ next unless node
160
+ round = node.round
161
+ (rounds[round] ||= []) << node if round
181
162
  end
182
-
183
- rounds.each do |round|
184
- next unless round
185
- round.collect! {|node| nodes.index(node) }
186
- end if as_indicies
187
-
188
163
  rounds
189
164
  end
190
165
 
191
- # Returns a collection of global nodes
192
- # (nodes with no input or output set).
193
- def globals(as_indicies=false)
166
+ # Returns a collection of nodes sorted into arrays by natural round.
167
+ def natural_rounds
168
+ rounds = []
169
+ nodes.each do |node|
170
+ next unless node
171
+ round = node.natural_round
172
+ (rounds[round] ||= []) << node if round
173
+ end
174
+ rounds
175
+ end
176
+
177
+ # Returns a collection of global nodes.
178
+ def globals
194
179
  globals = []
195
180
  nodes.each do |node|
196
- globals << node if node && node.global?
181
+ if node && node.global?
182
+ globals << node
183
+ end
197
184
  end
198
-
199
- globals.collect! do |node|
200
- nodes.index(node)
201
- end if as_indicies
202
-
203
185
  globals
204
186
  end
205
187
 
206
- # Returns a hash of [join, [source_node, target_nodes]] pairs
207
- # across all nodes.
208
- def joins(as_indicies=false)
209
- joins = {}
188
+ # Returns an array of joins among nodes in self.
189
+ def joins
190
+ joins = []
191
+
210
192
  nodes.each do |node|
211
193
  next unless node
212
-
213
- case node.input
214
- when Join, ReverseJoin
215
- (joins[node.input] ||= [nil,[]])[1] << node
216
- end
217
-
218
- case node.output
219
- when Join, ReverseJoin
220
- (joins[node.output] ||= [nil,[]])[0] = node
194
+ if join = node.output_join
195
+ joins << join
221
196
  end
222
197
  end
223
198
 
224
- if as_indicies
225
- summary = []
226
- joins.each_pair do |join, (source_node, target_nodes)|
227
- target_indicies = target_nodes.collect {|node| nodes.index(node) }
228
- summary << [join.name, nodes.index(source_node), target_indicies, join.options]
199
+ nodes.each do |node|
200
+ next unless node
201
+ if join = node.input_join
202
+ joins << join
229
203
  end
204
+ end
205
+
206
+ joins.uniq
207
+ end
208
+
209
+ # Sets a join between the nodes at the input and output indicies.
210
+ # Returns the new join.
211
+ def set(join_class, inputs, outputs, options={})
212
+ unless inputs && !inputs.empty?
213
+ raise ArgumentError, "no input nodes specified"
214
+ end
215
+
216
+ join = [join_class.new(options), [],[]]
217
+
218
+ inputs.each {|index| self[index].output = join }
219
+ outputs.each {|index| self[index].input = join }
220
+
221
+ join
222
+ end
223
+
224
+ # Removes all nil nodes, nodes with empty argvs, and orphaned joins.
225
+ # Additionally reassigns rounds by shifting later rounds up to fill
226
+ # any nils in the rounds array.
227
+ #
228
+ # Returns self.
229
+ #
230
+ #--
231
+ # Note: the algorithm for cleaning up joins can likely be optimized.
232
+ def cleanup
233
+ # remove nil and empty nodes
234
+ nodes.delete_if do |node|
235
+ node == nil || node.argv.empty?
236
+ end
237
+
238
+ # cleanup joins
239
+ joins.each do |join, input_nodes, output_nodes|
230
240
 
231
- summary.sort_by {|entry| entry[1] || -1 }
232
- else
233
- joins
241
+ # remove missing output nodes
242
+ output_nodes.delete_if {|node| !nodes.include?(node) }
243
+
244
+ # remove missing input nodes; the removed nodes need
245
+ # to be preserved in case an orphan join results and
246
+ # the natural round before cleanup needs to be
247
+ # determined.
248
+ remaining_nodes, removed_nodes = input_nodes.partition {|node| nodes.include?(node) }
249
+
250
+ case
251
+ when remaining_nodes.empty?
252
+ # orphan join: reassign output nodes to natural round
253
+ orphan_round = Node.natural_round(removed_nodes)
254
+ output_nodes.dup.each {|node| node.round = orphan_round }
255
+ else
256
+ input_nodes.replace(remaining_nodes)
257
+ end
258
+ end
259
+
260
+ # reassign rounds
261
+ index = 0
262
+ rounds.compact.each do |round|
263
+ round.each {|node| node.round = index }
264
+ index += 1
234
265
  end
266
+
267
+ self
235
268
  end
236
269
 
237
270
  def build(app)
238
- tasks = {}
271
+ cleanup
239
272
 
240
273
  # instantiate the nodes
274
+ tasks = {}
241
275
  nodes.each do |node|
242
276
  tasks[node] = yield(node.argv) if node
243
277
  end
@@ -258,102 +292,94 @@ module Tap
258
292
  end
259
293
 
260
294
  # build the workflow
261
- joins.each_pair do |join, (source_node, target_nodes)|
262
- raise "unassigned join: #{join}" if source_node == nil || target_nodes.empty?
263
-
264
- targets = target_nodes.collect do |target_node|
265
- tasks[target_node][0]
266
- end
267
- source = tasks[source_node][0]
295
+ joins.each do |join, input_nodes, output_nodes|
296
+ sources = input_nodes.collect {|node| tasks[node][0] }
297
+ targets = output_nodes.collect {|node| tasks[node][0] }
268
298
 
269
- join.join(source, targets)
299
+ join.join(sources, targets)
270
300
  end
271
301
 
272
- # build queues
302
+ # build rounds
273
303
  queues = rounds.compact.collect do |round|
274
- round.each do |node|
275
- task, args = tasks.delete(node)
276
- task.enq(*args)
277
- end
278
-
279
- app.queue.clear
304
+ round.collect {|node| tasks.delete(node) }
280
305
  end
281
306
 
282
307
  # notify any args that will be overlooked
283
308
  tasks.each_pair do |node, (task, args)|
284
309
  next if args.empty?
285
- warn "warning: ignoring args for node (#{nodes.index(node)}) #{task} [#{args.join(' ')}]"
310
+ warn "warning: ignoring args for node (#{index(node)}) #{task} [#{args.join(' ')}]"
286
311
  end
287
-
288
- queues
312
+
313
+ # enque
314
+ queues.each {|queue| app.queue.concat(queue) }
315
+ app
289
316
  end
290
317
 
291
318
  # Creates an array dump of the contents of self.
292
319
  def dump
293
- segments = argvs
294
- each_schema_str {|str| segments << str }
295
- segments
296
- end
297
-
298
- # Constructs a command-line string for the schema, ex:
299
- # '-- a -- b --0:1'.
300
- def to_s
301
- segments = []
302
- nodes.each do |node|
303
- segments << "--"
304
-
305
- node.argv.each do |arg|
306
- segments << shell_quote(arg)
307
- end unless node == nil
308
- end
320
+ cleanup
309
321
 
310
- each_schema_str {|str| segments << "--#{str}" }
311
- segments.join(' ')
312
- end
313
-
314
- protected
315
-
316
- # Yields each formatted schema string (global, round, and join).
317
- def each_schema_str # :nodoc:
318
- each_globals_str {|str| yield str }
319
- each_round_str {|str| yield str }
320
- each_join_str {|str| yield str }
321
- end
322
-
323
- # Yields globals formatted as a string.
324
- def each_globals_str # :nodoc:
322
+ # add argvs
323
+ array = argvs
324
+
325
+ # add global declarations
325
326
  globals.each do |node|
326
- yield format_instance(nodes.index(node))
327
+ array << format_instance(index(node))
327
328
  end
328
- end
329
-
330
- # Yields each round formatted as a string.
331
- def each_round_str # :nodoc
329
+
330
+ # add round declarations
332
331
  index = 0
333
- rounds.each do |indicies|
334
- unless indicies == nil || index == 0
335
- indicies = indicies.collect {|node| nodes.index(node) }
336
- yield format_round(index, indicies)
332
+ rounds.each do |nodes|
333
+
334
+ # skip round 0 as it is implicit
335
+ if index > 0
336
+ indicies = nodes.collect {|node| index(node) }
337
+ array << format_round(index, indicies)
337
338
  end
339
+
338
340
  index += 1
339
341
  end
340
- end
342
+
343
+ # add join declarations
344
+ joins.each do |join, input_nodes, output_nodes|
345
+ inputs = input_nodes.collect {|node| nodes.index(node) }
346
+ outputs = output_nodes.collect {|node| nodes.index(node) }
341
347
 
342
- # Yields each join formatted as a string.
343
- def each_join_str # :nodoc
344
- joins.each_pair do |join, (source_node, target_nodes)|
345
- source_index = nodes.index(source_node)
346
- target_indicies = target_nodes.collect {|node| nodes.index(node) }
347
-
348
- yield case join
349
- when Joins::Sequence then format_sequence(source_index, target_indicies, join.options)
350
- when Joins::Fork then format_fork(source_index, target_indicies, join.options)
351
- when Joins::Merge then format_merge(source_index, target_indicies, join.options)
352
- when Joins::SyncMerge then format_sync_merge(source_index, target_indicies, join.options)
353
- else raise "unknown join type: #{join.class} (#{source_index}, [#{target_indicies.join(',')}])"
348
+ array << case join
349
+ when Joins::SyncMerge then format_sync_merge(outputs, inputs, join.options)
350
+ when Join
351
+ if inputs.length == 1
352
+ if outputs.length == 1
353
+ format_sequence(inputs, outputs, join.options)
354
+ else
355
+ format_fork(inputs, outputs, join.options)
356
+ end
357
+ else
358
+ format_merge(outputs, inputs, join.options)
359
+ end
360
+ else raise "unknown join type: #{join.class} ([#{inputs.join(',')}], [#{outputs.join(',')}])"
361
+ end
362
+ end
363
+
364
+ array
365
+ end
366
+
367
+ # Constructs a command-line string for the schema, ex:
368
+ # '-- a -- b --0:1'.
369
+ def to_s
370
+ args = []
371
+ dump.each do |obj|
372
+ if obj.kind_of?(Array)
373
+ args << "--"
374
+ args.concat obj.collect {|arg| shell_quote(arg) }
375
+ else
376
+ args << "--#{obj}"
354
377
  end
355
378
  end
379
+
380
+ args.join(' ')
356
381
  end
382
+
357
383
  end
358
384
  end
359
385
  end