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
@@ -21,7 +21,8 @@ module Tap
21
21
  # const_name. When a constant is missing, constantize yields
22
22
  # the current base and any non-existant constant names the block,
23
23
  # if given, or raises a NameError. The block is expected
24
- # to return the proper constant.
24
+ # to return the desired constant; in the example 'Non::Existant'
25
+ # is effectively mapping to ConstName.
25
26
  #
26
27
  # module ConstName; end
27
28
  #
@@ -4,18 +4,24 @@ require 'tap/support/constant'
4
4
  module Tap
5
5
  module Support
6
6
 
7
+ # :startdoc:::-
8
+ #
7
9
  # ConstantManifest builds a manifest of Constant entries using Lazydoc.
8
- # The idea is that Lazydoc can find files with resouces of a specific type
9
- # (ex tasks) and Constant can reference those resouces and load them as
10
- # necessary. ConstantManifest registers paths so that they may be lazily
11
- # scanned when searching for a specific resource.
10
+ #
11
+ # Lazydoc can quickly scan files for constant attributes, and thereby
12
+ # identify constants based upon a flag like the '::manifest' attribute used
13
+ # to identify task classes. ConstantManifest registers paths that will be
14
+ # scanned for a specific resource, and lazily builds the references to load
15
+ # them as necessary.
16
+ #
17
+ # :startdoc:::+
12
18
  class ConstantManifest < Support::Manifest
13
19
 
14
- # The attribute identifying resources in a file
20
+ # The attribute identifying constants in a file
15
21
  attr_reader :const_attr
16
22
 
17
- # Registered [root, [paths]] pairs that will be searched
18
- # for the const_attr
23
+ # An array of registered (root, [paths]) pairs
24
+ # that will be searched for const_attr
19
25
  attr_reader :search_paths
20
26
 
21
27
  # The current index of search_paths
@@ -24,7 +30,8 @@ module Tap
24
30
  # The current index of paths
25
31
  attr_reader :path_index
26
32
 
27
- # Initializes a new ConstantManifest
33
+ # Initializes a new ConstantManifest that will identify constants
34
+ # using the specified constant attribute.
28
35
  def initialize(const_attr)
29
36
  @const_attr = const_attr
30
37
  @search_paths = []
@@ -55,15 +62,15 @@ module Tap
55
62
  # Sets search_path_index and path_index to zero and clears entries.
56
63
  # Returns self.
57
64
  def reset
58
- # Support::Lazydoc[path].resolved = false
65
+ @search_paths.each {|path| Lazydoc[path].resolved = false }
59
66
  @entries.clear
60
67
  @search_path_index = 0
61
68
  @path_index = 0
62
69
  super
63
70
  end
64
71
 
65
- # Yields each entry to the block. Unless built?, each lazily
66
- # iterates over search_paths to look for new entries.
72
+ # Yields each Constant entry to the block. Unless built?, each
73
+ # lazily iterates over search_paths to look for new entries.
67
74
  def each
68
75
  entries.each do |entry|
69
76
  yield(entry)
@@ -93,25 +100,21 @@ module Tap
93
100
  # objects for each. If the document has no default_const_name set,
94
101
  # resolve will set the default_const_name based on the relative
95
102
  # filepath from path_root to path.
96
- def resolve(path_root, path)
103
+ def resolve(path_root, path) # :nodoc:
97
104
  entries = []
98
- lazydoc = nil
105
+ document = nil
99
106
 
100
- Lazydoc.scan(File.read(path), const_attr) do |const_name, attr_key, comment|
101
- if lazydoc == nil
102
- lazydoc = Lazydoc[path]
103
-
104
- if lazydoc.default_const_name.empty?
105
- relative_path = Root.relative_filepath(path_root, path).chomp(File.extname(path))
106
- lazydoc.default_const_name = relative_path.camelize
107
- end
107
+ Lazydoc::Document.scan(File.read(path), const_attr) do |const_name, key, value|
108
+ if document == nil
109
+ relative_path = Root.relative_filepath(path_root, path).chomp(File.extname(path))
110
+ document = Lazydoc.register_file(path, relative_path.camelize)
108
111
  end
109
112
 
