bahuvrihi-tap 0.11.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/bin/rap +2 -3
  2. data/bin/tap +1 -1
  3. data/cmd/console.rb +2 -2
  4. data/cmd/manifest.rb +2 -2
  5. data/cmd/run.rb +7 -9
  6. data/cmd/server.rb +5 -5
  7. data/doc/Class Reference +17 -20
  8. data/doc/Tutorial +5 -7
  9. data/lib/tap.rb +2 -0
  10. data/lib/tap/app.rb +21 -31
  11. data/lib/tap/constants.rb +2 -2
  12. data/lib/tap/declarations.rb +85 -97
  13. data/lib/tap/declarations/declaration_task.rb +58 -0
  14. data/lib/tap/declarations/description.rb +24 -0
  15. data/lib/tap/env.rb +20 -16
  16. data/lib/tap/exe.rb +2 -2
  17. data/lib/tap/file_task.rb +224 -410
  18. data/lib/tap/generator/arguments.rb +9 -0
  19. data/lib/tap/generator/base.rb +105 -28
  20. data/lib/tap/generator/destroy.rb +29 -12
  21. data/lib/tap/generator/generate.rb +55 -39
  22. data/lib/tap/generator/generators/command/templates/command.erb +3 -3
  23. data/lib/tap/generator/generators/config/config_generator.rb +34 -3
  24. data/lib/tap/generator/generators/root/root_generator.rb +6 -9
  25. data/lib/tap/generator/generators/root/templates/Rakefile +4 -4
  26. data/lib/tap/generator/generators/task/templates/test.erb +1 -1
  27. data/lib/tap/root.rb +211 -156
  28. data/lib/tap/support/aggregator.rb +6 -9
  29. data/lib/tap/support/audit.rb +278 -357
  30. data/lib/tap/support/constant_manifest.rb +24 -21
  31. data/lib/tap/support/dependency.rb +1 -1
  32. data/lib/tap/support/executable.rb +26 -48
  33. data/lib/tap/support/join.rb +44 -19
  34. data/lib/tap/support/joins/sync_merge.rb +3 -5
  35. data/lib/tap/support/parser.rb +1 -1
  36. data/lib/tap/task.rb +195 -150
  37. data/lib/tap/tasks/dump.rb +2 -2
  38. data/lib/tap/test/extensions.rb +11 -13
  39. data/lib/tap/test/file_test.rb +71 -129
  40. data/lib/tap/test/file_test_class.rb +4 -1
  41. data/lib/tap/test/tap_test.rb +26 -154
  42. metadata +15 -22
  43. data/lib/tap/patches/optparse/summarize.rb +0 -62
  44. data/lib/tap/support/assignments.rb +0 -173
  45. data/lib/tap/support/class_configuration.rb +0 -182
  46. data/lib/tap/support/configurable.rb +0 -113
  47. data/lib/tap/support/configurable_class.rb +0 -271
  48. data/lib/tap/support/configuration.rb +0 -170
  49. data/lib/tap/support/instance_configuration.rb +0 -173
  50. data/lib/tap/support/lazydoc.rb +0 -386
  51. data/lib/tap/support/lazydoc/attributes.rb +0 -48
  52. data/lib/tap/support/lazydoc/comment.rb +0 -503
  53. data/lib/tap/support/lazydoc/config.rb +0 -17
  54. data/lib/tap/support/lazydoc/definition.rb +0 -36
  55. data/lib/tap/support/lazydoc/document.rb +0 -152
  56. data/lib/tap/support/lazydoc/method.rb +0 -24
  57. data/lib/tap/support/tdoc.rb +0 -409
  58. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -38
  59. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -42
  60. data/lib/tap/support/validation.rb +0 -479
@@ -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 a manifest of Constant
15
+ # references to load 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 = []
@@ -95,23 +102,19 @@ module Tap
95
102
  # filepath from path_root to path.
96
103
  def resolve(path_root, path)
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
  #
@@ -10,7 +10,7 @@ module Tap
10
10
  attr_reader :app
