tap 0.8.0 → 0.9.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/Basic Overview +151 -0
- data/Command Reference +99 -0
- data/History +24 -0
- data/MIT-LICENSE +1 -1
- data/README +29 -57
- data/Rakefile +30 -37
- data/Tutorial +243 -191
- data/bin/tap +66 -35
- data/lib/tap.rb +47 -29
- data/lib/tap/app.rb +700 -342
- data/lib/tap/{script → cmd}/console.rb +0 -0
- data/lib/tap/{script → cmd}/destroy.rb +0 -0
- data/lib/tap/{script → cmd}/generate.rb +0 -0
- data/lib/tap/cmd/run.rb +156 -0
- data/lib/tap/constants.rb +4 -0
- data/lib/tap/dump.rb +57 -0
- data/lib/tap/env.rb +316 -0
- data/lib/tap/file_task.rb +106 -109
- data/lib/tap/generator.rb +4 -1
- data/lib/tap/generator/generators/command/USAGE +6 -0
- data/lib/tap/generator/generators/command/command_generator.rb +17 -0
- data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
- data/lib/tap/generator/generators/config/USAGE +21 -0
- data/lib/tap/generator/generators/config/config_generator.rb +17 -7
- data/lib/tap/generator/generators/file_task/USAGE +3 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
- data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
- data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
- data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
- data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
- data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
- data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
- data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
- data/lib/tap/generator/generators/task/USAGE +3 -0
- data/lib/tap/generator/generators/task/task_generator.rb +18 -5
- data/lib/tap/generator/generators/task/templates/task.erb +7 -12
- data/lib/tap/generator/generators/task/templates/test.erb +10 -11
- data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
- data/lib/tap/patches/rake/testtask.rb +55 -0
- data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
- data/lib/tap/patches/ruby19/parsedate.rb +16 -0
- data/lib/tap/root.rb +172 -67
- data/lib/tap/script.rb +70 -336
- data/lib/tap/support/aggregator.rb +55 -0
- data/lib/tap/support/audit.rb +281 -280
- data/lib/tap/support/batchable.rb +59 -0
- data/lib/tap/support/class_configuration.rb +279 -0
- data/lib/tap/support/configurable.rb +92 -0
- data/lib/tap/support/configurable_methods.rb +296 -0
- data/lib/tap/support/executable.rb +98 -0
- data/lib/tap/support/executable_queue.rb +82 -0
- data/lib/tap/support/logger.rb +9 -15
- data/lib/tap/support/rake.rb +43 -54
- data/lib/tap/support/run_error.rb +32 -13
- data/lib/tap/support/shell_utils.rb +47 -0
- data/lib/tap/support/tdoc.rb +9 -8
- data/lib/tap/support/tdoc/config_attr.rb +40 -16
- data/lib/tap/support/validation.rb +77 -0
- data/lib/tap/support/versions.rb +36 -36
- data/lib/tap/task.rb +276 -482
- data/lib/tap/test.rb +20 -261
- data/lib/tap/test/env_vars.rb +7 -5
- data/lib/tap/test/file_methods.rb +126 -121
- data/lib/tap/test/subset_methods.rb +86 -45
- data/lib/tap/test/tap_methods.rb +271 -0
- data/lib/tap/workflow.rb +174 -46
- data/test/app/config/another/task.yml +1 -0
- data/test/app/config/erb.yml +2 -1
- data/test/app/config/some/task.yml +1 -0
- data/test/app/config/template.yml +2 -6
- data/test/app_test.rb +1241 -1008
- data/test/env/test_configure/recurse_a.yml +2 -0
- data/test/env/test_configure/recurse_b.yml +2 -0
- data/test/env/test_configure/tap.yml +23 -0
- data/test/env/test_load_env_config/dir/tap.yml +3 -0
- data/test/env/test_load_env_config/recurse_a.yml +2 -0
- data/test/env/test_load_env_config/recurse_b.yml +2 -0
- data/test/env/test_load_env_config/tap.yml +3 -0
- data/test/env_test.rb +198 -0
- data/test/file_task_test.rb +70 -53
- data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
- data/test/root_test.rb +621 -454
- data/test/script_test.rb +38 -174
- data/test/support/aggregator_test.rb +99 -0
- data/test/support/audit_test.rb +409 -416
- data/test/support/batchable_test.rb +74 -0
- data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
- data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
- data/test/support/configurable_test.rb +295 -0
- data/test/support/executable_queue_test.rb +103 -0
- data/test/support/executable_test.rb +38 -0
- data/test/support/logger_test.rb +17 -17
- data/test/support/rake_test.rb +4 -2
- data/test/support/shell_utils_test.rb +24 -0
- data/test/support/tdoc_test.rb +265 -258
- data/test/support/validation_test.rb +54 -0
- data/test/support/versions_test.rb +38 -38
- data/test/tap_test_helper.rb +19 -5
- data/test/tap_test_suite.rb +5 -2
- data/test/task_base_test.rb +13 -104
- data/test/task_syntax_test.rb +300 -0
- data/test/task_test.rb +258 -381
- data/test/test/env_vars_test.rb +40 -40
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
- data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
- data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
- data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
- data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
- data/test/test/file_methods_doc_test.rb +29 -0
- data/test/test/file_methods_test.rb +214 -143
- data/test/test/subset_methods_test.rb +111 -115
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
- data/test/test/tap_methods_test.rb +399 -0
- data/test/workflow_test.rb +101 -91
- metadata +86 -70
- data/lib/tap/generator/generators/package/package_generator.rb +0 -38
- data/lib/tap/generator/generators/package/templates/package.erb +0 -186
- data/lib/tap/generator/generators/script/USAGE +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +0 -17
- data/lib/tap/script/run.rb +0 -154
- data/lib/tap/support/batch_queue.rb +0 -162
- data/lib/tap/support/combinator.rb +0 -114
- data/lib/tap/support/task_configuration.rb +0 -169
- data/lib/tap/support/template.rb +0 -81
- data/lib/tap/support/templater.rb +0 -155
- data/lib/tap/version.rb +0 -4
- data/test/app/config/addition_template.yml +0 -6
- data/test/app_class_test.rb +0 -33
- data/test/check/binding_eval.rb +0 -23
- data/test/check/define_method_check.rb +0 -22
- data/test/check/dependencies_check.rb +0 -175
- data/test/check/inheritance_check.rb +0 -22
- data/test/support/batch_queue_test.rb +0 -320
- data/test/support/combinator_test.rb +0 -249
- data/test/support/template_test.rb +0 -122
- data/test/support/templater/erb.txt +0 -2
- data/test/support/templater/erb.yml +0 -2
- data/test/support/templater/somefile.txt +0 -2
- data/test/support/templater_test.rb +0 -192
- data/test/task/config/template.yml +0 -4
- data/test/task_class_test.rb +0 -170
- data/test/task_execute_test.rb +0 -262
- data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
- 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_file_compare/expected/output_1.txt +0 -3
- data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
- data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
- data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
- 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_yml_compare/expected/output_1.yml +0 -6
- data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
- data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
- data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
- data/test/test_test.rb +0 -373
@@ -12,7 +12,7 @@
|
|
12
12
|
# unaffected as the constants are reset after RDoc loads.
|
13
13
|
#
|
14
14
|
if Object.const_defined?(:RubyToken) || Object.const_defined?(:RubyLex)
|
15
|
-
class Object
|
15
|
+
class Object
|
16
16
|
old_ruby_token = const_defined?(:RubyToken) ? remove_const(:RubyToken) : nil
|
17
17
|
old_ruby_lex = const_defined?(:RubyLex) ? remove_const(:RubyLex) : nil
|
18
18
|
|
@@ -34,13 +34,13 @@ else
|
|
34
34
|
require 'rdoc/rdoc'
|
35
35
|
|
36
36
|
if Object.const_defined?(:RubyToken) && !RDoc.const_defined?(:RubyToken)
|
37
|
-
class Object
|
37
|
+
class Object
|
38
38
|
RDoc.const_set(:RubyToken, remove_const(:RubyToken))
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
if Object.const_defined?(:RubyLex) && !RDoc.const_defined?(:RubyLex)
|
43
|
-
class Object
|
43
|
+
class Object
|
44
44
|
RDoc.const_set(:RubyLex, remove_const(:RubyLex))
|
45
45
|
RDoc::RubyLex.const_set(:RubyLex, RDoc::RubyLex)
|
46
46
|
end
|
@@ -113,8 +113,8 @@ module Tap
|
|
113
113
|
lines = []
|
114
114
|
else
|
115
115
|
if normalize_comments
|
116
|
-
line =~ /^\s
|
117
|
-
line = $1.to_s
|
116
|
+
line =~ /^\s*#\s?(.*)/
|
117
|
+
line = $1.to_s
|
118
118
|
end
|
119
119
|
|
120
120
|
lines << line
|
@@ -129,7 +129,8 @@ module Tap
|
|
129
129
|
def find_class_or_module_named(name)
|
130
130
|
return self if full_name == name
|
131
131
|
(@classes.values + @modules.values).each do |c|
|
132
|
-
|
132
|
+
res = c.find_class_or_module_named(name)
|
133
|
+
return res if res
|
133
134
|
end
|
134
135
|
nil
|
135
136
|
end
|
@@ -206,7 +207,25 @@ module Tap
|
|
206
207
|
# (see 'rdoc/parsers/parse_rb' line 2509)
|
207
208
|
def parse_config(context, single, tk, comment)
|
208
209
|
tks = get_tk_to_nl
|
210
|
+
|
211
|
+
key_tk = nil
|
212
|
+
value_tk = nil
|
213
|
+
|
214
|
+
tks.each do |token|
|
215
|
+
next if token.kind_of?(TkSPACE)
|
209
216
|
|
217
|
+
if key_tk == nil
|
218
|
+
key_tk = token if token.kind_of?(TkSYMBOL)
|
219
|
+
else
|
220
|
+
case token
|
221
|
+
when TkCOMMA then value_tk = token
|
222
|
+
else
|
223
|
+
value_tk = token if value_tk.kind_of?(TkCOMMA)
|
224
|
+
break
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
210
229
|
text = ""
|
211
230
|
if tks.last.kind_of?(TkCOMMENT)
|
212
231
|
text = tks.last.text.chomp("\n").chomp("\r")
|
@@ -216,18 +235,23 @@ module Tap
|
|
216
235
|
|
217
236
|
tmp = RDoc::CodeObject.new
|
218
237
|
read_documentation_modifiers(tmp, RDoc::ATTR_MODIFIERS)
|
219
|
-
|
238
|
+
text = nil unless tmp.document_self
|
220
239
|
end
|
221
|
-
|
222
|
-
|
223
|
-
return if key_tk
|
240
|
+
|
241
|
+
tks.reverse_each {|token| unget_tk(token) }
|
242
|
+
return if key_tk == nil || text == nil
|
224
243
|
|
225
244
|
arg = key_tk.text[1..-1]
|
245
|
+
if value_tk
|
246
|
+
if text =~ /(.*):no_default:(.*)/
|
247
|
+
text = $1 + $2
|
248
|
+
else
|
249
|
+
text += " (#{value_tk.text})"
|
250
|
+
end
|
251
|
+
end
|
226
252
|
att = TDoc::ConfigAttr.new(text, arg, config_rw, comment)
|
227
253
|
att.config_declaration = get_tkread
|
228
|
-
|
229
|
-
# TODO -- it would be nice to read the default value here...
|
230
|
-
|
254
|
+
|
231
255
|
context.add_attribute(att)
|
232
256
|
end
|
233
257
|
|
@@ -241,14 +265,14 @@ module Tap
|
|
241
265
|
self.config_mode = tk.name
|
242
266
|
|
243
267
|
tks = get_tk_to_nl
|
244
|
-
is_config_mode_flag = tks.select do |
|
245
|
-
!
|
268
|
+
is_config_mode_flag = tks.select do |token|
|
269
|
+
!token.kind_of?(TkSPACE) && !token.kind_of?(TkCOMMENT)
|
246
270
|
end.empty?
|
247
271
|
|
248
272
|
# If no args are given, take this as a flag for c
|
249
273
|
return if is_config_mode_flag
|
250
274
|
|
251
|
-
tks.reverse_each {|
|
275
|
+
tks.reverse_each {|token| unget_tk(token) }
|
252
276
|
args = parse_symbol_arg
|
253
277
|
read = get_tkread
|
254
278
|
rw = "?"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
autoload(:PP, 'pp')
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
|
6
|
+
# Validation generates blocks for common validations/processing of
|
7
|
+
# configurations set through Configurable. These blocks can be passed
|
8
|
+
# to the config declarations using an ampersand (&).
|
9
|
+
#
|
10
|
+
# See the 'Configuration' section in the Tap::Task documentation for
|
11
|
+
# more details on how Validation works in practice.
|
12
|
+
module Validation
|
13
|
+
|
14
|
+
# Raised when Validation blocks fail.
|
15
|
+
class ValidationError < ArgumentError
|
16
|
+
def initialize(input, validations)
|
17
|
+
validation_str = PP.singleline_pp(validations, "")
|
18
|
+
super PP.singleline_pp(input, "expected #{validation_str} but was: ")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function
|
23
|
+
|
24
|
+
# Yaml conversion and checker. Valid if any of the validations
|
25
|
+
# match in a case statement. Otherwise raises an error.
|
26
|
+
|
27
|
+
# Returns input if any of the validations match the input, as
|
28
|
+
# in a case statement. Raises a ValidationError otherwise.
|
29
|
+
#
|
30
|
+
# For example:
|
31
|
+
#
|
32
|
+
# validate(10, [Integer, nil])
|
33
|
+
#
|
34
|
+
# Does the same as:
|
35
|
+
#
|
36
|
+
# case 10
|
37
|
+
# when Integer, nil then input
|
38
|
+
# else raise ValidationError.new(...)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
def validate(input, validations)
|
42
|
+
case input
|
43
|
+
when *validations then input
|
44
|
+
else
|
45
|
+
raise ValidationError.new(input, validations)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a block that calls validate using the block input
|
50
|
+
# and the input validations.
|
51
|
+
def check(*validations)
|
52
|
+
lambda {|input| validate(input, validations) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a block that loads input strings as YAML, then
|
56
|
+
# calls validate with the result and the input validations.
|
57
|
+
# If the block input is not a string, the block input is
|
58
|
+
# validated.
|
59
|
+
#
|
60
|
+
# b = yaml(Integer, nil)
|
61
|
+
# b.class # => Proc
|
62
|
+
# b.call(1) # => 1
|
63
|
+
# b.call("1") # => 1
|
64
|
+
# b.call(nil) # => nil
|
65
|
+
# b.call("str") # => ValidationError
|
66
|
+
#
|
67
|
+
# Note: yaml is especially useful for validating configs
|
68
|
+
# that may be specified as strings or as an actual object.
|
69
|
+
def yaml(*validations)
|
70
|
+
lambda do |input|
|
71
|
+
res = input.kind_of?(String) ? YAML.load(input) : input
|
72
|
+
validate(res, validations)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/tap/support/versions.rb
CHANGED
@@ -11,7 +11,7 @@ module Tap
|
|
11
11
|
# 'path-version.extension'. If no version is specified, then the filepath
|
12
12
|
# is returned.
|
13
13
|
#
|
14
|
-
# version("path/to/file.txt", 1.0)
|
14
|
+
# version("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
|
15
15
|
#
|
16
16
|
def version(path, version)
|
17
17
|
version = version.to_s.strip
|
@@ -26,7 +26,7 @@ module Tap
|
|
26
26
|
# Increments the version of the filepath by the specified increment.
|
27
27
|
#
|
28
28
|
# increment("path/to/file-1.0.txt", "0.0.1") # => "path/to/file-1.0.1.txt"
|
29
|
-
# increment("path/to/file.txt", 1.0)
|
29
|
+
# increment("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
|
30
30
|
#
|
31
31
|
def increment(path, increment)
|
32
32
|
path, version = deversion(path)
|
@@ -52,43 +52,43 @@ module Tap
|
|
52
52
|
# Splits the version from the input path, then returns the path and version.
|
53
53
|
# If no version is specified, then the returned version will be nil.
|
54
54
|
#
|
55
|
-
# deversion("path/to/file-1.0.txt")
|
56
|
-
# deversion("path/to/file.txt")
|
55
|
+
# deversion("path/to/file-1.0.txt") # => ["path/to/file.txt", "1.0"]
|
56
|
+
# deversion("path/to/file.txt") # => ["path/to/file.txt", nil]
|
57
57
|
#
|
58
58
|
def deversion(path)
|
59
59
|
path =~ /^(.*)-(\d(\.?\d)*)(.*)?/ ? [$1 + $4, $2] : [path, nil]
|
60
|
-
end
|
61
|
-
|
62
|
-
# A <=>
|
63
|
-
# integers, or even arrays representing the parts of a version.
|
64
|
-
#
|
65
|
-
# compare_versions("1.0.0", "0.9.9")
|
66
|
-
# compare_versions(1.1, 1.1)
|
67
|
-
# compare_versions([0,9], [0,9,1])
|
68
|
-
def compare_versions(a,b)
|
69
|
-
a, b = [a,b].collect {|item| to_integer_array(item) }
|
70
|
-
|
71
|
-
# equalize the lengths of the integer arrays
|
72
|
-
d = b.length - a.length
|
73
|
-
case
|
74
|
-
when d < 0 then b.concat Array.new(-d, 0)
|
75
|
-
when d > 0 then a.concat Array.new(d, 0)
|
76
|
-
end
|
77
|
-
|
78
|
-
a <=> b
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
# Converts an input argument (typically a string or an array)
|
84
|
-
# to an array of integers. Splits version string on "."
|
85
|
-
def to_integer_array(arg)
|
86
|
-
arr = case arg
|
87
|
-
when Array then arg
|
88
|
-
else arg.to_s.split('.')
|
89
|
-
end
|
90
|
-
arr.collect {|i| i.to_i}
|
91
|
-
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A <=> comparison for versions. compare_versions can take strings,
|
63
|
+
# integers, or even arrays representing the parts of a version.
|
64
|
+
#
|
65
|
+
# compare_versions("1.0.0", "0.9.9") # => 1
|
66
|
+
# compare_versions(1.1, 1.1) # => 0
|
67
|
+
# compare_versions([0,9], [0,9,1]) # => -1
|
68
|
+
def compare_versions(a,b)
|
69
|
+
a, b = [a,b].collect {|item| to_integer_array(item) }
|
70
|
+
|
71
|
+
# equalize the lengths of the integer arrays
|
72
|
+
d = b.length - a.length
|
73
|
+
case
|
74
|
+
when d < 0 then b.concat Array.new(-d, 0)
|
75
|
+
when d > 0 then a.concat Array.new(d, 0)
|
76
|
+
end
|
77
|
+
|
78
|
+
a <=> b
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Converts an input argument (typically a string or an array)
|
84
|
+
# to an array of integers. Splits version string on "."
|
85
|
+
def to_integer_array(arg)
|
86
|
+
arr = case arg
|
87
|
+
when Array then arg
|
88
|
+
else arg.to_s.split('.')
|
89
|
+
end
|
90
|
+
arr.collect {|i| i.to_i}
|
91
|
+
end
|
92
92
|
|
93
93
|
end
|
94
94
|
end
|
data/lib/tap/task.rb
CHANGED
@@ -1,14 +1,58 @@
|
|
1
1
|
module Tap
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
2
|
+
# = Overview
|
3
|
+
#
|
4
|
+
# Tasks are the basic organizational unit of Tap. Tasks provide
|
5
|
+
# a standard backbone for creating the working parts of an application
|
6
|
+
# by facilitating configuration, batched execution of methods, and
|
7
|
+
# interaction with the command line.
|
8
|
+
#
|
9
|
+
# The functionality of Task is built from several base modules:
|
10
|
+
# - Tap::Support::Batchable
|
11
|
+
# - Tap::Support::Configurable
|
12
|
+
# - Tap::Support::Executable
|
13
|
+
#
|
14
|
+
# Tap::Workflow is built on the same foundations; the sectons on
|
15
|
+
# configuration and batching apply equally to Workflows as Tasks.
|
16
|
+
#
|
17
|
+
# === Task Definition
|
18
|
+
#
|
19
|
+
# Tasks are instantiated with a task block; when the task is run
|
20
|
+
# the block gets called with the enqued inputs. As such, the block
|
21
|
+
# should specify the same number of inputs as you enque (plus the
|
22
|
+
# task itself, which is a standard input).
|
23
|
+
#
|
24
|
+
# no_inputs = Task.new {|task| }
|
25
|
+
# one_input = Task.new {|task, input| }
|
26
|
+
# mixed_inputs = Task.new {|task, a, b, *args| }
|
27
|
+
#
|
28
|
+
# no_inputs.enq
|
29
|
+
# one_input.enq(:a)
|
30
|
+
# mixed_inputs.enq(:a, :b)
|
31
|
+
# mixed_inputs.enq(:a, :b, 1, 2, 3)
|
32
|
+
#
|
33
|
+
# Subclasses of Task specify executable code by overridding the process
|
34
|
+
# method. In this case the number of enqued inputs should correspond to
|
35
|
+
# process (passing the task would be redundant).
|
36
|
+
#
|
37
|
+
# class NoInput < Tap::Task
|
38
|
+
# def process() end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class OneInput < Tap::Task
|
42
|
+
# def process(input) end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# class MixedInputs < Tap::Task
|
46
|
+
# def process(a, b, *args) end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# NoInput.new.enq
|
50
|
+
# OneInput.new.enq(:a)
|
51
|
+
# MixedInputs.new.enq(:a, :b)
|
52
|
+
# MixedInputs.new.enq(:a, :b, 1, 2, 3)
|
53
|
+
#
|
54
|
+
# === Configuration
|
55
|
+
#
|
12
56
|
# Tasks are configurable. By default each task will be configured
|
13
57
|
# with the default class configurations, which can be set when the
|
14
58
|
# class is defined.
|
@@ -19,516 +63,262 @@ module Tap
|
|
19
63
|
# end
|
20
64
|
#
|
21
65
|
# t = ConfiguredTask.new
|
22
|
-
# t.
|
23
|
-
#
|
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.
|
29
|
-
#
|
30
|
-
# t.name # => "configured_task"
|
31
|
-
# t.app[:config] # => "/path/to/app/config"
|
32
|
-
# t.config_file # => "/path/to/app/config/configured_task.yml"
|
33
|
-
#
|
34
|
-
# # [/path/to/app/config/example.yml]
|
35
|
-
# # one: ONE
|
36
|
-
#
|
37
|
-
# t = ConfiguredTask.new "example", :three => 'three'
|
38
|
-
# t.name # => "example"
|
39
|
-
# t.config_file # => "/path/to/app/config/example.yml"
|
40
|
-
# t.config # => {:one => 'ONE', :two => 'two', :three => 'three'}
|
66
|
+
# t.name # => "configured_task"
|
67
|
+
# t.config # => {:one => 'one', :two => 'two'}
|
41
68
|
#
|
42
|
-
#
|
69
|
+
# Configurations can be validated or processed using an optional
|
70
|
+
# block. Tap::Support::Validation pre-packages several common
|
71
|
+
# validation/processing blocks, and can be accessed through the
|
72
|
+
# class method 'c':
|
43
73
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
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:
|
74
|
+
# class ValidatingTask < Tap::Task
|
75
|
+
# # string config validated to be a string
|
76
|
+
# config :string, 'str', &c.check(String)
|
50
77
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
# t1.enq 2,3
|
55
|
-
# t1.app.run
|
56
|
-
# runlist # => [1,2,3]
|
78
|
+
# # integer config; string inputs are converted using YAML
|
79
|
+
# config :integer, 1, &c.yaml(Integer)
|
80
|
+
# end
|
57
81
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# t1.enq 2,3
|
62
|
-
# t1.app.run
|
63
|
-
# runlist # => [[1,2,3]]
|
82
|
+
# t = ValidatingTask.new
|
83
|
+
# t.string = 1 # => ValidationError
|
84
|
+
# t.integer = 1.1 # => ValidationError
|
64
85
|
#
|
65
|
-
#
|
66
|
-
#
|
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:
|
86
|
+
# t.integer = "1"
|
87
|
+
# t.integer == 1 # => true
|
69
88
|
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
89
|
+
# Tasks have a name that gets used as a relative filepath to find
|
90
|
+
# associated files (for instance config_file). By default the task
|
91
|
+
# name is based on the task class, such that Tap::Task corresponds
|
92
|
+
# to 'tap/task'. Custom names can be provided when a task is
|
93
|
+
# initialized, as can additional and/or overriding configurations.
|
94
|
+
#
|
95
|
+
# # [/path/to/app/config/example.yml]
|
96
|
+
# # one: ONE
|
97
|
+
#
|
98
|
+
# t = ConfiguredTask.new "example", :three => 'three'
|
99
|
+
# t.name # => "example"
|
100
|
+
# t.app[:config] # => "/path/to/app/config"
|
101
|
+
# t.config_file # => "/path/to/app/config/example.yml"
|
102
|
+
# t.config # => {:one => 'ONE', :two => 'two', :three => 'three'}
|
103
|
+
#
|
104
|
+
# Tasks can be assembled into batches that enque and execute
|
105
|
+
# collectively. Batched tasks are automatically generated when a
|
106
|
+
# config_file specifies an array of configurations.
|
85
107
|
#
|
86
108
|
# # [/path/to/app/config/batch.yml]
|
87
109
|
# # - one: ONE
|
88
110
|
# # - one: ANOTHER ONE
|
89
111
|
#
|
90
112
|
# t = ConfiguredTask.new "batch"
|
91
|
-
# t.batch.size
|
113
|
+
# t.batch.size # => 2
|
92
114
|
# t1, t2 = t.batch
|
93
115
|
#
|
94
|
-
# t1.name
|
95
|
-
# t1.
|
96
|
-
# t1.config # => {:one => 'ONE', :two => 'two'}
|
116
|
+
# t1.name # => "batch"
|
117
|
+
# t1.config # => {:one => 'ONE', :two => 'two'}
|
97
118
|
#
|
98
|
-
# t2.name
|
99
|
-
# t2.
|
100
|
-
# t2.config # => {:one => 'ANOTHER ONE', :two => 'two'}
|
119
|
+
# t2.name # => "batch"
|
120
|
+
# t2.config # => {:one => 'ANOTHER ONE', :two => 'two'}
|
101
121
|
#
|
102
|
-
#
|
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:
|
106
|
-
#
|
107
|
-
# # [/path/to/app/config/template.yml]
|
108
|
-
# # one: ONE
|
109
|
-
# # variations!:
|
110
|
-
# # - two: TWO
|
111
|
-
# # - three: THREE
|
122
|
+
# === Batches
|
112
123
|
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
124
|
+
# Tasks facilitate batch processing of inputs using batched tasks. Often
|
125
|
+
# a batch consists of the the same task class instantiated with a variety
|
126
|
+
# of configurations. Once batched, tasks enque together; when any one of
|
127
|
+
# the tasks is enqued, the entire batch is enqued.
|
116
128
|
#
|
117
|
-
#
|
118
|
-
#
|
129
|
+
# runlist = []
|
130
|
+
# t1 = Task.new {|task, input| runlist << input}
|
131
|
+
# t1.batch # => [t1]
|
119
132
|
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
133
|
+
# t2 = t1.initialize_batch_obj
|
134
|
+
# t1.batch # => [t1, t2]
|
135
|
+
# t2.batch # => [t1, t2]
|
136
|
+
#
|
137
|
+
# t1.enq 1
|
138
|
+
# t2.enq 2
|
139
|
+
# t1.app.run
|
123
140
|
#
|
124
|
-
#
|
141
|
+
# runlist # => [1,1,2,2]
|
125
142
|
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
# and Non-Task objects extended to behave like Tasks; ie they can be enqued, run,
|
129
|
-
# and incorporated into workflows.
|
143
|
+
# Here runlist reflects that t1 and t2 were run in succession with the 1
|
144
|
+
# input, and then the 2 input.
|
130
145
|
#
|
131
|
-
#
|
132
|
-
# as well as Tap::Support::Rake, which makes Rake tasks behave like Tap tasks.
|
146
|
+
# === Non-Task Tasks
|
133
147
|
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
# Declares a configuration without any accessors.
|
153
|
-
#
|
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
|
164
|
-
end
|
165
|
-
|
166
|
-
# Creates a configuration writer for the input keys. Works like
|
167
|
-
# attr_writer, except the value is written to config, rather than
|
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.
|
173
|
-
def config_writer(*keys)
|
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)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
# Creates a configuration reader for the input keys. Works like
|
185
|
-
# attr_reader, except the value is read from config, rather than
|
186
|
-
# a local variable.
|
187
|
-
#
|
188
|
-
# With no keys specified, sets config to create a config_reader
|
189
|
-
# for each new configuration.
|
190
|
-
def config_reader(*keys)
|
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)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
# Creates configuration accessors for the input keys. Works like
|
202
|
-
# attr_accessor, except the value is read from and written to config,
|
203
|
-
# rather than a local variable.
|
204
|
-
#
|
205
|
-
# With no keys specified, sets config to create a config_accessor
|
206
|
-
# for each new configuration.
|
207
|
-
def config_accessor(*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
|
148
|
+
# The essential behavior of a Task is expressed in the Tap::Task::Base
|
149
|
+
# module. Using this module, non-task classes can be made to behave like
|
150
|
+
# tasks. An even more fundamental module, Tap::Executable, allows any
|
151
|
+
# method to behave in this manner.
|
152
|
+
#
|
153
|
+
# Configurations are specific to Task but batches are not. Non-task
|
154
|
+
# tasks can be batched. Executable methods cannot be batched.
|
155
|
+
#
|
156
|
+
# See Tap::Task::Base and Tap::Support::Executable for more details as
|
157
|
+
# well as Tap::Support::Rake::Task, which makes Rake[http://rake.rubyforge.org/]
|
158
|
+
# tasks behave like Tap tasks.
|
159
|
+
class Task
|
160
|
+
|
161
|
+
# Defines the essential behavior of a Task. Using this module,
|
162
|
+
# non-task classes can be made to behave like tasks; ie they can
|
163
|
+
# be enqued, batched, and incorporated into workflows.
|
164
|
+
module Base
|
165
|
+
include Support::Executable
|
218
166
|
|
219
|
-
|
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
|
248
|
-
end
|
167
|
+
attr_reader :app
|
249
168
|
|
250
|
-
#
|
251
|
-
#
|
252
|
-
|
253
|
-
|
254
|
-
|
169
|
+
# Initializes obj to behave like a Task. The input method will be
|
170
|
+
# called when obj is run by Tap::App.
|
171
|
+
def self.initialize(obj, method_name, app=App.instance)
|
172
|
+
obj.extend Base
|
173
|
+
obj.extend Support::Batchable
|
174
|
+
obj.instance_variable_set(:@app, app)
|
175
|
+
obj.instance_variable_set(:@batch, [])
|
176
|
+
obj.instance_variable_set(:@multithread, false)
|
177
|
+
obj.instance_variable_set(:@on_complete_block, nil)
|
178
|
+
obj.instance_variable_set(:@_method_name, method_name)
|
179
|
+
obj.initialize_batch_obj
|
180
|
+
obj
|
255
181
|
end
|
256
182
|
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
def
|
261
|
-
|
183
|
+
# Enqueues self and self.batch to app with the inputs.
|
184
|
+
# The number of inputs provided should match the number
|
185
|
+
# of inputs specified by the arity of the _method_name method.
|
186
|
+
def enq(*inputs)
|
187
|
+
batch.each {|t| t.unbatched_enq(*inputs) }
|
188
|
+
self
|
262
189
|
end
|
263
|
-
|
264
|
-
protected
|
265
|
-
|
266
|
-
attr_accessor :config_mode
|
267
190
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
define_method(key) do
|
272
|
-
config[key]
|
273
|
-
end
|
191
|
+
# Like enq, but only enques self and not self.batch.
|
192
|
+
def unbatched_enq(*inputs)
|
193
|
+
app.queue.enq(self, inputs)
|
274
194
|
end
|
275
195
|
|
276
|
-
|
277
|
-
define_method("#{key}=") do |value|
|
278
|
-
config[key] = value
|
279
|
-
validate_config(key, value)
|
280
|
-
value
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
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
|
196
|
+
alias :unbatched_on_complete :on_complete
|
294
197
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
base.instance_variable_set("@iterate", false) if base.iterate.nil?
|
198
|
+
# Sets the on_complete_block for self and self.batch.
|
199
|
+
# Use unbatched_on_complete to set the on_complete_block
|
200
|
+
# for just self.
|
201
|
+
def on_complete(override=false, &block)
|
202
|
+
batch.each {|t| t.unbatched_on_complete(override, &block)}
|
203
|
+
self
|
302
204
|
end
|
303
205
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
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
|
206
|
+
alias :unbatched_multithread= :multithread=
|
207
|
+
|
208
|
+
# Sets the multithread for self and self.batch. Use
|
209
|
+
# unbatched_multithread= to set multithread for just self.
|
210
|
+
def multithread=(value)
|
211
|
+
batch.each {|t| t.unbatched_multithread = value }
|
212
|
+
self
|
390
213
|
end
|
391
214
|
|
392
|
-
# Raises a TerminateError if
|
393
|
-
#
|
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.
|
215
|
+
# Raises a TerminateError if app.state == State::TERMINATE.
|
216
|
+
# check_terminate may be called at any time to provide a
|
217
|
+
# breakpoint in long-running processes.
|
401
218
|
def check_terminate
|
402
|
-
#if multithread? && app.state == App::State::TERMINATE
|
403
219
|
if app.state == App::State::TERMINATE
|
404
220
|
raise App::TerminateError.new
|
405
221
|
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
|
222
|
+
end
|
440
223
|
end
|
441
224
|
|
442
225
|
include Base
|
443
|
-
|
444
|
-
attr_reader :name, :config, :config_template, :task_block
|
445
|
-
|
446
|
-
# Creates a new Task with the specified attributes.
|
447
|
-
def initialize(name=nil, config=nil, app=App.instance, &task_block)
|
448
|
-
super() # required for Monitor
|
449
|
-
|
450
|
-
self.name = name.nil? ? self.class.to_s.underscore : name
|
451
|
-
self.app = app
|
452
|
-
|
453
|
-
self.condition_block = nil
|
454
|
-
self.task_block = task_block
|
455
|
-
self.on_complete_block = nil
|
456
|
-
self.results = []
|
457
|
-
self.multithread = false
|
458
|
-
self.iterate = self.class.iterative
|
459
|
-
self.batch = []
|
460
|
-
|
461
|
-
# collect all configuration templates from the app
|
462
|
-
templates = []
|
463
|
-
app.config_templates(self.config_file).each do |template|
|
464
|
-
templates.concat make_config_templates(template)
|
465
|
-
end
|
466
|
-
|
467
|
-
# be sure there is at least one template and
|
468
|
-
# add a task to batch for every template
|
469
|
-
templates << {} if templates.empty?
|
470
|
-
templates.each do |template|
|
471
|
-
self.create_batch_task(template, config)
|
472
|
-
end
|
473
|
-
end
|
226
|
+
include Support::Configurable
|
474
227
|
|
475
|
-
|
476
|
-
# and adds the task to batch. The batched task will be a duplicate
|
477
|
-
# of the current task but with new configurations.
|
478
|
-
#
|
479
|
-
# This method can be overridden to initialize variables that are
|
480
|
-
# specific to a batch task, for instance variables depending on
|
481
|
-
# batch_index.
|
482
|
-
def create_batch_task(config_template={}, overrides={})
|
483
|
-
task = (self.batch.empty? ? self : self.dup)
|
484
|
-
task.config_template = config_template.symbolize_keys
|
485
|
-
task.batch << task
|
486
|
-
task.config = overrides
|
487
|
-
task
|
488
|
-
end
|
228
|
+
attr_reader :task_block
|
489
229
|
|
490
|
-
#
|
491
|
-
# templates. By default this is the YAML file for the task name relative to the
|
492
|
-
# application config directory:
|
230
|
+
# Creates a new Task with the specified attributes.
|
493
231
|
#
|
494
|
-
#
|
495
|
-
#
|
496
|
-
#
|
232
|
+
# === Subclassing
|
233
|
+
# Batched tasks are generated by duplicating an existing instance, hence
|
234
|
+
# it is a good idea to set shared instance variables BEFORE calling super
|
235
|
+
# in a subclass initialize method. Non-shared instance variables can be
|
236
|
+
# set by overriding the initialize_batch_obj method:
|
497
237
|
#
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
#
|
506
|
-
#
|
507
|
-
#
|
508
|
-
#
|
238
|
+
# class SubclassTask < Tap::Task
|
239
|
+
# attr_accessor :shared_variable, :instance_specific_variable
|
240
|
+
#
|
241
|
+
# def initialize(*args)
|
242
|
+
# @shared_variable = Object.new
|
243
|
+
# super
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# def initialize_batch_obj(*args)
|
247
|
+
# task = super
|
248
|
+
# task.instance_specific_variable = Object.new
|
249
|
+
# task
|
250
|
+
# end
|
251
|
+
# end
|
509
252
|
#
|
510
|
-
#
|
511
|
-
#
|
512
|
-
#
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
253
|
+
# t1 = SubclassTask.new
|
254
|
+
# t2 = t1.initialize_batch_obj
|
255
|
+
# t1.shared_variable == t2.shared_variable # => true
|
256
|
+
# t1.instance_specific_variable == t2.instance_specific_variable # => false
|
257
|
+
#
|
258
|
+
def initialize(name=nil, config={}, app=App.instance, &task_block)
|
259
|
+
@task_block = (task_block == nil ? default_task_block : task_block)
|
260
|
+
@multithread = false
|
261
|
+
@on_complete_block = nil
|
262
|
+
@_method_name = :execute
|
263
|
+
super(name, config, app)
|
518
264
|
end
|
519
265
|
|
520
|
-
#
|
521
|
-
#
|
522
|
-
#
|
523
|
-
#
|
524
|
-
|
525
|
-
|
526
|
-
|
266
|
+
# Executes self with the given inputs. Execute provides hooks for subclasses
|
267
|
+
# to insert standard execution code: before_execute, on_execute_error,
|
268
|
+
# and after_execute. Override any/all of these methods as needed.
|
269
|
+
#
|
270
|
+
# Execute passes the inputs to process and returns the result.
|
271
|
+
def execute(*inputs)
|
272
|
+
before_execute
|
273
|
+
begin
|
274
|
+
result = process(*inputs)
|
275
|
+
rescue
|
276
|
+
on_execute_error($!)
|
277
|
+
end
|
278
|
+
after_execute
|
279
|
+
|
280
|
+
result
|
527
281
|
end
|
528
282
|
|
529
|
-
#
|
530
|
-
|
531
|
-
|
283
|
+
# The method for processing inputs into outputs. Override this method in
|
284
|
+
# subclasses to provide class-specific process logic. The number of
|
285
|
+
# arguments specified by process corresponds to the number of arguments
|
286
|
+
# the task should have when enqued.
|
287
|
+
#
|
288
|
+
# class TaskWithTwoInputs < Tap::Task
|
289
|
+
# def process(a, b)
|
290
|
+
# [b,a]
|
291
|
+
# end
|
292
|
+
# end
|
293
|
+
#
|
294
|
+
# t = TaskWithTwoInputs.new
|
295
|
+
# t.enq(1,2).enq(3,4)
|
296
|
+
# t.app.run
|
297
|
+
# t.app.results(t) # => [[2,1], [4,3]]
|
298
|
+
#
|
299
|
+
# By default process passes self and the input(s) to the task_block
|
300
|
+
# provided during initialization. In this case the task block dictates
|
301
|
+
# the number of arguments enq should receive. Simply returns the inputs
|
302
|
+
# if no task_block is set.
|
303
|
+
#
|
304
|
+
# # two arguments in addition to task are specified
|
305
|
+
# # so this Task must be enqued with two inputs...
|
306
|
+
# t = Task.new {|task, a, b| [b,a] }
|
307
|
+
# t.enq(1,2).enq(3,4)
|
308
|
+
# t.app.run
|
309
|
+
# t.app.results(t) # => [[2,1], [4,3]]
|
310
|
+
#
|
311
|
+
def process(*inputs)
|
312
|
+
return inputs if task_block == nil
|
313
|
+
inputs.unshift(self)
|
314
|
+
|
315
|
+
arity = task_block.arity
|
316
|
+
n = inputs.length
|
317
|
+
unless n == arity || (arity < 0 && (-1-n) <= arity)
|
318
|
+
raise ArgumentError.new("wrong number of arguments (#{n} for #{arity})")
|
319
|
+
end
|
320
|
+
|
321
|
+
task_block.call(*inputs)
|
532
322
|
end
|
533
323
|
|
534
324
|
# Logs the inputs to the application logger (via app.log)
|
@@ -537,24 +327,28 @@ module Tap
|
|
537
327
|
app.log(action, msg, level)
|
538
328
|
end
|
539
329
|
|
330
|
+
# Returns self.name
|
331
|
+
def to_s
|
332
|
+
name
|
333
|
+
end
|
334
|
+
|
540
335
|
protected
|
541
|
-
|
542
|
-
attr_writer :name, :config_template, :task_block
|
543
336
|
|
544
|
-
# Hook to
|
545
|
-
def
|
337
|
+
# Hook to set a default task block. By default, nil.
|
338
|
+
def default_task_block
|
339
|
+
nil
|
546
340
|
end
|
547
341
|
|
548
|
-
# Hook to
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
# this would be a security hole... can't do it.
|
554
|
-
# if you can set :data then you could read
|
555
|
-
# any accessible file.
|
556
|
-
#{ |filename| app.read(:data, filename) }
|
557
|
-
end
|
342
|
+
# Hook to execute code before inputs are processed.
|
343
|
+
def before_execute() end
|
344
|
+
|
345
|
+
# Hook to execute code after inputs are processed.
|
346
|
+
def after_execute() end
|
558
347
|
|
348
|
+
# Hook to handle unhandled errors from processing inputs on a task level.
|
349
|
+
# By default on_execute_error simply re-raises the unhandled error.
|
350
|
+
def on_execute_error(err)
|
351
|
+
raise err
|
352
|
+
end
|
559
353
|
end
|
560
354
|
end
|