110
- if const_name.empty?
111
- const_name = lazydoc.default_const_name
112
- end
113
+ const_name = document.default_const_name if const_name.empty?
114
+ comment = Lazydoc::Subject.new(nil, document)
115
+ comment.value = value
113
116
 
114
- lazydoc[const_name][attr_key] = comment
117
+ document[const_name][key] = comment
115
118
  entries << Constant.new(const_name, path)
116
119
  end
117
120
 
@@ -12,7 +12,7 @@ module Tap
12
12
  base.instance_variable_set(:@_result, nil)
13
13
  end
14
14
 
15
- # Conditional _execute; only calls _method_name if
15
+ # Conditional _execute; only calls method_name if
16
16
  # resolved? is false (thus assuring self will only
17
17
  # be executed once).
18
18
  #
@@ -1,4 +1,5 @@
1
1
  require 'tap/support/audit'
2
+ require 'tap/support/joins'
2
3
 
3
4
  module Tap
4
5
  module Support
@@ -10,7 +11,7 @@ module Tap
10
11
  attr_reader :app
11
12
 
12
13
  # The method called during _execute
13
- attr_reader :_method_name
14
+ attr_reader :method_name
14
15
 
15
16
  # The block called when _execute completes
16
17
  attr_reader :on_complete_block
@@ -18,143 +19,33 @@ module Tap
18
19
  # An array of dependency indicies that will be resolved on _execute
19
20
  attr_reader :dependencies
20
21
 
21
- # The batch for self
22
- attr_reader :batch
23
-
24
22
  public
25
23
 
26
24
  # Extends obj with Executable and sets up all required variables. The
27
25
  # specified method will be called on _execute.
28
- def self.initialize(obj, method_name, app=App.instance, batch=[], dependencies=[], &on_complete_block)
26
+ def self.initialize(obj, method_name, app=App.instance, dependencies=[], &on_complete_block)
29
27
  obj.extend Executable
30
28
  obj.instance_variable_set(:@app, app)
31
- obj.instance_variable_set(:@_method_name, method_name)
29
+ obj.instance_variable_set(:@method_name, method_name)
32
30
  obj.instance_variable_set(:@on_complete_block, on_complete_block)
33
31
  obj.instance_variable_set(:@dependencies, dependencies)
34
- obj.instance_variable_set(:@batch, batch)
35
- batch << obj
36
-
37
32
  obj
38
33
  end
39
34
 
40
- # Initializes a new batch object and adds the object to batch.
41
- # The object will be a duplicate of self. (Note this method
42
- # can raise an error for objects that don't support dup,
43
- # notably Method objects generated by Object#_method).
44
- def initialize_batch_obj
45
- obj = self.dup
46
-
47
- if obj.kind_of?(Executable)
48
- batch << obj
49
- obj
50
- else
51
- Executable.initialize(obj, _method_name, app, batch, dependencies, &on_complete_block)
52
- end
53
- end
54
-
55
- # Returns true if the batch size is greater than one
56
- # (the one is assumed to be self).
57
- def batched?
58
- batch.length > 1
59
- end
60
-
61
- # Returns the index of self in batch.
62
- def batch_index
63
- batch.index(self)
64
- end
65
-
66
- # Merges the batches for self and the specified Executables,
67
- # removing duplicates.
68
- #
69
- # class BatchExecutable
70
- # include Tap::Support::Executable
71
- # def initialize(batch=[])
72
- # @batch = batch
73
- # batch << self
74
- # end
75
- # end
76
- #
77
- # b1 = BatchExecutable.new
78
- # b2 = BatchExecutable.new
79
- # b3 = BatchExecutable.new
80
- #
81
- # b1.batch_with(b2, b3)
82
- # b1.batch # => [b1, b2, b3]
83
- # b3.batch # => [b1, b2, b3]
84
- #
85
- # Note that batch_with is not recursive (ie it does not
86
- # merge the batches of each member in the batch):
87
- #
88
- # b4 = BatchExecutable.new
89
- # b4.batch_with(b3)
90
- #
91
- # b4.batch # => [b4, b1, b2, b3]
92
- # b3.batch # => [b4, b1, b2, b3]
93
- # b2.batch # => [b1, b2, b3]
94
- # b1.batch # => [b1, b2, b3]
95
- #
96
- # However it does affect all objects that share the same
97
- # underlying batch:
98
- #
99
- # b5 = BatchExecutable.new(b1.batch)
100
- # b6 = BatchExecutable.new
101
- #
102
- # b5.batch.object_id # => b1.batch.object_id
103
- # b5.batch # => [b1, b2, b3, b5]
104
- #
105
- # b5.batch_with(b6)
106
- #
107
- # b5.batch # => [b1, b2, b3, b5, b6]
108
- # b1.batch # => [b1, b2, b3, b5, b6]
109
- #
110
- # Returns self.
111
- def batch_with(*executables)
112
- batches = [batch] + executables.collect {|executable| executable.batch }
113
- batches.uniq!
114
-
115
- merged = []
116
- batches.each do |batch|
117
- merged.concat(batch)
118
- batch.clear
119
- end
120
-
121
- merged.uniq!
122
- batches.each {|batch| batch.concat(merged) }
123
- self
124
- end
125
-
126
- # Enqueues each member of batch (and implicitly self) to app with the
127
- # inputs. The number of inputs provided should match the number of
128
- # inputs for the _method_name method.
35
+ # Enqueues self to app with the inputs. The number of inputs provided
36
+ # should match the number of inputs for the method_name method.
129
37
  def enq(*inputs)