11
11
 
12
12
  # The method called during _execute
13
- attr_reader :_method_name
13
+ attr_reader :method_name
14
14
 
15
15
  # The block called when _execute completes
16
16
  attr_reader :on_complete_block
@@ -28,7 +28,7 @@ module Tap
28
28
  def self.initialize(obj, method_name, app=App.instance, batch=[], dependencies=[], &on_complete_block)
29
29
  obj.extend Executable
30
30
  obj.instance_variable_set(:@app, app)
31
- obj.instance_variable_set(:@_method_name, method_name)
31
+ obj.instance_variable_set(:@method_name, method_name)
32
32
  obj.instance_variable_set(:@on_complete_block, on_complete_block)
33
33
  obj.instance_variable_set(:@dependencies, dependencies)
34
34
  obj.instance_variable_set(:@batch, batch)
@@ -37,10 +37,11 @@ module Tap
37
37
  obj
38
38
  end
39
39
 
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).
40
+ # Initializes a new batch object and adds the object to batch. The object
41
+ # will be a duplicate of self.
42
+ #
43
+ # Note this method can raise an error for objects that don't support dup,
44
+ # notably Method objects generated by Object#_method.
44
45
  def initialize_batch_obj
45
46
  obj = self.dup
46
47
 
@@ -48,7 +49,7 @@ module Tap
48
49
  batch << obj
49
50
  obj
50
51
  else
51
- Executable.initialize(obj, _method_name, app, batch, dependencies, &on_complete_block)
52
+ Executable.initialize(obj, method_name, app, batch, dependencies, &on_complete_block)
52
53
  end
53
54
  end
54
55
 
@@ -125,7 +126,7 @@ module Tap
125
126
 
126
127
  # Enqueues each member of batch (and implicitly self) to app with the
127
128
  # inputs. The number of inputs provided should match the number of
128
- # inputs for the _method_name method.
129
+ # inputs for the method_name method.
129
130
  def enq(*inputs)
130
131
  batch.each do |executable|
131
132
  executable.unbatched_enq(*inputs)
@@ -193,10 +194,10 @@ module Tap
193
194
  end
194
195
 
195
196
  # 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.
197
+ # switch yields the audited result to the block and the block should
198
+ # return the index of the target to enque with the results. No target
199
+ # will be enqued if the index is false or nil. An error is raised if
200
+ # no target can be found for the specified index. See Joins::Switch.
200
201
  def switch(*targets, &block) # :yields: _result
201
202
  Joins::Switch.join(self, targets, &block)
202
203
  end
@@ -236,53 +237,34 @@ module Tap
236
237
  self
237
238
  end
238
239
 
239
- # Auditing method call. Resolves dependencies, executes _method_name,
240
+ # Auditing method call. Resolves dependencies, executes method_name,
240
241
  # and sends the audited result to the on_complete_block (if set).
241
242
  #
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
- #
243
+ # Returns the audited result.
249
244
  def _execute(*inputs)
250
245
  resolve_dependencies
251
246
 
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
247
+ previous = []
248
+ inputs.collect! do |input|
249
+ if input.kind_of?(Audit)
250
+ previous << input
251
+ input.value
259
252
  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
253
+ previous << Audit.new(nil, input)
254
+ input
272
255
  end
273
- Audit.new(inputs, sources)
274
256
  end
275
-
276
- audit._record(self, send(_method_name, *inputs))
257
+
258
+ audit = Audit.new(self, send(method_name, *inputs), previous)
277
259
  on_complete_block ? on_complete_block.call(audit) : app.aggregator.store(audit)
278
260
 
279
261
  audit
280
262
  end
281
263
 
282
- # Calls _execute with the inputs and returns the un-audited result.
264
+ # Calls _execute with the inputs and returns the non-audited result.
283
265
  # Execute is not a batched method.
284
266
  def execute(*inputs)
285
- _execute(*inputs)._current
267
+ _execute(*inputs).value
286
268
  end
287
269
 
288
270
  # Raises a TerminateError if app.state == State::TERMINATE.
