tap 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. data/History +28 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README +71 -43
  4. data/Rakefile +81 -64
  5. data/Tutorial +235 -0
  6. data/bin/tap +80 -44
  7. data/lib/tap.rb +41 -12
  8. data/lib/tap/app.rb +243 -246
  9. data/lib/tap/file_task.rb +357 -118
  10. data/lib/tap/generator.rb +88 -29
  11. data/lib/tap/generator/generators/config/config_generator.rb +4 -2
  12. data/lib/tap/generator/generators/config/templates/config.erb +1 -2
  13. data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
  14. data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
  15. data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
  16. data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
  17. data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
  18. data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
  19. data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
  20. data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
  21. data/lib/tap/generator/generators/package/package_generator.rb +38 -0
  22. data/lib/tap/generator/generators/package/templates/package.erb +186 -0
  23. data/lib/tap/generator/generators/root/root_generator.rb +14 -9
  24. data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
  25. data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
  26. data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
  27. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
  28. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
  29. data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
  30. data/lib/tap/generator/generators/script/script_generator.rb +17 -0
  31. data/lib/tap/generator/generators/script/templates/script.erb +42 -0
  32. data/lib/tap/generator/generators/task/task_generator.rb +1 -1
  33. data/lib/tap/generator/generators/task/templates/task.erb +24 -16
  34. data/lib/tap/generator/generators/task/templates/test.erb +13 -17
  35. data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
  36. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  37. data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
  38. data/lib/tap/root.rb +108 -146
  39. data/lib/tap/script.rb +362 -0
  40. data/lib/tap/script/console.rb +28 -0
  41. data/lib/tap/script/destroy.rb +13 -1
  42. data/lib/tap/script/generate.rb +13 -1
  43. data/lib/tap/script/run.rb +100 -57
  44. data/lib/tap/support/batch_queue.rb +0 -3
  45. data/lib/tap/support/logger.rb +6 -3
  46. data/lib/tap/support/rake.rb +54 -0
  47. data/lib/tap/support/task_configuration.rb +169 -0
  48. data/lib/tap/support/tdoc.rb +198 -0
  49. data/lib/tap/support/tdoc/config_attr.rb +338 -0
  50. data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
  51. data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
  52. data/lib/tap/support/versions.rb +33 -1
  53. data/lib/tap/task.rb +339 -227
  54. data/lib/tap/test.rb +86 -128
  55. data/lib/tap/test/env_vars.rb +16 -5
  56. data/lib/tap/test/file_methods.rb +373 -0
  57. data/lib/tap/test/subset_methods.rb +299 -180
  58. data/lib/tap/version.rb +2 -1
  59. data/lib/tap/workflow.rb +2 -0
  60. data/test/app/lib/app_test_task.rb +1 -0
  61. data/test/app_test.rb +327 -83
  62. data/test/check/binding_eval.rb +23 -0
  63. data/test/check/define_method_check.rb +22 -0
  64. data/test/check/dependencies_check.rb +175 -0
  65. data/test/check/inheritance_check.rb +22 -0
  66. data/test/file_task_test.rb +524 -291
  67. data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
  68. data/test/root/glob/two.txt +0 -0
  69. data/test/root_test.rb +330 -262
  70. data/test/script_test.rb +194 -0
  71. data/test/support/audit_test.rb +5 -2
  72. data/test/support/combinator_test.rb +10 -10
  73. data/test/support/rake_test.rb +35 -0
  74. data/test/support/task_configuration_test.rb +272 -0
  75. data/test/support/tdoc_test.rb +363 -0
  76. data/test/support/templater_test.rb +2 -2
  77. data/test/support/versions_test.rb +32 -0
  78. data/test/tap_test_helper.rb +39 -0
  79. data/test/task_base_test.rb +115 -0
  80. data/test/task_class_test.rb +56 -4
  81. data/test/task_execute_test.rb +29 -0
  82. data/test/task_test.rb +89 -70
  83. data/test/test/env_vars_test.rb +48 -0
  84. data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
  85. data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
  86. data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
  87. data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
  88. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  89. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  90. data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
  91. data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
  92. data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
  93. data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
  94. data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
  95. data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
  96. data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
  97. data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
  98. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  99. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  100. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  101. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  102. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  103. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  104. data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
  105. data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
  106. data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
  107. data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
  108. data/test/test/file_methods_test.rb +204 -0
  109. data/test/test/subset_methods_test.rb +93 -33
  110. data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
  111. data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
  112. data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
  113. data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
  114. data/test/test/test_file_task_test/expected/one.txt +1 -0
  115. data/test/test/test_file_task_test/expected/two.txt +1 -0
  116. data/test/test/test_file_task_test/input/one.txt +1 -0
  117. data/test/test/test_file_task_test/input/two.txt +1 -0
  118. data/test/test_test.rb +143 -3
  119. data/test/workflow_test.rb +2 -0
  120. data/vendor/rails_generator.rb +56 -0
  121. data/vendor/rails_generator/base.rb +263 -0
  122. data/vendor/rails_generator/commands.rb +581 -0
  123. data/vendor/rails_generator/generated_attribute.rb +42 -0
  124. data/vendor/rails_generator/lookup.rb +209 -0
  125. data/vendor/rails_generator/manifest.rb +53 -0
  126. data/vendor/rails_generator/options.rb +143 -0
  127. data/vendor/rails_generator/scripts.rb +83 -0
  128. data/vendor/rails_generator/scripts/destroy.rb +7 -0
  129. data/vendor/rails_generator/scripts/generate.rb +7 -0
  130. data/vendor/rails_generator/scripts/update.rb +12 -0
  131. data/vendor/rails_generator/simple_logger.rb +46 -0
  132. data/vendor/rails_generator/spec.rb +44 -0
  133. metadata +180 -196
  134. data/lib/tap/generator/generators/root/templates/app.yml +0 -19
  135. data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
  136. data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
  137. data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
  138. data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
  139. data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
  140. data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
  141. data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
  142. data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
  143. data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
  144. data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
  145. data/lib/tap/script/server.rb +0 -12
  146. data/lib/tap/support/rap.rb +0 -38
  147. data/lib/tap/test/inference_methods.rb +0 -298
  148. data/test/task/config/task_with_config.yml +0 -1
  149. data/test/test/inference_methods_test.rb +0 -311
