tap 0.7.9
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +21 -0
- data/README +71 -0
- data/Rakefile +117 -0
- data/bin/tap +63 -0
- data/lib/tap.rb +15 -0
- data/lib/tap/app.rb +739 -0
- data/lib/tap/file_task.rb +354 -0
- data/lib/tap/generator.rb +29 -0
- data/lib/tap/generator/generators/config/USAGE +0 -0
- data/lib/tap/generator/generators/config/config_generator.rb +23 -0
- data/lib/tap/generator/generators/config/templates/config.erb +2 -0
- data/lib/tap/generator/generators/file_task/USAGE +0 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +21 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +27 -0
- data/lib/tap/generator/generators/file_task/templates/test.erb +12 -0
- data/lib/tap/generator/generators/root/USAGE +0 -0
- data/lib/tap/generator/generators/root/root_generator.rb +36 -0
- data/lib/tap/generator/generators/root/templates/Rakefile +48 -0
- data/lib/tap/generator/generators/root/templates/app.yml +19 -0
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +4 -0
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +26 -0
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +57 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +108 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +40 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +21 -0
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +60 -0
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +5 -0
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +53 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +4 -0
- data/lib/tap/generator/generators/task/USAGE +0 -0
- data/lib/tap/generator/generators/task/task_generator.rb +21 -0
- data/lib/tap/generator/generators/task/templates/task.erb +21 -0
- data/lib/tap/generator/generators/task/templates/test.erb +29 -0
- data/lib/tap/generator/generators/workflow/USAGE +0 -0
- data/lib/tap/generator/generators/workflow/templates/task.erb +16 -0
- data/lib/tap/generator/generators/workflow/templates/test.erb +7 -0
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +21 -0
- data/lib/tap/generator/options.rb +26 -0
- data/lib/tap/generator/usage.rb +26 -0
- data/lib/tap/root.rb +275 -0
- data/lib/tap/script/console.rb +7 -0
- data/lib/tap/script/destroy.rb +8 -0
- data/lib/tap/script/generate.rb +8 -0
- data/lib/tap/script/run.rb +111 -0
- data/lib/tap/script/server.rb +12 -0
- data/lib/tap/support/audit.rb +415 -0
- data/lib/tap/support/batch_queue.rb +165 -0
- data/lib/tap/support/combinator.rb +114 -0
- data/lib/tap/support/logger.rb +91 -0
- data/lib/tap/support/rap.rb +38 -0
- data/lib/tap/support/run_error.rb +20 -0
- data/lib/tap/support/template.rb +81 -0
- data/lib/tap/support/templater.rb +155 -0
- data/lib/tap/support/versions.rb +63 -0
- data/lib/tap/task.rb +448 -0
- data/lib/tap/test.rb +320 -0
- data/lib/tap/test/env_vars.rb +16 -0
- data/lib/tap/test/inference_methods.rb +298 -0
- data/lib/tap/test/subset_methods.rb +260 -0
- data/lib/tap/version.rb +3 -0
- data/lib/tap/workflow.rb +73 -0
- data/test/app/config/addition_template.yml +6 -0
- data/test/app/config/batch.yml +2 -0
- data/test/app/config/empty.yml +0 -0
- data/test/app/config/erb.yml +1 -0
- data/test/app/config/template.yml +6 -0
- data/test/app/config/version-0.1.yml +1 -0
- data/test/app/config/version.yml +1 -0
- data/test/app/lib/app_test_task.rb +2 -0
- data/test/app_class_test.rb +33 -0
- data/test/app_test.rb +1372 -0
- data/test/file_task/config/batch.yml +2 -0
- data/test/file_task/config/configured.yml +1 -0
- data/test/file_task/old_file_one.txt +0 -0
- data/test/file_task/old_file_two.txt +0 -0
- data/test/file_task_test.rb +1041 -0
- data/test/root/alt_lib/alt_module.rb +4 -0
- data/test/root/lib/absolute_alt_filepath.rb +2 -0
- data/test/root/lib/alternative_filepath.rb +2 -0
- data/test/root/lib/another_module.rb +2 -0
- data/test/root/lib/nested/some_module.rb +4 -0
- data/test/root/lib/no_module_included.rb +0 -0
- data/test/root/lib/some/module.rb +4 -0
- data/test/root/lib/some_class.rb +2 -0
- data/test/root/lib/some_module.rb +3 -0
- data/test/root/load_path/load_path_module.rb +2 -0
- data/test/root/load_path/skip_module.rb +2 -0
- data/test/root/mtime/older.txt +0 -0
- data/test/root/unload/full_path.rb +2 -0
- data/test/root/unload/loaded_by_nested.rb +2 -0
- data/test/root/unload/nested/nested_load.rb +6 -0
- data/test/root/unload/nested/nested_with_ext.rb +4 -0
- data/test/root/unload/nested/relative_path.rb +4 -0
- data/test/root/unload/older.rb +2 -0
- data/test/root/unload/unload_base.rb +9 -0
- data/test/root/versions/another.yml +0 -0
- data/test/root/versions/file-0.1.2.yml +0 -0
- data/test/root/versions/file-0.1.yml +0 -0
- data/test/root/versions/file.yml +0 -0
- data/test/root_test.rb +483 -0
- data/test/support/audit_test.rb +449 -0
- data/test/support/batch_queue_test.rb +320 -0
- data/test/support/combinator_test.rb +249 -0
- data/test/support/logger_test.rb +31 -0
- data/test/support/template_test.rb +122 -0
- data/test/support/templater/erb.txt +2 -0
- data/test/support/templater/erb.yml +2 -0
- data/test/support/templater/somefile.txt +2 -0
- data/test/support/templater_test.rb +192 -0
- data/test/support/versions_test.rb +71 -0
- data/test/tap_test_helper.rb +4 -0
- data/test/tap_test_suite.rb +4 -0
- data/test/task/config/batch.yml +2 -0
- data/test/task/config/batched.yml +2 -0
- data/test/task/config/configured.yml +1 -0
- data/test/task/config/example.yml +1 -0
- data/test/task/config/overriding.yml +2 -0
- data/test/task/config/task_with_config.yml +1 -0
- data/test/task/config/template.yml +4 -0
- data/test/task_class_test.rb +118 -0
- data/test/task_execute_test.rb +233 -0
- data/test/task_test.rb +424 -0
- data/test/test/inference_methods/test_assert_expected/expected/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/expected/folder/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/input/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/input/folder/file.txt +1 -0
- data/test/test/inference_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/inference_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/inference_methods/test_file_compare/expected/output_1.txt +3 -0
- data/test/test/inference_methods/test_file_compare/expected/output_2.txt +1 -0
- data/test/test/inference_methods/test_file_compare/input/input_1.txt +3 -0
- data/test/test/inference_methods/test_file_compare/input/input_2.txt +3 -0
- data/test/test/inference_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/inference_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/inference_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/inference_methods/test_yml_compare/expected/output_1.yml +6 -0
- data/test/test/inference_methods/test_yml_compare/expected/output_2.yml +6 -0
- data/test/test/inference_methods/test_yml_compare/input/input_1.yml +4 -0
- data/test/test/inference_methods/test_yml_compare/input/input_2.yml +4 -0
- data/test/test/inference_methods_test.rb +311 -0
- data/test/test/subset_methods_test.rb +115 -0
- data/test/test_test.rb +233 -0
- data/test/workflow_test.rb +108 -0
- metadata +274 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'tap/support/audit'
|
2
|
+
require 'monitor'
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
module Support
|
6
|
+
|
7
|
+
# BatchQueue allows thread-safe enqueing and dequeing of tasks and inputs for
|
8
|
+
# execution. Until the task is dequeued, additional enqueuements will aggregate
|
9
|
+
# the inputs into a batch. The queue order is determined solely by the task;
|
10
|
+
# all accumulated inputs are dequeued at once.
|
11
|
+
#
|
12
|
+
# # given tasks t1 and t2:
|
13
|
+
# q = BatchQueue.new
|
14
|
+
# q.enq t1, 1
|
15
|
+
# q.enq t2, :a,:b,:c
|
16
|
+
# q.enq t1, 2,3
|
17
|
+
#
|
18
|
+
# q.deq # => [t1, [1,2,3]]
|
19
|
+
# q.deq # => [t2, [:a,:b,:c]]
|
20
|
+
# q.deq # => nil
|
21
|
+
#
|
22
|
+
# All BatchQueue methods are monitored such that they may only be accessed
|
23
|
+
# by one thread at a time, in order to ensure that transactions are atomic.
|
24
|
+
class BatchQueue
|
25
|
+
include MonitorMixin
|
26
|
+
|
27
|
+
# creates a new BatchQueue
|
28
|
+
def initialize
|
29
|
+
super
|
30
|
+
self.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
# Clears the BatchQueue of all tasks and inputs.
|
34
|
+
def clear
|
35
|
+
synchronize do
|
36
|
+
self.inputs = {}
|
37
|
+
self.tasks = []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the number of enqueued tasks (same as length)
|
42
|
+
def size
|
43
|
+
tasks.length
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the number of enqueued tasks (same as size)
|
47
|
+
def length
|
48
|
+
size
|
49
|
+
end
|
50
|
+
|
51
|
+
# True if no tasks are enqueued
|
52
|
+
def empty?
|
53
|
+
tasks.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Enqueues the inputs to the task. If the task has not already been added to the
|
57
|
+
# queue, then the task will be pushed onto the queue and the inputs registered.
|
58
|
+
# Otherwise, the inputs will be added to the registered inputs; the task will
|
59
|
+
# not be added to the queue twice. If the task is batched, then each task in the
|
60
|
+
# batch will be enqueued in order, with the same inputs (Audit inputs will be forked
|
61
|
+
# for each task).
|
62
|
+
def enq(task, *inputs)
|
63
|
+
synchronize do
|
64
|
+
batch = task.batch
|
65
|
+
batch.each do |t|
|
66
|
+
tasks << t unless tasks.include?(t)
|
67
|
+
|
68
|
+
inputs = inputs.collect do |result|
|
69
|
+
result.kind_of?(Support::Audit) ? result._fork : result
|
70
|
+
end if batch.length > 1
|
71
|
+
|
72
|
+
self.inputs[t] = [] unless self.inputs[t]
|
73
|
+
self.inputs[t].concat(inputs)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Enqueues the task and input, then prioritizes the task to the front of the queue.
|
79
|
+
def priority_enq(task, *inputs)
|
80
|
+
synchronize do
|
81
|
+
enq(task, *inputs)
|
82
|
+
prioritize(task)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Moves the task to the front of the queue. Does not queue a task if the task is not
|
87
|
+
# already in the queue. If the task is batched, then each task in the batch will be
|
88
|
+
# prioritized in order.
|
89
|
+
def prioritize(task)
|
90
|
+
synchronize do
|
91
|
+
task.batch.reverse_each do |t|
|
92
|
+
tasks.unshift(t) if tasks.delete(t)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Moves the task to the back of the queue. Does not queue a task if the task is not
|
98
|
+
# already in the queue. If the task is batched, then each task in the batch will be
|
99
|
+
# marginalized in order.
|
100
|
+
def marginalize(task)
|
101
|
+
synchronize do
|
102
|
+
task.batch.each do |t|
|
103
|
+
tasks.push(t) if tasks.delete(t)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns true if the task is executable with the currently registered inputs
|
109
|
+
# (as determined by task.executable?).
|
110
|
+
def executable?(task)
|
111
|
+
synchronize do
|
112
|
+
task.executable?(inputs[task])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Dequeues the next executable task, or the specified task (even if it is not
|
117
|
+
# executable). The task and the current inputs for the task are returned in
|
118
|
+
# an array like: [task, inputs]. Returns nil if the task is not in the queue.
|
119
|
+
def deq(task=nil)
|
120
|
+
synchronize do
|
121
|
+
task = peek if task.nil?
|
122
|
+
|
123
|
+
if tasks.include?(task)
|
124
|
+
[tasks.delete(task), inputs.delete(task)]
|
125
|
+
else
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns the next executable task, or nil if no queued tasks are executable.
|
132
|
+
# The task is not dequeued.
|
133
|
+
def peek
|
134
|
+
synchronize do
|
135
|
+
tasks.each do |task|
|
136
|
+
next unless executable?(task)
|
137
|
+
return task
|
138
|
+
end
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the number of executable tasks in the queue.
|
144
|
+
def num_executable
|
145
|
+
synchronize do
|
146
|
+
tasks.inject(0) do |count, task|
|
147
|
+
executable?(task) ? count + 1 : count
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the number of non-executable tasks in the queue.
|
153
|
+
def num_not_executable
|
154
|
+
synchronize do
|
155
|
+
size - num_executable
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
attr_accessor :inputs, :tasks
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
|
6
|
+
# Combinator provides a method for iterating over all possible combinations
|
7
|
+
# of items in the input sets.
|
8
|
+
#
|
9
|
+
# c = Combinator.new [1,2], [3,4]
|
10
|
+
# c.collect # => [[1,3], [1,4], [2,3], [2,4]]
|
11
|
+
#
|
12
|
+
# Combinator works by iteratively combining each element from the first set (a)
|
13
|
+
# with each element from the second set (b). When more than two sets are given,
|
14
|
+
# a Combinator will bundle all but the first set into a new Combinator which
|
15
|
+
# then acts as the second set.
|
16
|
+
#
|
17
|
+
# # Note: each element in a set is arrayified
|
18
|
+
# # to simplify creation of the combination.
|
19
|
+
#
|
20
|
+
# c = Combinator.new [1,2], [3,4], [5,6]
|
21
|
+
# c.a # => [[1],[2]]
|
22
|
+
# c.b.class # => Combinator
|
23
|
+
# c.b.a # => [[3],[4]]
|
24
|
+
# c.b.b # => [[5],[6]]
|
25
|
+
#
|
26
|
+
# Thus when iterating, each Combinator makes it's pairwise combinations and
|
27
|
+
# works outwards to the final multi-set combinations.
|
28
|
+
class Combinator
|
29
|
+
include Enumerable
|
30
|
+
|
31
|
+
attr_reader :a, :b
|
32
|
+
|
33
|
+
# Creates a new Combinator. Input sets must be an Array, or nil.
|
34
|
+
# Within the Array any objects are allowed for combination.
|
35
|
+
def initialize(*sets)
|
36
|
+
@a = make_set(sets.shift)
|
37
|
+
@b = make_set(*sets)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the sets used to initialize the Combinator, minus any
|
41
|
+
# nil or empty sets.
|
42
|
+
def sets
|
43
|
+
sets_in(a) + sets_in(b)
|
44
|
+
end
|
45
|
+
|
46
|
+
# True if the Combinator has no combinations.
|
47
|
+
def empty?
|
48
|
+
a.empty? && b.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the number of combinations in the Combinator.
|
52
|
+
def length
|
53
|
+
if a.empty? || b.empty?
|
54
|
+
if !a.empty?
|
55
|
+
a.length
|
56
|
+
elsif !b.empty?
|
57
|
+
b.length
|
58
|
+
else
|
59
|
+
0
|
60
|
+
end
|
61
|
+
else
|
62
|
+
a.length * b.length
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Passes each combination as an array to the input block.
|
67
|
+
def each(&block)
|
68
|
+
# if either set is empty, iterate only the non-empty set
|
69
|
+
# note, the two-step check on each ensures that if both
|
70
|
+
# sets are empty, then neither is iterated.
|
71
|
+
if a.empty? || b.empty?
|
72
|
+
if !a.empty?
|
73
|
+
a.each {|aa| yield aa }
|
74
|
+
elsif !b.empty?
|
75
|
+
b.each {|bb| yield bb }
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# otherwise, iterate all combinations of both
|
79
|
+
a.each do |aa|
|
80
|
+
b.each do |bb|
|
81
|
+
yield aa + bb
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def sets_in(set)
|
90
|
+
# recursively de-arrayifies each element in the array
|
91
|
+
case set
|
92
|
+
when Combinator then set.sets
|
93
|
+
when Array then set.empty? ? [] : [set.collect {|s| s.first}]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def make_set(*sets)
|
98
|
+
# recieves an array of arrays or combinators
|
99
|
+
return Combinator.new(*sets) if sets.length > 1
|
100
|
+
return [] if sets.empty?
|
101
|
+
|
102
|
+
# recursively arrayifies each element in an array
|
103
|
+
set = sets.first
|
104
|
+
case set
|
105
|
+
when Array then set.collect {|s| [s]}
|
106
|
+
when nil then []
|
107
|
+
else
|
108
|
+
raise ArgumentError.new("sets must be arrays or nil")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
# Logger provides methods to extend Logger objects.
|
6
|
+
#
|
7
|
+
# The output format is designed to look nice in a command prompt, but provide useful information
|
8
|
+
# if directed at a log file. The Logger output format is like:
|
9
|
+
#
|
10
|
+
# I[datetime] type message
|
11
|
+
#
|
12
|
+
# The first letter ('I' - INFO) indicates the severity of the message, next comes the datetime of the
|
13
|
+
# log, the type of log message, then the message itself. Specify the log type and message like:
|
14
|
+
#
|
15
|
+
# logger.type "message"
|
16
|
+
#
|
17
|
+
# Typed logging occurs through +method_missing+. Any type is possible so long as the logger doesn't
|
18
|
+
# already have a method of the input type.
|
19
|
+
module Logger
|
20
|
+
Format = " %s[%s] %18s %s\n"
|
21
|
+
|
22
|
+
def self.extended(base)
|
23
|
+
# On OS X (and maybe other systems), $stdout is not sync-ed.
|
24
|
+
# Set sync to true so that writes are immediately flushed
|
25
|
+
# to the console.
|
26
|
+
base.logdev.dev.sync = true if base.logdev.dev == $stdout
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_writer :format
|
30
|
+
def format
|
31
|
+
(@format ||= nil) || Format
|
32
|
+
end
|
33
|
+
|
34
|
+
# Provides direct access to the log device
|
35
|
+
def logdev
|
36
|
+
@logdev
|
37
|
+
end
|
38
|
+
|
39
|
+
def tick(mark=".")
|
40
|
+
@logdev.write mark
|
41
|
+
end
|
42
|
+
|
43
|
+
def monitor(action="beginning", msg="", &block)
|
44
|
+
begin_time = Time.now
|
45
|
+
format_add(INFO, msg, action) {|format| format.chomp("\n")}
|
46
|
+
|
47
|
+
result = yield
|
48
|
+
|
49
|
+
format_add(INFO, "#{Time.now-begin_time}s", "completed") {|format| "\n" + format}
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
def format_add(level, msg, action=nil, &block)
|
54
|
+
current_format = self.format
|
55
|
+
self.format = yield(current_format)
|
56
|
+
add(level, msg, action.to_s)
|
57
|
+
self.format = current_format
|
58
|
+
end
|
59
|
+
|
60
|
+
# Overrides the default format message to produce output like:
|
61
|
+
#
|
62
|
+
# I[H:M:S] type message
|
63
|
+
#
|
64
|
+
# If no progname is specified, '--' is used.
|
65
|
+
def format_message(severity, timestamp, progname, msg)
|
66
|
+
if timestamp.respond_to?(:strftime) && self.datetime_format
|
67
|
+
timestamp = timestamp.strftime(self.datetime_format)
|
68
|
+
end
|
69
|
+
|
70
|
+
format % [severity[0..0], timestamp, progname || '--' , msg]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convenience method for creating section breaks in the output, formatted like:
|
74
|
+
#
|
75
|
+
# ----- message -----
|
76
|
+
def section_break(message)
|
77
|
+
@logdev.write(" ----- #{message} -----\n")
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
INFO = Object::Logger::INFO
|
83
|
+
|
84
|
+
# Specifies that any unknown method calls will be treated as logging calls where the method
|
85
|
+
# is the log type, and the first argument is the message. Types are underscored before logging.
|
86
|
+
def method_missing(method, *args, &block)
|
87
|
+
add(INFO, args.first, method.to_s.underscore, &block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# Defines the minimum methods to run (ie to queue and to execute)
|
5
|
+
# an object from Tap::App, aside from execute which must be
|
6
|
+
# provided by the object itself. Does not permit use in workflow
|
7
|
+
# methods (requires results, condition, and on_complete)
|
8
|
+
#
|
9
|
+
# == Advice
|
10
|
+
#
|
11
|
+
# If you ever want to multithread the object, execute should be
|
12
|
+
# limited to one thread at a time, for example by wrapping it in
|
13
|
+
# synchronize from the MonitorMixin. You will probably get away
|
14
|
+
# without doing so unless you're adding the object to the queue
|
15
|
+
# multiple times in succession -- but be on the lookout for
|
16
|
+
# unexpected results due to unsynchronized execution.
|
17
|
+
module Rap
|
18
|
+
attr_accessor :multithread
|
19
|
+
|
20
|
+
def multithread?
|
21
|
+
multithread
|
22
|
+
end
|
23
|
+
|
24
|
+
def executable?(inputs)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def batch
|
29
|
+
@batch ||= [self]
|
30
|
+
end
|
31
|
+
|
32
|
+
def batched?
|
33
|
+
batch.length > 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
class RunError < RuntimeError
|
4
|
+
attr_reader :original_error, :terminate_errors
|
5
|
+
|
6
|
+
def initialize(original_error, terminate_errors)
|
7
|
+
@original_error = original_error
|
8
|
+
@terminate_errors = terminate_errors
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{original_error.class} #{original_error.message}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def backtrace
|
16
|
+
original_error.backtrace
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'tap/support/combinator'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
|
6
|
+
# Template provides the default methods for making hash templates using Templater.
|
7
|
+
# Methods are provided to make variations of the template, as well as to pattern
|
8
|
+
# templates based on combinations of possible values.
|
9
|
+
#
|
10
|
+
module Template
|
11
|
+
attr_accessor :templater
|
12
|
+
|
13
|
+
# Provides a standard way of creating a variations of a template and takes an
|
14
|
+
# array of hashes with the specified variations. The template is treated as the
|
15
|
+
# default and variations are pushed (<<) to the templater. The template is
|
16
|
+
# cleared upon complete, so that it will be removed if used in the context of
|
17
|
+
# a Templater.
|
18
|
+
#
|
19
|
+
# t = {"default" => "default value"}.extend Template
|
20
|
+
# t.templater = []
|
21
|
+
# t.variations([
|
22
|
+
# {"default" => "non-default value"},
|
23
|
+
# {"another" => "value"}])
|
24
|
+
#
|
25
|
+
# t.templater
|
26
|
+
# # => [{"default" => "non-default value"},
|
27
|
+
# {"default" => "default value", "another" => "value"}]
|
28
|
+
#
|
29
|
+
def variations(array)
|
30
|
+
variations!(array)
|
31
|
+
self.clear
|
32
|
+
end
|
33
|
+
|
34
|
+
# Same as variations but does NOT clear the template (ie the template will
|
35
|
+
# be kept as specified, effectively acting as another variant). Note that
|
36
|
+
# variations! is called by the 'variations!!' key.
|
37
|
+
def variations!(array)
|
38
|
+
array.each { |var| templater << self.merge(var) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Provides a standard way of creating variations of a template based on all
|
42
|
+
# combinations of the keys in the input hash. The template itself is treated
|
43
|
+
# as the default and variations are pushed (<<) to the templater. The template
|
44
|
+
# is cleared upon complete, so that it will be removed if used in the context of
|
45
|
+
# a Templater.
|
46
|
+
#
|
47
|
+
# t = {"default" => "default value"}.extend Template
|
48
|
+
# t.templater = []
|
49
|
+
# t.combinations(
|
50
|
+
# "letter" => ["a", "b"],
|
51
|
+
# "number" => [1, 2])
|
52
|
+
#
|
53
|
+
# t.templater
|
54
|
+
# # => [{"default" => "default value", "letter" => "a", "number" => 1},
|
55
|
+
# {"default" => "default value", "letter" => "a", "number" => 2},
|
56
|
+
# {"default" => "default value", "letter" => "b", "number" => 1},
|
57
|
+
# {"default" => "default value", "letter" => "b", "number" => 2}]
|
58
|
+
def combinations(hash)
|
59
|
+
combinations!(hash)
|
60
|
+
self.clear
|
61
|
+
end
|
62
|
+
|
63
|
+
# Same as combinations but does NOT clear the template (ie the template will
|
64
|
+
# be kept as specified, effectively acting as another variant). Note that
|
65
|
+
# combinations! is called by the 'combinations!!' key.
|
66
|
+
def combinations!(hash)
|
67
|
+
keys, values = hash.to_a.transpose
|
68
|
+
values.collect! do |value|
|
69
|
+
value.kind_of?(Array) ? value : [value]
|
70
|
+
end
|
71
|
+
|
72
|
+
Combinator.new(*values).each do |c|
|
73
|
+
template = {}
|
74
|
+
keys.each_with_index {|key,i| template[key] = c[i] }
|
75
|
+
|
76
|
+
templater << self.merge(template)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|