@@ -294,10 +276,6 @@ module Tap
294
276
  end
295
277
  end
296
278
 
297
- def inspect
298
- "#<#{self.class.to_s}:#{object_id} _method: #{_method_name} batch_length: #{batch.length} app: #{app}>"
299
- end
300
-
301
279
  end
302
280
  end
303
281
  end
@@ -24,30 +24,34 @@ module Tap
24
24
 
25
25
  # Causes the join to iterate the results
26
26
  # of the source when enquing the targets.
27
- config :iterate, false, &c.boolean
27
+ config :iterate, false, :short => 'i', &c.boolean
28
+
29
+ # Causes joins to splat ('*') the results
30
+ # of the source when enquing the targets.
31
+ config :splat, false, :short => 's', &c.boolean
28
32
 
29
33
  # Causes the targets to be enqued rather
30
34
  # than executed immediately.
31
- config :stack, false, &c.boolean
35
+ config :stack, false, :short => 'k', &c.boolean
32
36
 
33
37
  # Causes joins to only occur between the
34
38
  # explicitly named source and targets,
35
39
  # and not their batches.
36
- config :unbatched, false, &c.boolean
40
+ config :unbatched, false, :short => 'u', &c.boolean
37
41
 
38
42
  # An array of workflow flags. Workflow flags are false unless specified.
39
43
  FLAGS = configurations.keys
40
44
 
41
- # An array of the first character in each WORKFLOW_FLAGS.
42
- SHORT_FLAGS = FLAGS.collect {|flag| flag.to_s[0,1]}
45
+ # An array of the shorts in each WORKFLOW_FLAGS.
46
+ SHORT_FLAGS = configurations.values.collect {|config| config.attributes[:short] }
43
47
 
44
48
  # Initializes a new join with the specified configuration.
45
49
  def initialize(config)
46
50
  initialize_config(config)
47
51
  end
48
52
 
49
- # The name of the join, as a symbol. By default
50
- # name is the basename of the underscored class.
53
+ # The name of the join, as a symbol. By default name
54
+ # is the basename of the underscored class name.
51
55
  def name
52
56
  File.basename(self.class.to_s.underscore).to_sym
53
57
  end
@@ -85,34 +89,55 @@ module Tap
85
89
  #
86
90
  # true false
87
91
  # iterate _results are iterated _results are enqued directly
92
+ # splat _results are splat enqued _results are enqued directly
88
93
  # stack the executable is enqued the executable is executed
89
94
  # unbatched only exectuable is enqued executable.batch is enqued
90
95
  #
91
- def enq(executable, _results)
92
- app = executable.app
93
-
94
- results = iterate ? _results._iterate : [_results]
95
- results.each do |_result|
96
+ def enq(executable, *_results)
97
+ unpack(_results) do |_result|
96
98
  if stack
97
-
98
99
  if unbatched
99
- executable.unbatched_enq(_result)
100
+ executable.unbatched_enq(*_result)
100
101
  else
101
- executable.enq(_result)
102
+ executable.enq(*_result)
102
103
  end
103
-
104
104
  else
105
-
106
105
  if unbatched
107
- executable._execute(_result)
106
+ executable._execute(*_result)
108
107
  else
109
108
  executable.batch.each do |e|
110
- e._execute(_result)
109
+ e._execute(*_result)
111
110
  end
112
111
  end
112
+ end
113
+ end
114
+ end
115
+
116
+ # helper method to splat/iterate audited results
117
+ def unpack(_results) # :nodoc:
118
+ case
119
+ when iterate && splat
120
+ raise "splat and iterate"
121
+ when iterate
122
+ _splat(_results).each {|_result| yield(_result) }
123
+ when splat
124
+ yield(_splat(_results))
125
+ else
126
+ yield(_results)
127
+ end
128
+ end
113
129
 
130
+ # helper to splat audits
131
+ def _splat(_results) # :nodoc:
132
+ array = []
133
+ _results.each do |_result|
134
+ unless _result.kind_of?(Audit)
135
+ _result = Audit.new(nil, _result)
114
136
  end
