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.
- data/History +28 -0
- data/MIT-LICENSE +1 -1
- data/README +71 -43
- data/Rakefile +81 -64
- data/Tutorial +235 -0
- data/bin/tap +80 -44
- data/lib/tap.rb +41 -12
- data/lib/tap/app.rb +243 -246
- data/lib/tap/file_task.rb +357 -118
- data/lib/tap/generator.rb +88 -29
- data/lib/tap/generator/generators/config/config_generator.rb +4 -2
- data/lib/tap/generator/generators/config/templates/config.erb +1 -2
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
- data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
- data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
- data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
- data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
- data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
- data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
- data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
- data/lib/tap/generator/generators/package/package_generator.rb +38 -0
- data/lib/tap/generator/generators/package/templates/package.erb +186 -0
- data/lib/tap/generator/generators/root/root_generator.rb +14 -9
- data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
- data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
- data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
- data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +17 -0
- data/lib/tap/generator/generators/script/templates/script.erb +42 -0
- data/lib/tap/generator/generators/task/task_generator.rb +1 -1
- data/lib/tap/generator/generators/task/templates/task.erb +24 -16
- data/lib/tap/generator/generators/task/templates/test.erb +13 -17
- data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
- data/lib/tap/root.rb +108 -146
- data/lib/tap/script.rb +362 -0
- data/lib/tap/script/console.rb +28 -0
- data/lib/tap/script/destroy.rb +13 -1
- data/lib/tap/script/generate.rb +13 -1
- data/lib/tap/script/run.rb +100 -57
- data/lib/tap/support/batch_queue.rb +0 -3
- data/lib/tap/support/logger.rb +6 -3
- data/lib/tap/support/rake.rb +54 -0
- data/lib/tap/support/task_configuration.rb +169 -0
- data/lib/tap/support/tdoc.rb +198 -0
- data/lib/tap/support/tdoc/config_attr.rb +338 -0
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
- data/lib/tap/support/versions.rb +33 -1
- data/lib/tap/task.rb +339 -227
- data/lib/tap/test.rb +86 -128
- data/lib/tap/test/env_vars.rb +16 -5
- data/lib/tap/test/file_methods.rb +373 -0
- data/lib/tap/test/subset_methods.rb +299 -180
- data/lib/tap/version.rb +2 -1
- data/lib/tap/workflow.rb +2 -0
- data/test/app/lib/app_test_task.rb +1 -0
- data/test/app_test.rb +327 -83
- data/test/check/binding_eval.rb +23 -0
- data/test/check/define_method_check.rb +22 -0
- data/test/check/dependencies_check.rb +175 -0
- data/test/check/inheritance_check.rb +22 -0
- data/test/file_task_test.rb +524 -291
- data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
- data/test/root/glob/two.txt +0 -0
- data/test/root_test.rb +330 -262
- data/test/script_test.rb +194 -0
- data/test/support/audit_test.rb +5 -2
- data/test/support/combinator_test.rb +10 -10
- data/test/support/rake_test.rb +35 -0
- data/test/support/task_configuration_test.rb +272 -0
- data/test/support/tdoc_test.rb +363 -0
- data/test/support/templater_test.rb +2 -2
- data/test/support/versions_test.rb +32 -0
- data/test/tap_test_helper.rb +39 -0
- data/test/task_base_test.rb +115 -0
- data/test/task_class_test.rb +56 -4
- data/test/task_execute_test.rb +29 -0
- data/test/task_test.rb +89 -70
- data/test/test/env_vars_test.rb +48 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
- data/test/test/file_methods_test.rb +204 -0
- data/test/test/subset_methods_test.rb +93 -33
- data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
- data/test/test/test_file_task_test/expected/one.txt +1 -0
- data/test/test/test_file_task_test/expected/two.txt +1 -0
- data/test/test/test_file_task_test/input/one.txt +1 -0
- data/test/test/test_file_task_test/input/two.txt +1 -0
- data/test/test_test.rb +143 -3
- data/test/workflow_test.rb +2 -0
- data/vendor/rails_generator.rb +56 -0
- data/vendor/rails_generator/base.rb +263 -0
- data/vendor/rails_generator/commands.rb +581 -0
- data/vendor/rails_generator/generated_attribute.rb +42 -0
- data/vendor/rails_generator/lookup.rb +209 -0
- data/vendor/rails_generator/manifest.rb +53 -0
- data/vendor/rails_generator/options.rb +143 -0
- data/vendor/rails_generator/scripts.rb +83 -0
- data/vendor/rails_generator/scripts/destroy.rb +7 -0
- data/vendor/rails_generator/scripts/generate.rb +7 -0
- data/vendor/rails_generator/scripts/update.rb +12 -0
- data/vendor/rails_generator/simple_logger.rb +46 -0
- data/vendor/rails_generator/spec.rb +44 -0
- metadata +180 -196
- data/lib/tap/generator/generators/root/templates/app.yml +0 -19
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
- data/lib/tap/script/server.rb +0 -12
- data/lib/tap/support/rap.rb +0 -38
- data/lib/tap/test/inference_methods.rb +0 -298
- data/test/task/config/task_with_config.yml +0 -1
- 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
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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)
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
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.
|
|
46
|
-
# a task queues inputs to itself,
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
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.
|
|
54
|
-
# t1.
|
|
53
|
+
# t1.enq 1
|
|
54
|
+
# t1.enq 2,3
|
|
55
55
|
# t1.app.run
|
|
56
|
-
#
|
|
57
56
|
# runlist # => [1,2,3]
|
|
58
57
|
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
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.
|
|
69
|
-
# t2.
|
|
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
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# of
|
|
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
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
#
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
#
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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.
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
|
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.
|
|
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, :
|
|
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
|
|
371
|
-
def
|
|
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
|