data/lib/tap/task.rb CHANGED
@@ -1,32 +1,31 @@
1
- require 'monitor'
2
-
3
1
  module Tap
4
-
2
+
5
3
  # == Overview
6
4
  #
7
5
  # Tasks are the basic executable unit of Tap. When an App executes
8
- # a Task, the Task will be passed an array of inputs which it then
9
- # processes into outputs. Tasks are joined into workflows using
10
- # conditions that define when a set of inputs are ready for execution,
11
- # and what to do with the outputs when a the Task completes. Tasks
12
- # therefore fundamentally consist of a condition_block, a process
13
- # method, and an on_complete_block.
6
+ # a Task, the Task will be passed inputs which it then processes into
7
+ # results. Tasks are joined into workflows using condition blocks
8
+ # defining when a set of inputs are ready for execution, and where to
9
+ # pass results when the Task completes. Tasks fundamentally consist
10
+ # of a condition_block, a process method, and an on_complete_block.
14
11
  #
15
12
  # Tasks are configurable. By default each task will be configured
16
- # with the class default_config, which can be set when the class is
17
- # defined.
13
+ # with the default class configurations, which can be set when the
14
+ # class is defined.
18
15
  #
19
16
  # class ConfiguredTask < Tap::Task
20
- # set_default_config :one => 'one', :two => 'two'
17
+ # config :one, 'one'
18
+ # config :two, 'two'
21
19
  # end
22
20
  #
23
21
  # t = ConfiguredTask.new
24
22
  # t.config # => {:one => 'one', :two => 'two'}
25
23
  #
26
- # Tasks also have a name (based on the class by default) which is
27
- # used during initialization to lookup and merge any configurations
28
- # in the corresponding config_file. Additional, overriding
29
- # configurations may be provided during initialization as well.
24
+ # Tasks also have a name (based on the class name by default). The
25
+ # task name is used as a relative filepath from application directories
26
+ # to assoicated files. During initialization, the task name is used
27
+ # to lookup configurations from config_file. Additional and overriding
28
+ # configurations may be provided during initialization.
30
29
  #