137
+
138
+ array.concat(_result.splat)
115
139
  end
140
+ array
116
141
  end
117
142
  end
118
143
 
@@ -33,7 +33,7 @@ module Tap
33
33
 
34
34
  sources.each_with_index do |source, index|
35
35
  complete(source) do |_result|
36
- src = _result._current_source
36
+ src = _result.key
37
37
 
38
38
  source_index = indicies[src]
39
39
  (combinations[src] ||= []).each do |combination|
@@ -43,11 +43,9 @@ module Tap
43
43
 
44
44
  combination[source_index] = _result
45
45
  unless combination.include?(nil)
46
- # merge the source audits
47
- _merge_result = Support::Audit.merge(*combination)
48
46
 
49
- yield(_merge_result) if block_given?
50
- enq(target, _merge_result)
47
+ yield(*combination) if block_given?
48
+ enq(target, *combination)
51
49
 
52
50
  # reset the group array
53
51
  combination.collect! {|i| nil }
@@ -234,7 +234,7 @@ module Tap
234
234
  # if the options string contains unknown options.
235
235
  #
236
236
  # parse_options("") # => {}
237
- # parse_options("is") # => {:iterate => true, :stack => true}
237
+ # parse_options("ik") # => {:iterate => true, :stack => true}
238
238
  #
239
239
  def parse_options(three)
240
240
  options = {}
@@ -1,11 +1,12 @@
1
1
  require 'tap/support/executable'
2
- require 'tap/support/lazydoc/method'
3
- require 'tap/support/lazydoc/definition'
4
2
  require 'tap/support/intern'
5
- autoload(:OptionParser, 'optparse')
3
+ autoload(:ConfigParser, 'config_parser')
6
4
 
7
5
  module Tap
8
-
6
+ module Support
7
+ autoload(:Templater, 'tap/support/templater')
8
+ end
9
+
9
10
  # === Task Definition
10
11
  #
11
12
  # Tasks specify executable code by overridding the process method in
@@ -29,9 +30,8 @@ module Tap
29
30
  # MixedInputs.new.execute(:a, :b) # => [:a, :b, []]
30
31
  # MixedInputs.new.execute(:a, :b, 1, 2, 3) # => [:a, :b, [1,2,3]]
31
32
  #
32
- # Tasks may be create with new, or with intern. Intern overrides
33
- # process with a custom block that gets called with the task instance
34
- # and the inputs.
33
+ # Tasks may be create with new, or with intern. Intern overrides process
34
+ # using a block that receives the task instance and the inputs.
35
35
  #
36
36
  # no_inputs = Task.intern {|task| [] }
37
37
  # one_input = Task.intern {|task, input| [input] }
@@ -67,8 +67,8 @@ module Tap
67
67
  # t.respond_to?(:three) # => false
68
68
  #
69
69
  # Configurations can be validated/transformed using an optional block.
70
- # Tap::Support::Validation pre-packages many common blocks which may
71
- # be accessed through the class method 'c':
70
+ # Many common blocks are pre-packaged and may be accessed through the
71
+ # class method 'c':
72
72
  #
73
73
  # class ValidatingTask < Tap::Task
74
74
  # # string config validated to be a string
@@ -85,40 +85,31 @@ module Tap
85
85
  # t.integer = "1"
86
86
  # t.integer == 1 # => true
87
87
  #
88
- #--
89
- # === Subclassing
90
- # Tasks can be subclassed normally, with one reminder related to batching.
88
+ # See the {Configurable}[http://tap.rubyforge.org/configurable/]
89
+ # documentation for more information.
91
90
  #
92
- # Batched tasks are generated by duplicating an existing instance, hence
93
- # all instance variables will point to the same object in the batched
94
- # and original task. At times (as with configurations), this is
95
- # undesirable; the batched task should have it's own copy of an
96
- # instance variable.
91
+ # === Subclassing
92
+ # Tasks may be subclassed normally, but be sure to call super as necessary,
93
+ # in particular when overriding the following methods:
97
94
  #
