tap 0.12.4 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +34 -0
- data/README +62 -41
- data/bin/tap +36 -40
- data/cmd/console.rb +14 -6
- data/cmd/manifest.rb +62 -58
- data/cmd/run.rb +49 -31
- data/doc/API +84 -0
- data/doc/Class Reference +83 -115
- data/doc/Examples/Command Line +36 -0
- data/doc/Examples/Workflow +40 -0
- data/lib/tap/app.rb +293 -214
- data/lib/tap/app/node.rb +43 -0
- data/lib/tap/app/queue.rb +77 -0
- data/lib/tap/app/stack.rb +16 -0
- data/lib/tap/app/state.rb +22 -0
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/env.rb +400 -314
- data/lib/tap/env/constant.rb +227 -0
- data/lib/tap/env/gems.rb +63 -0
- data/lib/tap/env/manifest.rb +89 -0
- data/lib/tap/env/minimap.rb +292 -0
- data/lib/tap/{support → env}/string_ext.rb +2 -2
- data/lib/tap/exe.rb +113 -125
- data/lib/tap/join.rb +175 -0
- data/lib/tap/joins.rb +9 -0
- data/lib/tap/joins/switch.rb +44 -0
- data/lib/tap/joins/sync.rb +99 -0
- data/lib/tap/root.rb +100 -491
- data/lib/tap/root/utils.rb +220 -0
- data/lib/tap/{support → root}/versions.rb +31 -29
- data/lib/tap/schema.rb +248 -0
- data/lib/tap/schema/parser.rb +413 -0
- data/lib/tap/schema/utils.rb +82 -0
- data/lib/tap/support/intern.rb +19 -6
- data/lib/tap/support/templater.rb +8 -3
- data/lib/tap/task.rb +175 -171
- data/lib/tap/tasks/dump.rb +58 -0
- data/lib/tap/tasks/load.rb +62 -0
- metadata +30 -73
- data/cmd/destroy.rb +0 -27
- data/cmd/generate.rb +0 -27
- data/doc/Command Reference +0 -105
- data/doc/Syntax Reference +0 -234
- data/doc/Tutorial +0 -348
- data/lib/tap/dump.rb +0 -142
- data/lib/tap/file_task.rb +0 -384
- data/lib/tap/generator/arguments.rb +0 -13
- data/lib/tap/generator/base.rb +0 -176
- data/lib/tap/generator/destroy.rb +0 -60
- data/lib/tap/generator/generate.rb +0 -93
- data/lib/tap/generator/generators/command/command_generator.rb +0 -21
- data/lib/tap/generator/generators/command/templates/command.erb +0 -32
- data/lib/tap/generator/generators/config/config_generator.rb +0 -98
- data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
- data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
- data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
- data/lib/tap/generator/generators/root/root_generator.rb +0 -84
- data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
- data/lib/tap/generator/generators/root/templates/README +0 -14
- data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
- data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
- data/lib/tap/generator/generators/root/templates/gemspec +0 -27
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
- data/lib/tap/generator/generators/task/task_generator.rb +0 -25
- data/lib/tap/generator/generators/task/templates/task.erb +0 -14
- data/lib/tap/generator/generators/task/templates/test.erb +0 -19
- data/lib/tap/generator/manifest.rb +0 -20
- data/lib/tap/generator/preview.rb +0 -69
- data/lib/tap/load.rb +0 -64
- data/lib/tap/spec.rb +0 -41
- data/lib/tap/support/aggregator.rb +0 -65
- data/lib/tap/support/audit.rb +0 -333
- data/lib/tap/support/constant.rb +0 -143
- data/lib/tap/support/constant_manifest.rb +0 -126
- data/lib/tap/support/dependencies.rb +0 -54
- data/lib/tap/support/dependency.rb +0 -44
- data/lib/tap/support/executable.rb +0 -198
- data/lib/tap/support/executable_queue.rb +0 -125
- data/lib/tap/support/gems.rb +0 -43
- data/lib/tap/support/join.rb +0 -144
- data/lib/tap/support/joins.rb +0 -12
- data/lib/tap/support/joins/switch.rb +0 -27
- data/lib/tap/support/joins/sync_merge.rb +0 -38
- data/lib/tap/support/manifest.rb +0 -171
- data/lib/tap/support/minimap.rb +0 -90
- data/lib/tap/support/node.rb +0 -176
- data/lib/tap/support/parser.rb +0 -450
- data/lib/tap/support/schema.rb +0 -385
- data/lib/tap/support/shell_utils.rb +0 -67
- data/lib/tap/test.rb +0 -77
- data/lib/tap/test/assertions.rb +0 -38
- data/lib/tap/test/env_vars.rb +0 -29
- data/lib/tap/test/extensions.rb +0 -73
- data/lib/tap/test/file_test.rb +0 -362
- data/lib/tap/test/file_test_class.rb +0 -15
- data/lib/tap/test/regexp_escape.rb +0 -87
- data/lib/tap/test/script_test.rb +0 -46
- data/lib/tap/test/script_tester.rb +0 -115
- data/lib/tap/test/subset_test.rb +0 -260
- data/lib/tap/test/subset_test_class.rb +0 -99
- data/lib/tap/test/tap_test.rb +0 -109
- data/lib/tap/test/utils.rb +0 -231
@@ -0,0 +1,36 @@
|
|
1
|
+
= Command Line Examples
|
2
|
+
|
3
|
+
== Basic Input/Output
|
4
|
+
|
5
|
+
=== Read data from $stdin
|
6
|
+
# [goodnight.txt]
|
7
|
+
# goodnight moon
|
8
|
+
|
9
|
+
% tap run -- load --: dump < goodnight.txt
|
10
|
+
goodnight moon
|
11
|
+
|
12
|
+
=== Pipe data from $stdin
|
13
|
+
% echo goodnight moon | tap run -- load --: dump
|
14
|
+
goodnight moon
|
15
|
+
|
16
|
+
=== Load data from argument
|
17
|
+
% tap run -- load 'goodnight moon' --: dump
|
18
|
+
goodnight moon
|
19
|
+
|
20
|
+
=== Dump data to $stdout
|
21
|
+
% tap run -- load 'goodnight moon' --: dump > goodnight.txt
|
22
|
+
% more goodnight.txt
|
23
|
+
goodnight moon
|
24
|
+
|
25
|
+
=== Pipe data via $stdout
|
26
|
+
% tap run -- load 'goodnight moon' --: dump | more
|
27
|
+
goodnight moon
|
28
|
+
|
29
|
+
== Setup
|
30
|
+
|
31
|
+
=== Use an ENV variable to setup the Tap::Env
|
32
|
+
|
33
|
+
% TAP_GEMS= tap run -T
|
34
|
+
% TAP_GEMS=:all tap run -T
|
35
|
+
% TAP_GEMS=:latest tap run -T
|
36
|
+
% TAP_GEMS="[rap, tap-tasks]" tap run -T
|
@@ -0,0 +1,40 @@
|
|
1
|
+
= Workflow Examples
|
2
|
+
|
3
|
+
=== Sequence
|
4
|
+
|
5
|
+
A simple sequence using the --: syntax that joins the previous to next task.
|
6
|
+
|
7
|
+
% tap run -- load 'goodnight moon' --: dump
|
8
|
+
goodnight moon
|
9
|
+
|
10
|
+
=== Sequence (canonical)
|
11
|
+
|
12
|
+
The more canonical way of specifying a sequence.
|
13
|
+
|
14
|
+
% tap run -- load 'goodnight moon' -- dump --[0][1]
|
15
|
+
goodnight moon
|
16
|
+
|
17
|
+
=== Fork
|
18
|
+
|
19
|
+
Multiple outputs for a single input.
|
20
|
+
|
21
|
+
% tap run -- load 'goodnight moon' -- dump -- dump --[0][1,2]
|
22
|
+
goodnight moon
|
23
|
+
goodnight moon
|
24
|
+
|
25
|
+
=== Merge
|
26
|
+
|
27
|
+
Multiple inputs for a single output; note that as seen in the output, dump
|
28
|
+
receives the inputs in serial, whenever they happen to be ready.
|
29
|
+
|
30
|
+
% tap run -- load goodnight -- load moon -- dump --[0,1][2]
|
31
|
+
goodnight
|
32
|
+
moon
|
33
|
+
|
34
|
+
=== Synchronized Merge
|
35
|
+
|
36
|
+
Similar to a merge, but the results of each input are collected into an array
|
37
|
+
before being passed to dump. The printout is: ['goodnight', 'moon'].to_s
|
38
|
+
|
39
|
+
% tap run -- load goodnight -- load moon -- dump --[0,1][2].sync
|
40
|
+
goodnightmoon
|
data/lib/tap/app.rb
CHANGED
@@ -1,207 +1,181 @@
|
|
1
1
|
require 'logger'
|
2
|
-
|
3
|
-
require 'tap/
|
4
|
-
require 'tap/
|
5
|
-
require 'tap/
|
6
|
-
require 'tap/
|
2
|
+
require 'configurable'
|
3
|
+
require 'tap/app/node'
|
4
|
+
require 'tap/app/state'
|
5
|
+
require 'tap/app/stack'
|
6
|
+
require 'tap/app/queue'
|
7
7
|
|
8
8
|
module Tap
|
9
9
|
|
10
|
-
# App coordinates the setup and
|
11
|
-
# to the application directory structure. All tasks have an app (by default
|
12
|
-
# App.instance) through which they access application-wide resources like
|
13
|
-
# the logger, executable queue, and dependencies.
|
14
|
-
#
|
15
|
-
# === Running Tasks
|
10
|
+
# App coordinates the setup and execution of workflows.
|
16
11
|
#
|
17
|
-
#
|
12
|
+
# === Workflows
|
18
13
|
#
|
19
|
-
#
|
14
|
+
# Workflows are composed of nodes and joins such as instances of Tap::Task
|
15
|
+
# and Tap::Join. The actual workflow exists between nodes; each node can
|
16
|
+
# specify a join to receive it's output and enque or execute other nodes.
|
17
|
+
# When a node does not have a join, apps allow the specification of a
|
18
|
+
# default join to, for instance, aggregate results.
|
20
19
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# t.enq(1)
|
24
|
-
# t.enq(2)
|
25
|
-
# t.enq(3)
|
26
|
-
#
|
27
|
-
# app.run
|
28
|
-
# app.results(t) # => [['a', 'b', 'c'], [1], [2], [3]]
|
20
|
+
# Any object satisfying the correct API[link:files/doc/API.html] can be used
|
21
|
+
# as a node or join. Apps have helpers to make nodes out of blocks.
|
29
22
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
23
|
+
# app = Tap::App.new
|
24
|
+
# n = app.node {|*inputs| inputs }
|
25
|
+
# app.enq(n, 'a', 'b', 'c')
|
26
|
+
# app.enq(n, 1)
|
27
|
+
# app.enq(n, 2)
|
28
|
+
# app.enq(n, 3)
|
33
29
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# t2 = Task.intern {|task, input| "#{input}:2"}
|
30
|
+
# results = []
|
31
|
+
# app.on_complete {|result| results << result }
|
37
32
|
#
|
38
|
-
# t0.on_complete {|_result| t1.enq(_result) }
|
39
|
-
# t1.on_complete {|_result| t2.enq(_result) }
|
40
|
-
#
|
41
|
-
# t0.enq
|
42
33
|
# app.run
|
43
|
-
#
|
44
|
-
# app.results(t2) # => ["0:1:2"]
|
34
|
+
# results # => [['a', 'b', 'c'], [1], [2], [3]]
|
45
35
|
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
# task nor the app has an on_complete block, the app stores the audit in
|
49
|
-
# app.aggregator and makes it available through app.results. Note how after
|
50
|
-
# the sequence, the t0 and t1 results are not in the app (they were handled
|
51
|
-
# by the on_complete block).
|
36
|
+
# To construct a workflow, set joins for individual nodes. Here is a simple
|
37
|
+
# sequence:
|
52
38
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# the actual result of a task. Aggregated audits are available through
|
57
|
-
# app._results.
|
39
|
+
# n0 = app.node { "a" }
|
40
|
+
# n1 = app.node {|input| "#{input}.b" }
|
41
|
+
# n2 = app.node {|input| "#{input}.c"}
|
58
42
|
#
|
59
|
-
#
|
60
|
-
#
|
43
|
+
# n0.on_complete {|result| app.execute(n1, result) }
|
44
|
+
# n1.on_complete {|result| app.execute(n2, result) }
|
45
|
+
# app.enq(n0)
|
46
|
+
#
|
47
|
+
# results.clear
|
61
48
|
# app.run
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# t0.name = "zero"
|
65
|
-
# t1.name = "one"
|
66
|
-
# t2.name = "two"
|
49
|
+
# results # => ["a.b.c"]
|
67
50
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
51
|
+
# Tasks have helpers to simplify the manual constructon of workflows, but
|
52
|
+
# even with these methods large workflows are cumbersome to build. More
|
53
|
+
# typically, a Tap::Schema is used in such cases.
|
71
54
|
#
|
72
|
-
#
|
73
|
-
# # => %q{
|
74
|
-
# # o-[zero] "0"
|
75
|
-
# # o-[one] "0:1"
|
76
|
-
# # o-[two] "0:1:2"
|
77
|
-
# #
|
78
|
-
# # o-[] "a"
|
79
|
-
# # o-[two] "a:2"
|
80
|
-
# #
|
81
|
-
# # o-[] "b"
|
82
|
-
# # o-[one] "b:1"
|
83
|
-
# # o-[two] "b:1:2"
|
84
|
-
# # }
|
55
|
+
# === Middleware
|
85
56
|
#
|
86
|
-
#
|
57
|
+
# Apps allow middleware to wrap the execution of each node. This can be
|
58
|
+
# particularly useful to track the progress of a workflow. Middleware is
|
59
|
+
# initialized with the application stack and uses the call method to
|
60
|
+
# wrap the execution of the stack.
|
87
61
|
#
|
88
|
-
#
|
62
|
+
# Using middleware, an auditor looks like this:
|
89
63
|
#
|
90
|
-
#
|
91
|
-
#
|
64
|
+
# class AuditMiddleware
|
65
|
+
# attr_reader :stack, :audit
|
92
66
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
67
|
+
# def initialize(stack)
|
68
|
+
# @stack = stack
|
69
|
+
# @audit = []
|
70
|
+
# end
|
96
71
|
#
|
97
|
-
#
|
98
|
-
#
|
72
|
+
# def call(node, inputs=[])
|
73
|
+
# audit << node
|
74
|
+
# stack.call(node, inputs)
|
75
|
+
# end
|
76
|
+
# end
|
99
77
|
#
|
100
|
-
# app.
|
101
|
-
# runlist # => [t1, t0]
|
78
|
+
# auditor = app.use AuditMiddleware
|
102
79
|
#
|
103
|
-
#
|
80
|
+
# app.enq(n0)
|
81
|
+
# app.enq(n2, "x")
|
82
|
+
# app.enq(n1, "y")
|
104
83
|
#
|
105
|
-
#
|
84
|
+
# results.clear
|
106
85
|
# app.run
|
107
|
-
#
|
86
|
+
# results # => ["a.b.c", "x.c", "y.b.c"]
|
87
|
+
# auditor.audit
|
88
|
+
# # => [
|
89
|
+
# # n0, n1, n2,
|
90
|
+
# # n2,
|
91
|
+
# # n1, n2
|
92
|
+
# # ]
|
93
|
+
#
|
94
|
+
# Middleware can be nested with multiple calls to use.
|
108
95
|
#
|
109
|
-
# ===
|
96
|
+
# === Dependencies
|
110
97
|
#
|
111
|
-
#
|
112
|
-
#
|
98
|
+
# Nodes allow the construction of dependency-based workflows. A node only
|
99
|
+
# executes after its dependencies have been resolved (ie executed).
|
113
100
|
#
|
114
|
-
#
|
101
|
+
# runlist = []
|
102
|
+
# n0 = app.node { runlist << 0 }
|
103
|
+
# n1 = app.node { runlist << 1 }
|
115
104
|
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
# m.enq(2)
|
119
|
-
# m.enq(3)
|
105
|
+
# n0.depends_on(n1)
|
106
|
+
# app.enq(n0)
|
120
107
|
#
|
121
|
-
# array.empty? # => true
|
122
108
|
# app.run
|
123
|
-
#
|
109
|
+
# runlist # => [1, 0]
|
124
110
|
#
|
125
|
-
|
111
|
+
# Dependencies are resolved <em>every time</em> a node executes; individual
|
112
|
+
# dependencies can implement single-execution if desired. Dependencies are
|
113
|
+
# not resolved with arguments, ie dependency nodes must be able to execute
|
114
|
+
# without inputs.
|
115
|
+
#
|
116
|
+
class App
|
126
117
|
class << self
|
127
118
|
# Sets the current app instance
|
128
119
|
attr_writer :instance
|
129
120
|
|
130
121
|
# Returns the current instance of App. If no instance has been set,
|
131
|
-
# then instance initializes a new App with the default configuration.
|
132
|
-
|
133
|
-
|
122
|
+
# then instance initializes a new App with the default configuration.
|
123
|
+
#
|
124
|
+
# Instance is used to initialize tasks when no app is specified. Aside
|
125
|
+
# from that, there is nothing magical about instance.
|
126
|
+
def instance(auto_initialize=true)
|
127
|
+
@instance ||= (auto_initialize ? new : nil)
|
134
128
|
end
|
135
129
|
end
|
136
130
|
|
137
131
|
include Configurable
|
132
|
+
include MonitorMixin
|
138
133
|
|
139
|
-
# The
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
134
|
+
# The default App logger writes to $stderr at level INFO.
|
135
|
+
DEFAULT_LOGGER = Logger.new($stderr)
|
136
|
+
DEFAULT_LOGGER.level = Logger::INFO
|
137
|
+
DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
|
138
|
+
" %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
|
139
|
+
end
|
144
140
|
|
145
141
|
# The state of the application (see App::State)
|
146
142
|
attr_reader :state
|
147
143
|
|
148
|
-
#
|
149
|
-
|
150
|
-
attr_reader :aggregator
|
144
|
+
# The application call stack for executing nodes
|
145
|
+
attr_reader :stack
|
151
146
|
|
152
|
-
#
|
153
|
-
attr_reader :
|
147
|
+
# The application queue
|
148
|
+
attr_reader :queue
|
154
149
|
|
155
|
-
#
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
150
|
+
# A cache of application-specific data. Internally used to store class
|
151
|
+
# instances of tasks. Not recommended for casual use.
|
152
|
+
attr_reader :cache
|
153
|
+
|
154
|
+
# The default joins for nodes that have no joins set
|
155
|
+
attr_accessor :default_joins
|
156
|
+
|
157
|
+
# The application logger
|
158
|
+
attr_reader :logger
|
160
159
|
|
161
|
-
config :audit, true, &c.switch # Signal auditing
|
162
160
|
config :debug, false, &c.flag # Flag debugging
|
163
161
|
config :force, false, &c.flag # Force execution at checkpoints
|
164
162
|
config :quiet, false, &c.flag # Suppress logging
|
165
163
|
config :verbose, false, &c.flag # Enables extra logging (overrides quiet)
|
166
164
|
|
167
|
-
# The constants defining the possible App states.
|
168
|
-
module State
|
169
|
-
READY = 0
|
170
|
-
RUN = 1
|
171
|
-
STOP = 2
|
172
|
-
TERMINATE = 3
|
173
|
-
|
174
|
-
module_function
|
175
|
-
|
176
|
-
# Returns a string corresponding to the input state value.
|
177
|
-
# Returns nil for unknown states.
|
178
|
-
#
|
179
|
-
# State.state_str(0) # => 'READY'
|
180
|
-
# State.state_str(12) # => nil
|
181
|
-
def state_str(state)
|
182
|
-
constants.inject(nil) {|str, s| const_get(s) == state ? s.to_s : str}
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
165
|
# Creates a new App with the given configuration.
|
187
|
-
def initialize(config={},
|
166
|
+
def initialize(config={}, options={}, &block)
|
188
167
|
super() # monitor
|
189
168
|
|
190
169
|
@state = State::READY
|
191
|
-
@
|
192
|
-
@
|
193
|
-
@
|
194
|
-
@
|
170
|
+
@stack = options[:stack] || Stack.new
|
171
|
+
@queue = options[:queue] || Queue.new
|
172
|
+
@cache = options[:cache] || {}
|
173
|
+
@trace = []
|
174
|
+
@default_joins = []
|
175
|
+
on_complete(&block)
|
195
176
|
|
196
177
|
initialize_config(config)
|
197
|
-
self.logger = logger
|
198
|
-
end
|
199
|
-
|
200
|
-
# The default App logger writes to $stderr at level INFO.
|
201
|
-
DEFAULT_LOGGER = Logger.new($stderr)
|
202
|
-
DEFAULT_LOGGER.level = Logger::INFO
|
203
|
-
DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
|
204
|
-
" %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
|
178
|
+
self.logger = options[:logger] || DEFAULT_LOGGER
|
205
179
|
end
|
206
180
|
|
207
181
|
# True if debug or the global variable $DEBUG is true.
|
@@ -225,19 +199,111 @@ module Tap
|
|
225
199
|
logger.add(level, msg, action.to_s) if !quiet || verbose
|
226
200
|
end
|
227
201
|
|
228
|
-
#
|
229
|
-
|
202
|
+
# Returns a new node that executes block on call.
|
203
|
+
def node(&block) # :yields: *inputs
|
204
|
+
Node.intern(&block)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Enques the node with the inputs. Returns the node.
|
208
|
+
def enq(node, *inputs)
|
209
|
+
queue.enq(node, inputs)
|
210
|
+
node
|
211
|
+
end
|
212
|
+
|
213
|
+
# Generates a node from the block and enques. Returns the new node.
|
214
|
+
def bq(*inputs, &block) # :yields: *inputs
|
215
|
+
node = self.node(&block)
|
216
|
+
queue.enq(node, inputs)
|
217
|
+
node
|
218
|
+
end
|
219
|
+
|
220
|
+
# Adds the specified middleware to the stack.
|
221
|
+
def use(middleware)
|
222
|
+
@stack = middleware.new(@stack)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Clears the cache, the queue, and resets the stack so that no middleware
|
226
|
+
# is used. Reset raises an error unless state == State::READY.
|
227
|
+
def reset
|
228
|
+
synchronize do
|
229
|
+
unless state == State::READY
|
230
|
+
raise "cannot reset unless READY"
|
231
|
+
end
|
232
|
+
|
233
|
+
@stack = Stack.new
|
234
|
+
cache.clear
|
235
|
+
queue.clear
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Dispatches each dependency of node. A block can be given to do something
|
240
|
+
# else with the nodes (ex: reset single-execution dependencies). Resolve
|
241
|
+
# will recursively yield dependencies if specified.
|
242
|
+
#
|
243
|
+
# Resolve raises an error for circular dependencies.
|
244
|
+
def resolve(node, recursive=false, &block)
|
245
|
+
node.dependencies.each do |dependency|
|
246
|
+
if @trace.include?(dependency)
|
247
|
+
@trace.push dependency
|
248
|
+
raise DependencyError.new(@trace)
|
249
|
+
end
|
250
|
+
|
251
|
+
# mark the results at the index to prevent
|
252
|
+
# infinite loops with circular dependencies
|
253
|
+
@trace.push dependency
|
254
|
+
|
255
|
+
if recursive
|
256
|
+
resolve(dependency, recursive, &block)
|
257
|
+
end
|
258
|
+
|
259
|
+
if block_given?
|
260
|
+
yield(dependency)
|
261
|
+
else
|
262
|
+
dispatch(dependency)
|
263
|
+
end
|
264
|
+
|
265
|
+
@trace.pop
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Dispatches node to the application stack with the inputs.
|
270
|
+
def execute(node, *inputs)
|
271
|
+
dispatch(node, inputs)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Dispatch sends the node into the application stack with the inputs.
|
275
|
+
# Dispatch does the following in order:
|
276
|
+
#
|
277
|
+
# - resolve node dependencies using resolve_dependencies
|
278
|
+
# - call stack with the node and inputs
|
279
|
+
# - call the node joins, if set, or the default_joins with the results
|
280
|
+
#
|
281
|
+
# Dispatch returns the node result.
|
282
|
+
def dispatch(node, inputs=[])
|
283
|
+
resolve(node)
|
284
|
+
result = stack.call(node, inputs)
|
285
|
+
|
286
|
+
joins = node.joins.empty? ? default_joins : node.joins
|
287
|
+
joins.each do |join|
|
288
|
+
join.call(result)
|
289
|
+
end
|
290
|
+
result
|
291
|
+
end
|
292
|
+
|
293
|
+
# Sequentially dispatches each enqued (node, inputs) pair to the
|
294
|
+
# application stack. A run continues until the queue is empty. Returns
|
295
|
+
# self.
|
230
296
|
#
|
231
297
|
# ==== Run State
|
232
298
|
#
|
233
|
-
# Run checks the state of self before
|
299
|
+
# Run checks the state of self before dispatching a node. If the state
|
234
300
|
# changes from State::RUN, the following behaviors result:
|
235
301
|
#
|
236
|
-
# State::STOP:: No more
|
237
|
-
#
|
238
|
-
# State::TERMINATE:: No more
|
239
|
-
#
|
240
|
-
#
|
302
|
+
# State::STOP:: No more nodes will be dispatched; the current node will
|
303
|
+
# continute to completion.
|
304
|
+
# State::TERMINATE:: No more nodes will be dispatched and the currently
|
305
|
+
# running node will be discontinued as described in
|
306
|
+
# terminate.
|
241
307
|
#
|
242
308
|
# Calls to run when the state is not State::READY do nothing and
|
243
309
|
# return immediately.
|
@@ -250,8 +316,7 @@ module Tap
|
|
250
316
|
# TODO: log starting run
|
251
317
|
begin
|
252
318
|
until queue.empty? || state != State::RUN
|
253
|
-
|
254
|
-
executable._execute(*inputs)
|
319
|
+
dispatch(*queue.deq)
|
255
320
|
end
|
256
321
|
rescue(TerminateError)
|
257
322
|
# gracefully fail for termination errors
|
@@ -267,9 +332,9 @@ module Tap
|
|
267
332
|
self
|
268
333
|
end
|
269
334
|
|
270
|
-
# Signals a running
|
271
|
-
#
|
272
|
-
#
|
335
|
+
# Signals a running app to stop dispatching nodes to the application stack
|
336
|
+
# by setting state = State::STOP. The node currently in the stack will
|
337
|
+
# will continue to completion.
|
273
338
|
#
|
274
339
|
# Does nothing unless state is State::RUN.
|
275
340
|
def stop
|
@@ -278,10 +343,14 @@ module Tap
|
|
278
343
|
end
|
279
344
|
|
280
345
|
# Signals a running application to terminate execution by setting
|
281
|
-
# state = State::TERMINATE. In this state,
|
282
|
-
# will
|
283
|
-
#
|
284
|
-
#
|
346
|
+
# state = State::TERMINATE. In this state, calls to check_terminate
|
347
|
+
# will raise a TerminateError. Run considers TerminateErrors a normal
|
348
|
+
# exit and rescues them quietly.
|
349
|
+
#
|
350
|
+
# Nodes can set breakpoints that call check_terminate to invoke
|
351
|
+
# node-specific termination. If a node never calls check_terminate, then
|
352
|
+
# it will continue to completion and terminate is functionally the same
|
353
|
+
# as stop.
|
285
354
|
#
|
286
355
|
# Does nothing if state == State::READY.
|
287
356
|
def terminate
|
@@ -289,76 +358,86 @@ module Tap
|
|
289
358
|
self
|
290
359
|
end
|
291
360
|
|
361
|
+
# Raises a TerminateError if state == State::TERMINATE. Nodes should call
|
362
|
+
# check_terminate to provide breakpoints in long-running processes.
|
363
|
+
def check_terminate
|
364
|
+
if state == App::State::TERMINATE
|
365
|
+
raise App::TerminateError.new
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
292
369
|
# Returns an information string for the App.
|
293
370
|
#
|
294
|
-
# App.instance.info # => 'state: 0 (READY) queue: 0
|
371
|
+
# App.instance.info # => 'state: 0 (READY) queue: 0'
|
295
372
|
#
|
296
373
|
def info
|
297
|
-
"state: #{state} (#{State.state_str(state)}) queue: #{queue.size}
|
374
|
+
"state: #{state} (#{State.state_str(state)}) queue: #{queue.size}"
|
298
375
|
end
|
299
376
|
|
300
|
-
#
|
301
|
-
# input is not an Executable, or is not assigned to self. Returns task.
|
302
|
-
def enq(task, *inputs)
|
303
|
-
unless task.kind_of?(Support::Executable)
|
304
|
-
raise ArgumentError, "not an Executable: #{task.inspect}"
|
305
|
-
end
|
306
|
-
|
307
|
-
unless task.app == self
|
308
|
-
raise ArgumentError, "not assigned to enqueing app: #{task.inspect}"
|
309
|
-
end
|
310
|
-
|
311
|
-
queue.enq(task, inputs)
|
312
|
-
task
|
313
|
-
end
|
314
|
-
|
315
|
-
# Method enque. Enques the specified method from object with the inputs.
|
316
|
-
# Returns the enqued method.
|
317
|
-
def mq(object, method_name, *inputs)
|
318
|
-
m = object._method(method_name)
|
319
|
-
enq(m, *inputs)
|
320
|
-
end
|
321
|
-
|
322
|
-
# Returns all aggregated, audited results for the specified tasks.
|
323
|
-
# Results are joined into a single array. Arrays of tasks are
|
324
|
-
# allowed as inputs. See results.
|
325
|
-
def _results(*tasks)
|
326
|
-
aggregator.retrieve_all(*tasks.flatten)
|
327
|
-
end
|
328
|
-
|
329
|
-
# Returns all aggregated results for the specified tasks. Results are
|
330
|
-
# joined into a single array. Arrays of tasks are allowed as inputs.
|
377
|
+
# Dumps self to the target as YAML.
|
331
378
|
#
|
332
|
-
#
|
333
|
-
# t1 = Task.intern {|task, input| "#{input}.1" }
|
379
|
+
# ==== Notes
|
334
380
|
#
|
335
|
-
#
|
336
|
-
#
|
381
|
+
# Platforms that use {Syck}[http://whytheluckystiff.net/syck/] (ex MRI)
|
382
|
+
# require a fix because Syck misformats certain dumps, such that they
|
383
|
+
# cannot be reloaded (even by Syck). Specifically:
|
337
384
|
#
|
338
|
-
#
|
339
|
-
# app.results(t1, t0) # => ["1.1", "0.0"]
|
385
|
+
# &id001 !ruby/object:Tap::Task ?
|
340
386
|
#
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
# Sets a block to receive the results tasks with no on_complete_block
|
346
|
-
# set. Raises an error if an on_complete_block is already set.
|
347
|
-
# Override the existing on_complete_block by specifying override = true.
|
387
|
+
# should be:
|
388
|
+
#
|
389
|
+
# ? &id001 !ruby/object:Tap::Task
|
348
390
|
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
def
|
352
|
-
|
353
|
-
|
391
|
+
# Dump fixes this error and, in addition, removes Thread and Proc dumps
|
392
|
+
# because they can't be allocated on load.
|
393
|
+
def dump(target=$stdout, options={})
|
394
|
+
synchronize do
|
395
|
+
options = {
|
396
|
+
:date_format => '%Y-%m-%d %H:%M:%S',
|
397
|
+
:date => true,
|
398
|
+
:info => true
|
399
|
+
}.merge(options)
|
400
|
+
|
401
|
+
# print basic headers
|
402
|
+
target.puts "# date: #{Time.now.strftime(options[:date_format])}" if options[:date]
|
403
|
+
target.puts "# info: #{info}" if options[:info]
|
404
|
+
|
405
|
+
# # print load paths and requires
|
406
|
+
# target.puts "# load paths"
|
407
|
+
# target.puts $:.to_yaml
|
408
|
+
#
|
409
|
+
# target.puts "# requires"
|
410
|
+
# target.puts $".to_yaml
|
411
|
+
|
412
|
+
# dump yaml, fixing as necessary
|
413
|
+
yaml = YAML.dump(self)
|
414
|
+
yaml.gsub!(/\&(.*!ruby\/object:.*?)\s*\?/) {"? &#{$1} " } if YAML.const_defined?(:Syck)
|
415
|
+
yaml.gsub!(/!ruby\/object:(Thread|Proc) \{\}/, '')
|
416
|
+
target << yaml
|
354
417
|
end
|
355
|
-
|
418
|
+
|
419
|
+
target
|
420
|
+
end
|
421
|
+
|
422
|
+
# Sets the block to receive the audited result of nodes with no join
|
423
|
+
# (ie the block is set as default_join).
|
424
|
+
def on_complete(&block) # :yields: _result
|
425
|
+
self.default_joins << block if block
|
356
426
|
self
|
357
427
|
end
|
358
428
|
|
359
|
-
|
429
|
+
protected
|
430
|
+
|
431
|
+
# TerminateErrors are raised to kill executing nodes when terminate is
|
360
432
|
# called on an running App. They are handled by the run rescue code.
|
361
|
-
class TerminateError < RuntimeError
|
433
|
+
class TerminateError < RuntimeError
|
434
|
+
end
|
435
|
+
|
436
|
+
# Raised when Tap::App#resolve detects a circular dependency.
|
437
|
+
class DependencyError < StandardError
|
438
|
+
def initialize(trace)
|
439
|
+
super "circular dependency: [#{trace.join(', ')}]"
|
440
|
+
end
|
362
441
|
end
|
363
442
|
end
|
364
443
|
end
|