31
30
  # t.name # => "configured_task"
32
31
  # t.app[:config] # => "/path/to/app/config"
@@ -42,38 +41,47 @@ module Tap
42
41
  #
43
42
  # === Batches
44
43
  #
45
- # Tasks are designed to facilitate batch processing of inputs. When
46
- # a task queues inputs to itself, all inputs are collected into a
47
- # single batch until execution at which time all inputs are passed
48
- # to the task at once. If the task is iterative (the default), then
49
- # each of these inputs is processed individually:
44
+ # Tasks are designed to facilitate batch processing of inputs. Each
45
+ # time a task queues inputs to itself, the inputs are collected into a
46
+ # batch. Upon execution, all inputs are passed to the task at once.
47
+ # If the task is iterative (the default), then each of these inputs is
48
+ # processed individually. Otherwise, all inputs are processed as a single
49
+ # group:
50
50
  #
51
51
  # runlist = []
52
52
  # t1 = Task.new {|task, input| runlist << input}
53
- # t1.queue 1
54
- # t1.queue 2,3
53
+ # t1.enq 1
54
+ # t1.enq 2,3
55
55
  # t1.app.run
56
- #
57
56
  # runlist # => [1,2,3]
58
57
  #
59
- # However, tasks are also designed to facilitate batch processing
60
- # using a variety (ie batch) of configurations. When a task is
61
- # queued, each task in the batch attribute will be queued as well.
58
+ # runlist = []
59
+ # t1.iterate = false
60
+ # t1.enq 1
61
+ # t1.enq 2,3
62
+ # t1.app.run
63
+ # runlist # => [[1,2,3]]
64
+ #
65
+ # Additionally, tasks are designed to facilitate processing using a batch
66
+ # of related tasks. Often these batches consist of the same task class,
67
+ # but with a variety of configurations, but they can be assembled in other
68
+ # ways. When a task is queued, each task in the batch will be queued:
62
69
  #
63
70
  # runlist = []
71
+ # t1 = Task.new {|task, input| runlist << input}
64
72
  # t1.batch # => [t1]
65
73
  # t2 = t1.create_batch_task
66
74
  # t1.batch # => [t1, t2]
67
75
  #
68
- # t1.queue 1
69
- # t2.queue 2,3
76
+ # t1.enq 1
77
+ # t2.enq 2,3
70
78
  # t1.app.run
71
- #
72
79
  # runlist # => [1,2,3, 1,2,3]
73
80
  #
74
- # In the example, runlist reflects that t1 and t2 were run in succession
75
- # with the same [1,2,3] inputs. Configuration files may specify an array
76
- # of configuration templates; each is translated into a batched task.
81
+ # Here runlist reflects that t1 and t2 were run in succession with the same [1,2,3]
82
+ # inputs. Configuration batches are automatically generated when task.config_file
83
+ # specifies an array of configurations; each configuration is translated into a
84
+ # batched task.
77
85
  #
78
86
  # # [/path/to/app/config/batch.yml]
79
87
  # # - one: ONE
@@ -91,11 +99,10 @@ module Tap
91
99
  # t2.config_template # => {:one => 'ANOTHER ONE'}
92
100
  # t2.config # => {:one => 'ANOTHER ONE', :two => 'two'}
93
101
  #
94
- # In addition, configuration files are processed to make the definition
95
- # of configuration templates more concise and powerful
96
- # (see Tap::Support::Templater for more details). The most basic form
97
- # merges each variation with the rest of the file to produce the final
98
- # array of templates.
102
+ # Task processes configuration files to make the definition of configuration
103
+ # templates more DRY and powerful (see Tap::Support::Templater for more
104
+ # details). Here the default ConfiguredTask configurations are overridden
105
+ # by variations created by the variations! tag:
99
106
  #
100
107
  # # [/path/to/app/config/template.yml]
101
108
  # # one: ONE
@@ -113,47 +120,63 @@ module Tap
113
120
  # All together, these features make it easy to process a batch of inputs