98
- # In these cases, the <tt>initialize_copy</tt> should be overridden
99
- # and should re-initialize the appropriate variables. Be sure to call
100
- # super to invoke the default <tt>initialize_copy</tt>:
95
+ # class Subclass < Tap::Task
96
+ # class << self
97
+ # def inherited(child)
98
+ # super
99
+ # end
100
+ # end
101
101
  #
102
- # class SubclassTask < Tap::Task
103
- # attr_accessor :array
104
102
  # def initialize(*args)
105
- # @array = []
106
103
  # super
107
104
  # end
108
- #
105
+ #
109
106
  # def initialize_copy(orig)
110
- # @array = orig.array.dup
111
107
  # super
112
108
  # end
113
109
  # end
114
110
  #
115
- # t1 = SubclassTask.new
116
- # t2 = t1.initialize_batch_obj
117
- # t1.array == t2.array # => true
118
- # t1.array.object_id == t2.array.object_id # => false
119
- #
120
111
  class Task
121
- include Support::Configurable
112
+ include Configurable
122
113
  include Support::Executable
123
114
 
124
115
  class << self
@@ -140,15 +131,15 @@ module Tap
140
131
  # Returns an instance of self; the instance is a kind of 'global'
141
132
  # instance used in class-level dependencies. See depends_on.
142
133
  def instance
143
- @instance ||= new
134
+ @instance ||= new.extend(Support::Dependency)
144
135
  end
145
136
 
146
137
  def inherited(child) # :nodoc:
147
138
  unless child.instance_variable_defined?(:@source_file)
148
- caller.first =~ Support::Lazydoc::CALLER_REGEXP
139
+ caller[0] =~ Lazydoc::CALLER_REGEXP
149
140
  child.instance_variable_set(:@source_file, File.expand_path($1))
150
141
  end
151
-
142
+
152
143
  child.instance_variable_set(:@dependencies, dependencies.dup)
153
144
  super
154
145
  end
@@ -168,87 +159,59 @@ module Tap
168
159
  end
169
160
 
170
161
  # Parses the argv into an instance of self and an array of arguments
171
- # (implicitly to be enqued to the instance). Yields a help string to
172
- # the block when the argv indicates 'help'.
173
- def parse(argv=ARGV, app=Tap::App.instance, &block) # :yields: help_str
174
- parse!(argv.dup, &block)
162
+ # (implicitly to be enqued to the instance).
163
+ def parse(argv=ARGV, app=Tap::App.instance)
164
+ parse!(argv.dup)
175
165
  end
176
166
 
177
- # Same as parse, but removes switches destructively.
178
- def parse!(argv=ARGV, app=Tap::App.instance) # :yields: help_str
179
- opts = OptionParser.new
180
-
181
- # Add configurations
182
- argv_config = {}
183
- unless configurations.empty?
184
- opts.separator ""
185
- opts.separator "configurations:"
186
- end
187
-
188
- configurations.each do |receiver, key, config|
189
- opts.on(*config.to_optparse_argv) do |value|
190
- argv_config[key] = value
191
- end
192
- end
193
-
194
- # Add options on_tail, giving priority to configurations
167
+ # Same as parse, but removes switches destructively.
168
+ def parse!(argv=ARGV, app=Tap::App.instance)
169
+ opts = ConfigParser.new
170
+ opts.separator "configurations:"
171
+ opts.add(configurations)
172
+
195
173
  opts.separator ""
196
174
  opts.separator "options:"
197
-
198
- opts.on_tail("-h", "--help", "Print this help") do
175
+
176
+ # Add option to print help
177
+ opts.on("-h", "--help", "Print this help") do
199
178
  prg = case $0
200
179
  when /rap$/ then 'rap'
201
180
  else 'tap run --'
202
181
  end
203
182
 