130
- batch.each do |executable|
131
- executable.unbatched_enq(*inputs)
132
- end
133
- self
134
- end
135
-
136
- # Like enq, but only enques self.
137
- def unbatched_enq(*inputs)
138
38
  app.queue.enq(self, inputs)
139
39
  self
140
40
  end
141
41
 
142
- # Sets a block to receive the results of _execute for each member of
143
- # batch (and implicitly self). Raises an error if on_complete_block
144
- # is already set within the batch. Override the existing
42
+ # Sets a block to receive the results of _execute. Raises an error if
43
+ # an on_complete_block is already set. Override the existing
145
44
  # on_complete_block by specifying override = true.
146
45
  #
147
46
  # Note: the block recieves an audited result and not the result
148
47
  # itself (see Audit for more information).
149
48
  def on_complete(override=false, &block) # :yields: _result
150
- batch.each do |executable|
151
- executable.unbatched_on_complete(override, &block)
152
- end
153
- self
154
- end
155
-
156
- # Like on_complete, but only sets the on_complete_block for self.
157
- def unbatched_on_complete(override=false, &block) # :yields: _result
158
49
  unless on_complete_block == nil || override
159
50
  raise "on_complete_block already set: #{self}"
160
51
  end
@@ -163,56 +54,54 @@ module Tap
163
54
  end
164
55
 
165
56
  # Sets a sequence workflow pattern for the tasks; each task
166
- # enques the next task with it's results, starting with self.
167
- # See Joins::Sequence.
57
+ # enques the next task with it's results, starting with self.
168
58
  def sequence(*tasks, &block) # :yields: _result
169
- Joins::Sequence.join(self, tasks, &block)
59
+ options = tasks[-1].kind_of?(Hash) ? tasks.pop : {}
60
+
61
+ current_task = self
62
+ tasks.each do |next_task|
63
+ Join.new(options).join([current_task], [next_task], &block)
64
+ current_task = next_task
65
+ end
170
66
  end
171
67
 
172
- # Sets a fork workflow pattern for self; each target
173
- # will enque the results of self. See Joins::Fork.
68
+ # Sets a fork workflow pattern for self; each target will enque the
69
+ # results of self.
174
70
  def fork(*targets, &block) # :yields: _result
175
- Joins::Fork.join(self, targets, &block)
71
+ options = targets[-1].kind_of?(Hash) ? targets.pop : {}
72
+ Join.new(options).join([self], targets, &block)
176
73
  end
177
74
 
178
75
  # Sets a simple merge workflow pattern for the source tasks. Each
179
76
  # source enques self with it's result; no synchronization occurs,
180
- # nor are results grouped before being enqued. See Joins::Merge.
77
+ # nor are results grouped before being enqued.
181
78
  def merge(*sources, &block) # :yields: _result
182
- Joins::Merge.join(self, sources, &block)
79
+ options = sources[-1].kind_of?(Hash) ? sources.pop : {}
80
+ Join.new(options).join(sources, [self], &block)
183
81
  end
184
82
 