114
121
  # using a batch of configurations -- all encapsulated in a workflow-ready
115
122
  # architecture.
123
+ #
124
+ # == Non-Task Tasks!
125
+ #
126
+ # The methods definining the essential behavior of a Task are encapsulated in
127
+ # the Tap::Task::Base module. Using this module, Non-Task classes can be defined,
128
+ # and Non-Task objects extended to behave like Tasks; ie they can be enqued, run,
129
+ # and incorporated into workflows.
130
+ #
131
+ # See Tap::Task::Base for more details (esp for objects with a process method),
132
+ # as well as Tap::Support::Rake, which makes Rake tasks behave like Tap tasks.
133
+ #
116
134
  class Task < Monitor
117
- write_inheritable_hash(:default_config, {})
118
- class_inheritable_hash(:default_config)
135
+ write_inheritable_attribute(:configurations, Support::TaskConfiguration.new)
136
+ class_inheritable_reader(:configurations)
119
137
 
120
138
  write_inheritable_attribute(:iterative, true)
121
139
  class_inheritable_reader(:iterative)
122
-
140
+
141
+ write_inheritable_attribute(:source_files, [])
142
+ class_inheritable_reader(:source_files)
143
+
123
144
  class << self
124
-
125
- # Sets the defualt configurations for the task class. Configurations
126
- # are inherited, but can be overridden or added to in subclasses. Use
127
- # options (:make_accessors, :make_readers, :make_writers) to create
128
- # accessors for configurations.
129
- #
130
- # class SomeTask < Tap::Task
131
- # set_default_config({
132
- # :key => 'value'},
133
- # :make_accessors => true)
134
- # end
135
- #
136
- # t = SomeTask.new
137
- # t.key # => 'value'
138
- # t.key = 'another'
139
- # t.config # => {:key => 'another'}
145
+
146
+ # Currently an experimental way of identifying source files for TDoc
147
+ # documentation.
148
+ def source_file(arg) # :nodoc:
149
+ source_files << arg
150
+ end
151
+
152
+ # Declares a configuration without any accessors.
140
153
  #
141
- def set_default_config(hash={}, options={})
142
- keys = hash.keys
143
- self.write_inheritable_hash(:default_config, hash)
144
-
145
- self.config_accessor(*keys) if options[:make_accessors]
146
- self.config_reader(*keys) if options[:make_readers]
147
- self.config_writer(*keys) if options[:make_writers]
154
+ # With no keys specified, sets config to make no accessors
155
+ # for each new configuration.
156
+ def declare_config(*keys)
157
+ if keys.empty?
158
+ self.config_mode = :none
159
+ else
160
+ keys.each do |key|
161
+ configurations.declare(key, self)
162
+ end
163
+ end
148
164
  end
149
165
 
150
166
  # Creates a configuration writer for the input keys. Works like
151
167
  # attr_writer, except the value is written to config, rather than
152
- # a local variable.
168
+ # a local variable. In addition, the config will be validated
169
+ # using validate_config upon setting the value.
170
+ #
171
+ # With no keys specified, sets config to create config_writer
172
+ # for each new configuration.
153
173
  def config_writer(*keys)
154
- keys.each do |key|
155
- define_method("#{key}=") do |value|
156
- config[key] = value
174
+ if keys.empty?
175
+ self.config_mode = :config_writer
176
+ else
177
+ keys.each do |key|
178
+ configurations.declare(key, self)
179
+ define_config_writer(key)
157
180
  end
158
181
  end
159
182
  end
@@ -161,10 +184,16 @@ module Tap
161
184
  # Creates a configuration reader for the input keys. Works like
162
185
  # attr_reader, except the value is read from config, rather than
163
186
  # a local variable.
187
+ #
188
+ # With no keys specified, sets config to create a config_reader
189
+ # for each new configuration.
164
190
  def config_reader(*keys)
165
- keys.each do |key|
166
- define_method(key) do
167
- config[key]
191
+ if keys.empty?
192
+ self.config_mode = :config_reader
193
+ else
194
+ keys.each do |key|
195
+ configurations.declare(key, self)
196
+ define_config_reader(key)
168
197
  end