204
- opts.banner = "#{help}usage: #{prg} #{to_s.underscore} #{args.subject}"
205
- if block_given?
206
- yield(opts.to_s)
207
- else
208
- puts opts
209
- exit
210
- end
183
+ puts "#{help}usage: #{prg} #{to_s.underscore} #{args}"
184
+ puts
185
+ puts opts
186
+ exit
211
187
  end
212
-
213
- # Add option for name
188
+
189
+ # Add option to specify a config file
214
190
  name = default_name
215
- opts.on_tail('--name NAME', /^[^-].*/, 'Specify a name') do |value|
191
+ opts.on('--name NAME', 'Specify a name') do |value|
216
192
  name = value
217
193
  end
218
-
194
+
219
195
  # Add option to add args
220
196
  use_args = []
221
- opts.on_tail('--use FILE', /^[^-].*/, 'Loads inputs from file') do |value|
222
- obj = YAML.load_file(value)
223
- case obj
224
- when Hash
225
- obj.values.each do |array|
226
- # error if value isn't an array
227
- use_args.concat(array)
228
- end
229
- when Array
230
- use_args.concat(obj)
231
- else
232
- use_args << obj
233
- end
197
+ opts.on('--use FILE', 'Loads inputs from file') do |path|
198
+ use(path, use_args)
234
199
  end
235
200
 
236
- # parse the argv
237
- opts.parse!(argv)
238
-
239
201
  # build and reconfigure the instance and any associated
240
202
  # batch objects as specified in the file configurations
241
- obj = new({}, name, app)
242
- path_configs = load_config(app.config_filepath(name))
243
- if path_configs.kind_of?(Array)
244
- path_configs.each_with_index do |path_config, i|
245
- next if i == 0
246
- batch_obj = obj.initialize_batch_obj(path_config, "#{name}_#{i}")
247
- batch_obj.reconfigure(argv_config)
248
- end
249
- path_configs = path_configs[0]
203
+ argv = opts.parse!(argv)
204
+ configs = load(app.config_filepath(name))
205
+ configs = [configs] unless configs.kind_of?(Array)
206
+
207
+ obj = new(configs.shift, name, app)
208
+ configs.each do |config|
209
+ obj.initialize_batch_obj(config, "#{name}_#{obj.batch.length}")
210
+ end
211
+
212
+ obj.batch.each do |batch_obj|
213
+ batch_obj.reconfigure(opts.config)
250
214
  end
251
- obj.reconfigure(path_configs).reconfigure(argv_config)
252
215
 
253
216
  [obj, (argv + use_args)]
254
217
  end
@@ -256,58 +219,143 @@ module Tap
256
219
  # A convenience method to parse the argv and execute the instance
257
220
  # with the remaining arguments. If 'help' is specified in the argv,
258
221
  # execute prints the help and exits.
222
+ #
223
+ # Returns the non-audited result.
259
224
  def execute(argv=ARGV)
260
- instance, args = parse(ARGV) do |help|
261
- puts help
262
- exit
263
- end
264
-
225
+ instance, args = parse(ARGV)
265
226
  instance.execute(*args)
266
227
  end
267
-
268
- # Returns the class lazydoc, resolving if specified.
269
- def lazydoc(resolve=true)
270
- lazydoc = super(false)
271
- lazydoc[self.to_s]['args'] ||= lazydoc.register_method(:process, Support::Lazydoc::Method)
272
- super
273
- end
274
228
 
275
229
  DEFAULT_HELP_TEMPLATE = %Q{<% manifest = task_class.manifest %>
276
- <%= task_class %><%= manifest.subject.to_s.strip.empty? ? '' : ' -- ' %><%= manifest.subject %>
230
+ <%= task_class %><%= manifest.empty? ? '' : ' -- ' %><%= manifest.to_s %>
277
231
 
278
- <% unless manifest.empty? %>
232
+ <% desc = manifest.kind_of?(Lazydoc::Comment) ? manifest.wrap(77, 2, nil) : [] %>
233
+ <% unless desc.empty? %>
279
234
  <%= '-' * 80 %>
280
235
 
281
- <% manifest.wrap(77, 2, nil).each do |line| %>
236
+ <% desc.each do |line| %>
282
237
  <%= line %>
283
238
  <% end %>
284
239
  <%= '-' * 80 %>
285
240
  <% end %>
286
241
 
287
242
  }
