tap 0.12.4 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History +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
|