169
198
  end
170
199
  end
@@ -172,27 +201,248 @@ module Tap
172
201
  # Creates configuration accessors for the input keys. Works like
173
202
  # attr_accessor, except the value is read from and written to config,
174
203
  # rather than a local variable.
204
+ #
205
+ # With no keys specified, sets config to create a config_accessor
206
+ # for each new configuration.
175
207
  def config_accessor(*keys)
176
- config_writer(*keys)
177
- config_reader(*keys)
208
+ if keys.empty?
209
+ self.config_mode = :config_accessor
210
+ else
211
+ keys.each do |key|
212
+ configurations.declare(key, self)
213
+ define_config_reader(key)
214
+ define_config_writer(key)
215
+ end
216
+ end
217
+ end
218
+
219
+ # Sets a class configuration. Configurations are inherited, but can
220
+ # be overridden or added in subclasses. Accessors are created by
221
+ # default, but this behavior can be modified by use of the other
222
+ # config methods.
223
+ #
224
+ # class SampleTask < Tap::Task
225
+ # config :key, 'value'
226
+ #
227
+ # config_reader
228
+ # config :reader_only
229
+ # end
230
+ #
231
+ # t = SampleTask.new
232
+ # t.respond_to?(:reader_only) # => true
233
+ # t.respond_to?(:reader_only=) # => false
234
+ #
235
+ # t.config # => {:key => 'value', :reader_only => nil}
236
+ # t.key # => 'value'
237
+ # t.key = 'another'
238
+ # t.config # => {:key => 'another', :reader_only => nil}
239
+ def config(key, value=nil, attributes={})
240
+ declare_config(key)
241
+ configurations.set(key, value, attributes)
242
+
243
+ case config_mode
244
+ when nil, :config_accessor then config_accessor(key)
245
+ when :config_writer then config_writer(key)
246
+ when :config_reader then config_reader(key)
247
+ end
178
248
  end
179
249
 
180
250
  # Sets the default iteration behavior for a task class
181
- # to iterate inputs during task execution.
251
+ # to iterate inputs during task execution. As a result,
252
+ # tasks will NOT execute without inputs.
182
253
  def iterate
183
254
  self.write_inheritable_attribute(:iterative, true)
184
255
  end
185
256
 
186
257
  # Sets the default iteration behavior for a task class
187
- # to not iterate inputs during task execution.
258
+ # to not iterate inputs during task execution. As a result,
259
+ # tasks can execute without inputs.
188
260
  def do_not_iterate
189
261
  self.write_inheritable_attribute(:iterative, false)
190
262
  end
263
+
264
+ protected
265
+
266
+ attr_accessor :config_mode
267
+
268
+ private
269
+
270
+ def define_config_reader(key) # :nodoc:
271
+ define_method(key) do
272
+ config[key]
273
+ end
274
+ end
275
+
276
+ def define_config_writer(key) # :nodoc:
277
+ define_method("#{key}=") do |value|
278
+ config[key] = value
279
+ validate_config(key, value)
280
+ value
281
+ end
282
+ end
191
283
  end
192
-
193
- attr_reader :name, :config, :config_template, :app, :batch, :condition_block, :task_block, :on_complete_block, :results
194
- attr_accessor :multithread, :iterate
195
284
 