243
+
288
244
  # Returns the class help.
289
245
  def help
290
246
  Tap::Support::Templater.new(DEFAULT_HELP_TEMPLATE, :task_class => self).build
291
247
  end
292
248
 
249
+ # Recursively loads path into a nested configuration file.
250
+ #--
251
+ # TODO: move the logic of this to Configurable
252
+ def load(path, recursive=true)
253
+ base = Root.trivial?(path) ? {} : (YAML.load_file(path) || {})
254
+
255
+ if recursive
256
+ # determine the files/dirs to load recursively
257
+ # and add them to paths by key (ie the base
258
+ # name of the path, minus any extname)
259
+ paths = {}
260
+ files, dirs = Dir.glob("#{path.chomp(File.extname(path))}/*").partition do |sub_path|
261
+ File.file?(sub_path)
262
+ end
263
+
264
+ # directories are added to paths first so they can be
265
+ # overridden by the files (appropriate since the file
266
+ # will recursively load the directory if it exists)
267
+ dirs.each do |dir|
268
+ paths[File.basename(dir)] = dir
269
+ end
270
+
271
+ # when adding files, check that no two files map to
272
+ # the same key (ex a.yml, a.yaml).
273
+ files.each do |filepath|
274
+ key = File.basename(filepath).chomp(File.extname(filepath))
275
+ if existing = paths[key]
276
+ if File.file?(existing)
277
+ confict = [File.basename(paths[key]), File.basename(filepath)].sort
278
+ raise "multiple files load the same key: #{confict.inspect}"
279
+ end
280
+ end
281
+
282
+ paths[key] = filepath
283
+ end
284
+
285
+ # recursively load each file and reverse merge
286
+ # the result into the base
287
+ paths.each_pair do |key, recursive_path|
288
+ value = nil
289
+ each_hash_in(base) do |hash|
290
+ unless hash.has_key?(key)
291
+ hash[key] = (value ||= load(recursive_path, true))
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ base
298
+ end
299
+
300
+ # Loads the contents of path onto argv.
301
+ def use(path, argv=ARGV)
302
+ obj = Root.trivial?(path) ? [] : (YAML.load_file(path) || [])
303
+
304
+ case obj
305
+ when Array then argv.concat(obj)
306
+ else argv << obj
307
+ end
308
+
309
+ argv
310
+ end
311
+
293
312
  protected
294
313
 
295
- # Sets a class-level dependency. When task class B depends_on another task
296
- # class A, instances of B are initialized to depend on A.instance, with the
297
- # specified arguments. Returns self.
314
+ # Sets a class-level dependency; when task class B depends_on another
315
+ # task class A, instances of B are initialized to depend on A.instance.
316
+ # If a non-nil name is specified, depends_on will create a reader of
317
+ # the resolved dependency value.
318
+ #
319
+ # class A < Tap::Task
320
+ # def process
321
+ # "result"
322
+ # end
323
+ # end
324
+ #
325
+ # class B < Tap::Task
326
+ # depends_on :a, A
327
+ # end
328
+ #
329
+ # b = B.new
330
+ # b.dependencies # => [A.instance]
331
+ # b.a # => "result"
332
+ #
333
+ # A.instance.resolved? # => true
334
+ #
335
+ # Normally class-level dependencies are not added to existing instances
336
+ # but, as a special case, depends_on updates instance to depend on
337
+ # dependency_class.instance.
338
+ #
339
+ # Returns self.
298
340
  def depends_on(name, dependency_class)
299
341
  unless dependencies.include?(dependency_class)
300
342
  dependencies << dependency_class
301
343
  end
302
344
 
