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