285
+ # Tap::Task::Base encapsulates the methods definining the essential
286
+ # behavior of a Task.
287
+ module Base
288
+ attr_reader :app, :batch, :condition_block, :on_complete_block, :results
289
+ attr_accessor :multithread, :iterate
290
+
291
+ # Creates a new Task with the specified attributes.
292
+ def self.extended(base)
293
+ base.extend MonitorMixin
294
+
295
+ base.instance_variable_set("@app", App.instance) if base.app.nil?
296
+ base.instance_variable_set("@batch", [base]) if base.batch.nil?
297
+ base.instance_variable_set("@results", []) if base.results.nil?
298
+ base.instance_variable_set("@condition_block", nil) if base.condition_block.nil?
299
+ base.instance_variable_set("@on_complete_block", nil) if base.on_complete_block.nil?
300
+ base.instance_variable_set("@multithread", false) if base.multithread.nil?
301
+ base.instance_variable_set("@iterate", false) if base.iterate.nil?
302
+ end
303
+
304
+ # Returns true if the batch size is greater than one
305
+ # (the one being the current task itself).
306
+ def batched?
307
+ batch.length > 1
308
+ end
309
+
310
+ # Returns the index of the current task in batch.
311
+ def batch_index
312
+ batch.index(self)
313
+ end
314
+
315
+ # Returns true if multithread is true.
316
+ #
317
+ # (NOTE -- multithreading is currently experimental!)
318
+ def multithread?
319
+ multithread
320
+ end
321
+
322
+ # Returns true if iterate is true. If iterate has not been set for the task,
323
+ # then iterate? returns the value of the class iterative variable.
324
+ def iterate?
325
+ iterate
326
+ end
327
+
328
+ # Sets a condition block for the task. Raises an error if condition_block is
329
+ # already set, unless override = true.
330
+ #
331
+ # Note that the block will recieve the audited_inputs of the task
332
+ # (see Audit for more information).
333
+ def condition(override=false, &block) # :yields: self, audited_inputs
334
+ raise "Condition for task already set: #{self}" unless condition_block.nil? || override
335
+ self.condition_block = block
336
+ end
337
+
338
+ # Sets a block to execute when the task completes execution. Raises an error
339
+ # if on_complete_block is already set, unless override = true.
340
+ #
341
+ # Note that the block will recieve the results of the task, ie an array of
342
+ # audited values and not values themselves (see Audit for more information).
343
+ def on_complete(override=false, &block) # :yields: results
344
+ raise "On complete for task already set: #{self}" unless on_complete_block.nil? || override
345
+ self.on_complete_block = block
346
+ end
347
+
348
+ # Returns true if no condition block is specified, or the condition
349
+ # block evaluates to true with the given inputs.
350
+ def executable?(inputs)
351
+ condition_block ? condition_block.call(self, inputs) : true
352
+ end
353
+
354
+ # Execute the actions associated with this task. Returns an array of Audits;
355
+ # one for each input value. Raises an error if the task is not executable with
356
+ # the inputs, and executes the on_complete block when finished.
357
+ #
358
+ # Execute is synchronized such that a task can only execute on one thread at a
359
+ # time.
360
+ def execute(*inputs)
361
+ synchronize do
362
+ check_terminate
363
+
364
+ audited_inputs = if iterate?
365
+ Support::Audit.register(*inputs)
366
+ else
367
+ Support::Audit.merge(*inputs)
368
+ end
369
+
370
+ self.results = nil
371
+ raise "Condition not met." unless executable?(audited_inputs)
372
+
373
+ before_execute
374
+ begin
375
+ self.results = if iterate?
376
+ audited_inputs.collect do |audited_input|
377
+ on_execute(audited_input)
378
+ end
379
+ else
380
+ [on_execute(audited_inputs)]
381
+ end
382
+ rescue
383
+ on_execute_error($!)
384
+ end
385
+ after_execute
386
+
387
+ on_complete_block.call(results) if on_complete_block
388
+ results
389
+ end
390
+ end
391
+
392
+ # Raises a TerminateError if the application is in state
393
+ # TERMINATE, as a mechanism to gracefully terminate threads.
394
+ #--
395
+ # Only occurs if the task is multithreaded.
396
+ #++
397
+ #
398
+ # check_terminate is called before each execution, but can
399
+ # be manually called at any time to provide a breakpoint
400
+ # in long executions.
401
+ def check_terminate
402
+ #if multithread? && app.state == App::State::TERMINATE
403
+ if app.state == App::State::TERMINATE
404
+ raise App::TerminateError.new
405
+ end
406
+ end
407
+
408
+ protected
409
+
410
+ attr_writer :app, :batch, :condition_block, :on_complete_block, :results
411
+
412
+ # Hook to execute code before inputs are processed.
413
+ def before_execute() end
414
+
415
+ # Hook for overridding the execution code that generates the
416
+ # task results. The input will be an audited version of the
417
+ # inputs array provided to execute, or, if iterate? is true,
418
+ # an audited version of each individual input provided to
419
+ # execute. on_execute should return the audited_inputs.
420
+ #
421
+ # By default, on_execute sends the current value of the audited
422
+ # input process and then records the output with self as the source.
423
+ #
424
+ # It is not advised to override the default on_execute unless
425
+ # absolutely necessary. Consider adjusting the behavior of
426
+ # process first.
427
+ def on_execute(audited_inputs)
428
+ output = process(audited_inputs._current)
429
+ audited_inputs._record(self, output)
430
+ end
431
+
432
+ # Hook to execute code after inputs are processed, but before the on_complete block.
433
+ def after_execute() end
434
+
435
+ # Hook to handle unhandled errors from processing inputs on a task level.
436
+ # By default on_execute_error simply re-raises the unhandled error.
437
+ def on_execute_error(err)
438
+ raise err
439
+ end
440
+ end
441
+
442
+ include Base
443
+
444
+ attr_reader :name, :config, :config_template, :task_block
445
+
196
446
  # Creates a new Task with the specified attributes.