185
83
  # Sets a synchronized merge workflow for the source tasks. Results
186
84
  # from each source are collected and enqued as a single group to
187
85
  # self. The collective results are not enqued until all sources
188
86
  # have completed. See Joins::SyncMerge.
189
- #
190
- # Raises an error if a source returns twice before the target is enqued.
191
87
  def sync_merge(*sources, &block) # :yields: _result
192
- Joins::SyncMerge.join(self, sources, &block)
88
+ options = sources[-1].kind_of?(Hash) ? sources.pop : {}
89
+ Joins::SyncMerge.new(options).join(sources, [self], &block)
193
90
  end
194
91
 
195
- # Sets a switch workflow pattern for self. When _execute completes,
196
- # switch yields the audited result to the block which should return
197
- # the index of the target to enque with the results. No target will
198
- # be enqued if the index is false or nil; an error is raised if no
199
- # target can be found for the specified index. See Joins::Switch.
92
+ # Sets a switch workflow pattern for self. On complete, switch yields
93
+ # the audited result to the block and the block should return the index
94
+ # of the target to enque with the results. No target will be enqued if
95
+ # the index is false or nil. An error is raised if no target can be
96
+ # found for the specified index. See Joins::Switch.
200
97
  def switch(*targets, &block) # :yields: _result
201
- Joins::Switch.join(self, targets, &block)
98
+ options = targets[-1].kind_of?(Hash) ? targets.pop : {}
99
+ Joins::Switch.new(options).join([self], targets, &block)
202
100
  end
203
101
 
204
- # Adds the dependency to each member in batch (and implicitly self).
205
- # The dependency will be resolved with the input arguments during
206
- # _execute, using resolve_dependencies.
102
+ # Adds the dependencies to self. Dependencies are resolved during
103
+ # _execute through resolve_dependencies.
207
104
  def depends_on(*dependencies)
208
- batch.each do |e|
209
- e.unbatched_depends_on(*dependencies)
210
- end
211
- self
212
- end
213
-
214
- # Like depends_on, but only adds the dependency to self.
215
- def unbatched_depends_on(*dependencies)
216
105
  raise ArgumentError, "cannot depend on self" if dependencies.include?(self)
217
106
 
218
107
  dependencies.each do |dependency|
@@ -236,53 +125,37 @@ module Tap
236
125
  self
237
126
  end
238
127
 
239
- # Auditing method call. Resolves dependencies, executes _method_name,
128
+ # Auditing method call. Resolves dependencies, executes method_name,
240
129
  # and sends the audited result to the on_complete_block (if set).
241
130
  #
242
- # Audits are initialized in the follwing manner:
243
- # no inputs:: Creates a new, empty Audit. The first value of the audit
244
- # will be the result of call.
245
- # one input:: Forks the input if it is an audit, otherwise initializes
246
- # a new audit using the input.
247
- # multiple inputs:: Merges the inputs into a new Audit.
248
- #
131
+ # Returns the audited result.
249
132
  def _execute(*inputs)
250
133
  resolve_dependencies
251
134
 
252
- audit = case inputs.length
253
- when 0 then Audit.new
254
- when 1
255
- audit = inputs.first
256
- if audit.kind_of?(Audit)
257
- inputs = [audit._current]
258
- audit._fork
135
+ previous = []
136
+ inputs.collect! do |input|
137
+ if input.kind_of?(Audit)
138
+ previous << input
139
+ input.value
259
140
  else
260
- Audit.new(audit)
261
- end
262
- else
263
- sources = []
264
- inputs.collect! do |input|
265
- if input.kind_of?(Audit)
266
- sources << input._fork
267
- input._current
268
- else
269
- sources << nil
270
- input
271
- end
141
+ previous << Audit.new(nil, input)
142
+ input
272
143
  end
273
- Audit.new(inputs, sources)
274
144
  end
275
-
276
- audit._record(self, send(_method_name, *inputs))
277
- on_complete_block ? on_complete_block.call(audit) : app.aggregator.store(audit)
145
+
146
+ audit = Audit.new(self, send(method_name, *inputs), previous)
147
+ if complete_block = on_complete_block || app.on_complete_block
148
+ complete_block.call(audit)
149
+ else
150
+ app.aggregator.store(audit)
151
+ end
278
152
 