303
- # returns the resolved result of the dependency
304
- define_method(name) do
305
- instance = dependency_class.instance
306
- instance.resolve
307
- instance._result._current
345
+ # update instance with the dependency if necessary
346
+ if instance_variable_defined?(:@instance)
347
+ instance.depends_on(dependency_class.instance)
348
+ end
349
+
350
+ if name
351
+ # returns the resolved result of the dependency
352
+ define_method(name) do
353
+ dependency_class.instance.resolve.value
354
+ end
355
+
356
+ public(name)
308
357
  end
309
358
 
310
- public(name)
311
359
  self
312
360
  end
313
361
 
@@ -394,45 +442,42 @@ module Tap
394
442
  subclass.send(:define_method, :process, &block)
395
443
  end
396
444
 
397
- # define methods
398
- instance_var = "@#{name}".to_sym
399
- reader = (options[:reader] ||= "#{name}_config".to_sym)
400
- writer = (options[:writer] ||= "#{name}_config=".to_sym)
401
-
402
- attr_reader name
403
-
404
- define_method(reader) do
405
- # return the config for the instance
406
- instance_variable_get(instance_var).config
407
- end
408
-
409
- define_method(writer) do |value|
410
- # initialize or reconfigure the instance of subclass
411
- if instance_variable_defined?(instance_var)
412
- instance_variable_get(instance_var).reconfigure(value)
413
- else
414
- instance_variable_set(instance_var, subclass.new(value))
415
- end
416
- end
417
- public(name, reader, writer)
418
-
419
445
  # add the configuration
420
446
  if options[:desc] == nil
421
- caller[0] =~ Support::Lazydoc::CALLER_REGEXP
422
- desc = Support::Lazydoc.register($1, $3.to_i - 1, Support::Lazydoc::Definition)
423
- desc.subclass = subclass
447
+ caller[0] =~ Lazydoc::CALLER_REGEXP
448
+ desc = Lazydoc.register($1, $3.to_i - 1)#, Lazydoc::Definition)
449
+ #desc.subclass = subclass
424
450
  options[:desc] = desc
425
451
  end
426
452
 
427
- configurations.add(name, subclass.configurations.instance_config, options)
453
+ nest(name, subclass, options) {|overrides| subclass.new(overrides) }
454
+ end
455
+
456
+ private
457
+
458
+ # helper for load_config. yields each hash in the collection (ie each
459
+ # member of an Array, or the collection if it is a hash). returns
460
+ # the collection.
461
+ def each_hash_in(collection) # :nodoc:
462
+ case collection
463
+ when Hash then yield(collection)
464
+ when Array
465
+ collection.each do |hash|
466
+ yield(hash) if hash.kind_of?(Hash)
467
+ end
468
+ end
469
+
470
+ collection
428
471
  end
429
472
  end
430
473
 
431
474
  instance_variable_set(:@source_file, __FILE__)
432
475
  instance_variable_set(:@default_name, 'tap/task')
433
476
  instance_variable_set(:@dependencies, [])
477
+
434
478
  lazy_attr :manifest
435
- lazy_attr :args
479
+ lazy_attr :args, :process
480
+ lazy_register :process, Lazydoc::Arguments
436
481
 
437
482
  # The name of self.
438
483
  #--
@@ -446,21 +491,21 @@ module Tap
446
491
 
447
492
  @name = name || self.class.default_name
448
493
  @app = app
449
- @_method_name = :execute_with_callbacks
494
+ @method_name = :execute_with_callbacks
450
495
  @on_complete_block = nil
451
496
  @dependencies = []
452
497
  @batch = [self]
453
498
 
454
499
  case config
455
- when Support::InstanceConfiguration
500
+ when DelegateHash
456
501
  # update is prudent to ensure all configs have an input
457
502
  # (and hence, all configs will be initialized)
458
- @config = config.update(self.class.configurations)
459
- config.bind(self)
503
+ @config = config.update.bind(self)
460
504
  else
461
505
  initialize_config(config)
462
506
  end
463
507
 
508
+ # setup class dependencies
464
509
  self.class.dependencies.each do |dependency_class|
465
510
  depends_on(dependency_class.instance)
466
511
  end