197
447
  def initialize(name=nil, config=nil, app=App.instance, &task_block)
198
448
  super() # required for Monitor
@@ -237,28 +487,6 @@ module Tap
237
487
  task
238
488
  end
239
489
 
240
- # Returns true if the batch size is greater than one
241
- # (the one being the current task itself).
242
- def batched?
243
- batch.length > 1
244
- end
245
-
246
- # Returns the index of the current task in batch.
247
- def batch_index
248
- batch.index(self)
249
- end
250
-
251
- # Returns true if multithread is true.
252
- def multithread?
253
- multithread
254
- end
255
-
256
- # Returns true if iterate is true. If iterate has not been set for the task,
257
- # then iterate? returns the value of the class iterative variable.
258
- def iterate?
259
- iterate
260
- end
261
-
262
490
  # Returns the path to the configuration file used to make the task configuration
263
491
  # templates. By default this is the YAML file for the task name relative to the
264
492
  # application config directory:
@@ -275,77 +503,26 @@ module Tap
275
503
  end
276
504
 
277
505
  # Configures the task with the given configuration overrides. A Task has three
278
- # configuration sources: the class default_config, the config_template loaded from
279
- # config_file, and the inputs to this method. Configurations from these
506
+ # configuration sources: the default class configurations, the config_template
507
+ # loaded from config_file, and the inputs to this method. Configurations from these
280
508
  # sources are merged as: default_config.merge(config_template).merge(overrides)
281
509
  #
282
510
  # Configurations are symbolized before they are merged. If overrides is nil, then
283
- # they will be treated as an empty hash.
511
+ # they will be treated as an empty hash. All configurations are validated using
512
+ # validate_config.
284
513
  def config=(overrides)
285
514
  overrides = overrides.nil? ? {} : overrides.symbolize_keys
286
- @config = self.class.default_config.merge(config_template).merge(overrides)
287
-
515
+ @config = self.class.configurations.default.merge(config_template).merge(overrides)
516
+ self.config.each_pair {|key, value| validate_config(key, value) }
288
517
  self.config
289
518
  end
290
519
 
291
- # Sets a condition block for the task. Raises an error if condition_block is
292
- # already set, unless override = true.
293
- def condition(override=false, &block) # :yields: self, inputs
294
- raise "Condition for task already set: #{name}" unless condition_block.nil? || override
295
- self.condition_block = block
296
- end
297
-
298
- # Sets a block to execute when the task completes execution. Raises an error
299
- # if on_complete_block is already set, unless override = true.
300
- def on_complete(override=false, &block) # :yields: results
301
- raise "On complete for task already set: #{name}" unless on_complete_block.nil? || override
302
- self.on_complete_block = block
303
- end
304
-
305
- # Returns true if no condition block is specified, or the condition
306
- # block evaluates to true with the given inputs.
307
- def executable?(inputs)
308
- condition_block ? condition_block.call(self, inputs) : true
309
- end
310
-
311
- # Execute the actions associated with this task. Returns an array of Audits;
312
- # one for each input value. Raises an error if the task is not executable with
313
- # the inputs, and executes the on_complete block when finished.
314
- #
315
- # Execute is synchronized such that a task can only execute on one thread at a
316
- # time.
317
- def execute(*inputs)
318
- synchronize do
319
- self.results = nil
320
- raise "Condition not met." unless executable?(inputs)
321
-
322
- inputs = case
323
- when inputs.empty? then inputs
324
- when iterate? then Support::Audit.register(*inputs)
325
- else [Support::Audit.merge(*inputs)]
326
- end
327
-
328
- before_execute
329
- begin
330
- self.results = inputs.each do |input|
331
- output = process( input._current )
332
- input._record(self, output)
333
- end
334
- rescue
335
- on_execute_error($!)
336
- end
337
- after_execute
338
-
339
- on_complete_block.call(results) if on_complete_block
340
- results
341
- end
342
- end
343
-
344
520
  # The method for processing inputs into outputs. Override this method in
