tap 0.7.9 → 0.8.0

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