279
153
  audit
280
154
  end
281
155
 
282
- # Calls _execute with the inputs and returns the un-audited result.
283
- # Execute is not a batched method.
156
+ # Calls _execute with the inputs and returns the non-audited result.
284
157
  def execute(*inputs)
285
- _execute(*inputs)._current
158
+ _execute(*inputs).value
286
159
  end
287
160
 
288
161
  # Raises a TerminateError if app.state == State::TERMINATE.
@@ -294,10 +167,6 @@ module Tap
294
167
  end
295
168
  end
296
169
 
297
- def inspect
298
- "#<#{self.class.to_s}:#{object_id} _method: #{_method_name} batch_length: #{batch.length} app: #{app}>"
299
- end
300
-
301
170
  end
302
171
  end
303
172
  end
@@ -1,35 +1,39 @@
1
+ require 'tap/support/executable'
2
+
1
3
  module Tap
2
4
  module Support
3
5
 
4
- # ExecutableQueue allows thread-safe enqueing and dequeing of
5
- # Executable methods and inputs for execution.
6
- class ExecutableQueue
7
- include MonitorMixin
6
+ # ExecutableQueue allows thread-safe enqueing and dequeing of Executable
7
+ # methods and inputs for execution.
8
+ class ExecutableQueue < Monitor
8
9
 
9
10
  # Creates a new ExecutableQueue
10
11
  def initialize
11
- # required for MonitorMixin
12
- super()
13
- @queue = []
12
+ super
13
+ @rounds = [[]]
14
14
  end
15
15
 
16
- # Clears all methods and inputs. Returns the existing queue as an array.
16
+ # Clears self and returns an array of the enqueued methods and inputs,
17
+ # organized by round.
17
18
  def clear
18
19
  synchronize do
19
- current = self.queue
20
- self.queue = []
20
+ current, @rounds = @rounds, [[]]
21
21
  current
22
22
  end
23
23
  end
24
24
 
25
25
  # Returns the number of enqueued methods
26
26
  def size
27
- queue.length
27
+ synchronize do
28
+ size = 0
29
+ @rounds.each {|round| size += round.length }
30
+ size
31
+ end
28
32
  end
29
33
 
30
34
  # True if no methods are enqueued
31
35
  def empty?
32
- queue.empty?
36
+ synchronize { size == 0 }
33
37
  end
34
38
 
35
39
  # Enqueues the method and inputs. Raises an error if the
@@ -56,26 +60,52 @@ module Tap
56
60
  synchronize { queue.shift }
57
61
  end
58
62
 
59
- def concat(array)
63
+ # Enques an array of [method, inputs] entries as a round. Rounds are
64
+ # dequeued completely before the next round is dequeued.
65
+ def concat(round)
60
66
  synchronize do
61
- array.each do |method, inputs|
62
- enq(method, inputs)
67
+ round.each do |method, inputs|
68
+ check_method(method)
63
69
  end
70
+
71
+ @rounds << round.dup
64
72
  end
65
73
  end
66
74
 
67
- # Converts self to an array.
68
- def to_a
69
- queue.dup
75
+ # Converts self to an array. If flatten is specified, all rounds are
76
+ # concatenated into a single array.
77
+ def to_a(flatten=true)
78
+ synchronize do
79
+ if flatten
80
+ array = []
81
+ @rounds.each {|round| array.concat(round) }
82
+ array
83
+ else
84
+ @rounds.collect {|round| round.dup}
85
+ end
86
+ end
70
87
  end
71
88
 
72
89
  protected
73
90
 
74
- attr_accessor :queue # :nodoc:
91
+ # Returns the active round.
92
+ def queue # :nodoc:
93
+ while @rounds.length > 1
94
+ queue = @rounds[0]
95
+
96
+ if queue.empty?
97
+ @rounds.shift
98
+ else
99
+ return queue
100
+ end
101
+ end
102
+
103
+ @rounds[0]
104
+ end
75
105
 
76
106
  # Checks if the input method is extended with Executable
77
107
  def check_method(method) # :nodoc:
78
- raise "not Executable: #{method}" unless method.kind_of?(Executable)
108
+ raise "not executable: #{method.inspect}" unless method.kind_of?(Executable)
79
109
  end
80
110
  end
81
111
  end