345
521
  # subclasses to provide class-specific process logic. By default the
346
522
  # input is passed to the task_block (provided during initialization).
347
523
  # Simply returns the input if no task_block is set.
348
524
  def process(input)
525
+ check_terminate
349
526
  task_block.nil? ? input : task_block.call(self, input)
350
527
  end
351
528
 
@@ -362,20 +539,12 @@ module Tap
362
539
 
363
540
  protected
364
541
 
365
- attr_writer :name, :config_template, :app, :batch, :condition_block, :task_block, :on_complete_block, :results
366
-
367
- # Hook to execute code before inputs are processed.
368
- def before_execute() end
542
+ attr_writer :name, :config_template, :task_block
369
543
 
370
- # Hook to execute code after inputs are processed, but before the on_complete block.
371
- def after_execute() end
372
-
373
- # Hook to handle unhandled errors from processing inputs on a task level.
374
- # By default on_execute_error simply re-raises the unhandled error.
375
- def on_execute_error(err)
376
- raise err
544
+ # Hook to validate configurations.
545
+ def validate_config(key, value)
377
546
  end
378
-
547
+
379
548
  # Hook to provide class-specific processing of the config_file template
380
549
  # (ie app.config_template(config_file)). By default the template is
381
550
  # processed using Support::Templater.make_templates.
@@ -388,61 +557,4 @@ module Tap
388
557
  end
389
558
 
390
559
  end
391
- end
392
-
393
- class Hold # :nodoc:
394
- attr_accessor :help_doc
395
-
396
- def register_doc(*section_headers)
397
- section_headers << "Command Line Usage" if section_headers.empty?
398
- self.help_doc = [caller.first.sub(/:\d+$/, ''), section_headers.collect {|section| section.strip}]
399
- end
400
-
401
- def help
402
- return "No help available for #{self}" unless help_doc
403
-
404
- lines = []
405
- help_file, sections = help_doc
406
- File.open(help_file) do |file|
407
- in_section = nil
408
- while line = file.gets
409
- next unless line =~ /^#(.*)/
410
- line = $1.strip
411
-
412
- if line =~ /^=+(.*)/
413
- line = $1.strip
414
- in_section = sections.delete(line)
415
- line += ":"
416
- else
417
- line = " " + line
418
- end
419
-
420
- lines << line if in_section
421
- end
422
- end
423
-
424
- return lines.join("\n")
425
- end
426
-
427
- # Queues self to app with the input.
428
- #def <<(input)
429
- #queue(input)
430
- #end
431
-
432
- # Ticks the monitor by printing '.', or the specified message.
433
- # Only ticks if called from within a monitor block.
434
- #def tick_monitor(action=nil, msg="", level=Logger::INFO)
435
- # app.tick_monitor(action, msg, level)
436
- #end
437
-
438
- # Prints the hash of statistics if called within a monitor block.
439
- #def monitor_stats(hash)
440
- # app.monitor_stats(hash)
441
- #end
442
-
443
- # Times the execution of the block and enables the various monitor
444
- # methods (tick_monitor, monitor_stats).
445
- # def monitor(action="beginning", msg="", &block)
446
- # app.monitor(action,msg, &block)
447
- #end
448
560
  end