tap 0.19.0 → 1.3.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 +100 -45
- data/MIT-LICENSE +1 -1
- data/README +95 -51
- data/bin/tap +11 -57
- data/bin/tapexe +84 -0
- data/doc/API +91 -139
- data/doc/Configuration +93 -0
- data/doc/Examples/Command Line +10 -42
- data/doc/Examples/Tapfile +124 -0
- data/doc/Ruby to Ruby +87 -0
- data/doc/Workflow Syntax +185 -0
- data/lib/tap.rb +74 -5
- data/lib/tap/app.rb +217 -310
- data/lib/tap/app/api.rb +44 -23
- data/lib/tap/app/queue.rb +11 -12
- data/lib/tap/app/stack.rb +4 -4
- data/lib/tap/declarations.rb +200 -0
- data/lib/tap/declarations/context.rb +31 -0
- data/lib/tap/declarations/description.rb +33 -0
- data/lib/tap/env.rb +133 -779
- data/lib/tap/env/cache.rb +87 -0
- data/lib/tap/env/constant.rb +94 -39
- data/lib/tap/env/path.rb +71 -0
- data/lib/tap/join.rb +42 -78
- data/lib/tap/joins/gate.rb +85 -0
- data/lib/tap/joins/switch.rb +4 -2
- data/lib/tap/joins/sync.rb +3 -3
- data/lib/tap/middleware.rb +5 -5
- data/lib/tap/middlewares/debugger.rb +18 -58
- data/lib/tap/parser.rb +115 -183
- data/lib/tap/root.rb +162 -239
- data/lib/tap/signal.rb +72 -0
- data/lib/tap/signals.rb +20 -2
- data/lib/tap/signals/class_methods.rb +38 -43
- data/lib/tap/signals/configure.rb +19 -0
- data/lib/tap/signals/help.rb +5 -7
- data/lib/tap/signals/load.rb +49 -0
- data/lib/tap/signals/module_methods.rb +1 -0
- data/lib/tap/task.rb +46 -275
- data/lib/tap/tasks/dump.rb +21 -16
- data/lib/tap/tasks/list.rb +184 -0
- data/lib/tap/tasks/load.rb +4 -4
- data/lib/tap/tasks/prompt.rb +128 -0
- data/lib/tap/tasks/signal.rb +42 -0
- data/lib/tap/tasks/singleton.rb +35 -0
- data/lib/tap/tasks/stream.rb +64 -0
- data/lib/tap/utils.rb +83 -0
- data/lib/tap/version.rb +2 -2
- data/lib/tap/workflow.rb +124 -0
- data/tap.yml +0 -0
- metadata +59 -24
- data/cmd/console.rb +0 -43
- data/cmd/manifest.rb +0 -118
- data/cmd/run.rb +0 -145
- data/doc/Examples/Workflow +0 -40
- data/lib/tap/app/node.rb +0 -29
- data/lib/tap/env/context.rb +0 -61
- data/lib/tap/env/gems.rb +0 -63
- data/lib/tap/env/manifest.rb +0 -179
- data/lib/tap/env/minimap.rb +0 -308
- data/lib/tap/intern.rb +0 -50
- data/lib/tap/joins.rb +0 -9
- data/lib/tap/prompt.rb +0 -36
- data/lib/tap/root/utils.rb +0 -220
- data/lib/tap/root/versions.rb +0 -138
- data/lib/tap/signals/signal.rb +0 -68
data/lib/tap.rb
CHANGED
@@ -1,6 +1,75 @@
|
|
1
|
-
|
2
|
-
$:.unshift(lib) unless $:.include?(lib)
|
3
|
-
|
1
|
+
require 'tap/declarations'
|
4
2
|
require 'tap/version'
|
5
|
-
|
6
|
-
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def options
|
8
|
+
options = {
|
9
|
+
:tapfile => ENV['TAPFILE'],
|
10
|
+
:gems => ENV['TAP_GEMS'],
|
11
|
+
:path => ENV['TAP_PATH'],
|
12
|
+
:tapenv => ENV['TAPENV'],
|
13
|
+
:taprc => ENV['TAPRC'],
|
14
|
+
:tap_cache => ENV['TAP_CACHE'],
|
15
|
+
:debug => ENV['TAP_DEBUG']
|
16
|
+
}
|
17
|
+
options
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup(options=self.options)
|
21
|
+
env = Env.new
|
22
|
+
app = App.new({}, :env => env)
|
23
|
+
app.set('app', app)
|
24
|
+
app.set('env', env)
|
25
|
+
App.current = app
|
26
|
+
|
27
|
+
def options.process(key, default=nil)
|
28
|
+
value = self[key] || default
|
29
|
+
if self[:debug] == 'true'
|
30
|
+
$stderr.puts(App::LOG_FORMAT % [' ', nil, key, value])
|
31
|
+
end
|
32
|
+
value && block_given? ? yield(value) : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
if options[:debug] == 'true'
|
36
|
+
options.process(:ruby, "#{RbConfig::CONFIG['RUBY_INSTALL_NAME']}-#{RUBY_VERSION} (#{RUBY_RELEASE_DATE})")
|
37
|
+
options.process(:tap, VERSION)
|
38
|
+
app.debug = true
|
39
|
+
app.verbose = true
|
40
|
+
app.logger.level = Logger::DEBUG
|
41
|
+
end
|
42
|
+
|
43
|
+
options.process(:gems) do |gems|
|
44
|
+
cache_dir = options[:tap_cache]
|
45
|
+
|
46
|
+
if cache_dir.to_s.strip.empty?
|
47
|
+
require 'tmpdir'
|
48
|
+
cache_dir = Dir.tmpdir
|
49
|
+
end
|
50
|
+
|
51
|
+
env.signal(:load).call Env::Cache.new(cache_dir, options[:debug]).select(gems)
|
52
|
+
end
|
53
|
+
|
54
|
+
options.process(:path) do |path|
|
55
|
+
Env::Path.split(path).each {|dir| env.auto(:dir => dir) }
|
56
|
+
end
|
57
|
+
|
58
|
+
options.process(:tapenv) do |tapenv_path|
|
59
|
+
env.signal(:load).call Env::Path.split(tapenv_path)
|
60
|
+
end
|
61
|
+
|
62
|
+
options.process(:taprc) do |taprc_path|
|
63
|
+
app.signal(:load).call Env::Path.split(taprc_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
options.process(:tapfile) do |tapfile_path|
|
67
|
+
Env::Path.split(tapfile_path).each do |tapfile|
|
68
|
+
next unless File.file?(tapfile)
|
69
|
+
Declarations::Context.new(app, File.basename(tapfile)).instance_eval(File.read(tapfile), tapfile, 1)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
app
|
74
|
+
end
|
75
|
+
end
|
data/lib/tap/app.rb
CHANGED
@@ -1,46 +1,49 @@
|
|
1
1
|
require 'logger'
|
2
|
-
require 'tap/
|
3
|
-
require 'tap/app/node'
|
2
|
+
require 'tap/env'
|
4
3
|
require 'tap/app/state'
|
5
4
|
require 'tap/app/stack'
|
6
5
|
require 'tap/app/queue'
|
7
|
-
|
8
|
-
require 'tap/parser'
|
6
|
+
autoload(:YAML, 'yaml')
|
9
7
|
|
10
8
|
module Tap
|
11
|
-
|
12
|
-
# :startdoc::app
|
13
|
-
#
|
14
9
|
# App coordinates the setup and execution of workflows.
|
15
10
|
class App
|
16
11
|
class << self
|
17
|
-
|
18
|
-
|
12
|
+
def set_context(context={})
|
13
|
+
current = Thread.current[CONTEXT]
|
14
|
+
Thread.current[CONTEXT] = context
|
15
|
+
current
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_context(context)
|
19
|
+
begin
|
20
|
+
current = set_context(context)
|
21
|
+
yield
|
22
|
+
ensure
|
23
|
+
set_context(current)
|
24
|
+
end
|
25
|
+
end
|
19
26
|
|
20
|
-
|
21
|
-
|
22
|
-
# Instance is used to initialize tasks when no app is specified and
|
23
|
-
# exists for convenience only.
|
24
|
-
def instance(auto_initialize=true)
|
25
|
-
@instance ||= (auto_initialize ? new : nil)
|
27
|
+
def context
|
28
|
+
Thread.current[CONTEXT] ||= {}
|
26
29
|
end
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
# by the tap executable.
|
31
|
-
def setup(dir=Dir.pwd)
|
32
|
-
env = Env.setup(dir)
|
33
|
-
@instance = new(:env => env)
|
31
|
+
def current=(app)
|
32
|
+
context[CURRENT] = app
|
34
33
|
end
|
35
34
|
|
36
|
-
def
|
37
|
-
|
35
|
+
def current
|
36
|
+
context[CURRENT] ||= new
|
37
|
+
end
|
38
|
+
|
39
|
+
def build(spec={}, app=current)
|
40
|
+
config = spec['config'] || {}
|
38
41
|
signals = spec['signals'] || []
|
39
42
|
|
40
43
|
if spec['self']
|
41
44
|
app.reconfigure(config)
|
42
45
|
else
|
43
|
-
app = new(config)
|
46
|
+
app = new(config, :env => app.env)
|
44
47
|
end
|
45
48
|
|
46
49
|
signals.each do |args|
|
@@ -55,7 +58,12 @@ module Tap
|
|
55
58
|
include Configurable
|
56
59
|
include MonitorMixin
|
57
60
|
include Signals
|
58
|
-
|
61
|
+
|
62
|
+
# A variable to store the application context in Thread.current
|
63
|
+
CONTEXT = 'tap.context'
|
64
|
+
|
65
|
+
# A variable to store an instance in the application context.
|
66
|
+
CURRENT = 'tap.current'
|
59
67
|
|
60
68
|
# The reserved call keys
|
61
69
|
CALL_KEYS = %w{obj sig args}
|
@@ -66,17 +74,30 @@ module Tap
|
|
66
74
|
# Reserved call and init keys as a single array
|
67
75
|
RESERVED_KEYS = CALL_KEYS + INIT_KEYS
|
68
76
|
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
# Splits a signal into an object string and a signal string. If OBJECT
|
78
|
+
# doesn't match, then the string can be considered a signal, and the
|
79
|
+
# object is nil. After a match:
|
80
|
+
#
|
81
|
+
# $1:: The object string
|
82
|
+
# (ex: 'obj/sig' => 'obj')
|
83
|
+
# $2:: The signal string
|
84
|
+
# (ex: 'obj/sig' => 'sig')
|
85
|
+
#
|
86
|
+
OBJECT = /\A(.*)\/(.*)\z/
|
87
|
+
|
88
|
+
# The default log format
|
89
|
+
LOG_FORMAT = "%s %8s %10s %s\n"
|
90
|
+
|
91
|
+
# The default logger formatter -- uses LOG_FORMAT
|
92
|
+
LOG_FORMATTER = lambda do |severity, time, head, tail|
|
93
|
+
code = (severity == 'INFO' ? ' ' : severity[0,1])
|
94
|
+
LOG_FORMAT % [code, time.strftime('%H:%M:%S'), head, tail]
|
74
95
|
end
|
75
96
|
|
76
97
|
# The state of the application (see App::State)
|
77
98
|
attr_reader :state
|
78
99
|
|
79
|
-
# The application call stack for executing
|
100
|
+
# The application call stack for executing tasks
|
80
101
|
attr_reader :stack
|
81
102
|
|
82
103
|
# The application queue
|
@@ -88,60 +109,96 @@ module Tap
|
|
88
109
|
# The application logger
|
89
110
|
attr_accessor :logger
|
90
111
|
|
91
|
-
|
92
|
-
|
93
|
-
config :quiet, false, :short => :q, &c.flag # Suppress logging
|
94
|
-
config :verbose, false, :short => :v, &c.flag # Enables extra logging (overrides quiet)
|
95
|
-
config :auto_enque, true, &c.switch # Auto-enque parsed args
|
96
|
-
config :bang, true, &c.switch # Use parse! when possible
|
112
|
+
# The application environment
|
113
|
+
attr_accessor :env
|
97
114
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
115
|
+
config :debug, false, :short => :d, &c.flag # Flag debugging
|
116
|
+
config :force, false, :short => :f, &c.flag # Force execution at checkpoints
|
117
|
+
config :quiet, false, :short => :q, &c.flag # Suppress logging
|
118
|
+
config :verbose, false, :short => :v, &c.flag # Enables extra logging (overrides quiet)
|
119
|
+
|
120
|
+
define_signal :exe do |input| # executes an object
|
121
|
+
spec = convert_to_hash(input, ['var'], 'input')
|
122
|
+
obj.exe obj.obj(spec['var']), spec['input']
|
123
|
+
end
|
124
|
+
|
125
|
+
define_signal :enq do |input| # enques an object
|
126
|
+
spec = convert_to_hash(input, ['var'], 'input')
|
127
|
+
obj.enq obj.obj(spec['var']), spec['input']
|
128
|
+
end
|
102
129
|
|
103
|
-
|
130
|
+
define_signal :pq do |input| # priority-enques an object
|
131
|
+
spec = convert_to_hash(input, ['var'], 'input')
|
132
|
+
obj.pq obj.obj(spec['var']), spec['input']
|
133
|
+
end
|
134
|
+
|
135
|
+
signal_hash :set, # set or unset objects
|
104
136
|
:signature => ['var', 'class'],
|
105
137
|
:remainder => 'spec',
|
106
138
|
:bind => :build
|
107
|
-
|
108
|
-
signal :get, :signature => ['var'] # get objects
|
109
139
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
140
|
+
signal :get, # get objects
|
141
|
+
:signature => ['var']
|
142
|
+
|
143
|
+
signal_hash :bld, # build an object
|
144
|
+
:signature => ['class'],
|
145
|
+
:remainder => 'spec',
|
146
|
+
:bind => :build
|
147
|
+
|
148
|
+
define_signal :use do |input| # enables middleware
|
149
|
+
spec = convert_to_hash(input, ['class'], 'spec')
|
150
|
+
obj.stack = obj.build(spec, &block)
|
115
151
|
end
|
116
152
|
|
117
|
-
|
153
|
+
define_signal :configure, Configure # configures the app
|
118
154
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
155
|
+
signal :reset # reset the app
|
156
|
+
signal :run # run the app
|
157
|
+
signal :stop # stop the app
|
158
|
+
signal :terminate # terminate the app
|
159
|
+
signal :info # prints app status
|
160
|
+
|
161
|
+
define_signal :list do |input| # list available objects
|
162
|
+
lines = obj.objects.collect {|(key, obj)| "#{key}: #{obj.class}" }
|
163
|
+
lines.empty? ? "No objects yet..." : lines.sort.join("\n")
|
124
164
|
end
|
125
165
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
166
|
+
define_signal :serialize do |input| # serialize the app as signals
|
167
|
+
if input.kind_of?(Array)
|
168
|
+
psr = ConfigParser.new
|
169
|
+
psr.on('--[no-]bare') {|value| psr['bare'] = value }
|
170
|
+
path, *ignored = psr.parse!(input)
|
171
|
+
psr['path'] = path
|
172
|
+
input = psr.config
|
130
173
|
end
|
174
|
+
|
175
|
+
bare = input.has_key?('bare') ? input['bare'] : true
|
176
|
+
signals = obj.serialize(bare)
|
177
|
+
|
178
|
+
if path = input['path']
|
179
|
+
File.open(path, "w") {|io| YAML.dump(signals, io) }
|
180
|
+
else
|
181
|
+
YAML.dump(signals, $stdout)
|
182
|
+
end
|
183
|
+
|
184
|
+
obj
|
131
185
|
end
|
132
186
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
def process(args) # :nodoc:
|
140
|
-
exit(1)
|
187
|
+
define_signal :import do |input| # import serialized signals
|
188
|
+
paths = convert_to_array(input, ['paths'])
|
189
|
+
paths.each do |path|
|
190
|
+
YAML.load_file(path).each do |signal|
|
191
|
+
obj.call(signal)
|
192
|
+
end
|
141
193
|
end
|
194
|
+
|
195
|
+
obj
|
142
196
|
end
|
143
197
|
|
144
|
-
|
198
|
+
define_signal :load, Load # load a tapfile
|
199
|
+
define_signal :help, Help # signals help
|
200
|
+
|
201
|
+
cache_signals
|
145
202
|
|
146
203
|
# Creates a new App with the given configuration. Options can be used to
|
147
204
|
# specify objects that are normally initialized for every new app:
|
@@ -150,36 +207,27 @@ module Tap
|
|
150
207
|
# :queue the application queue; an App::Queue
|
151
208
|
# :objects application objects; a hash of (var, object) pairs
|
152
209
|
# :logger the application logger
|
210
|
+
# :env the application environment
|
153
211
|
#
|
154
212
|
# A block may also be provided; it will be set as a default join.
|
155
|
-
def initialize(config={}, options={}
|
213
|
+
def initialize(config={}, options={})
|
156
214
|
super() # monitor
|
157
215
|
|
158
216
|
@state = State::READY
|
159
217
|
@stack = options[:stack] || Stack.new(self)
|
160
218
|
@queue = options[:queue] || Queue.new
|
161
219
|
@objects = options[:objects] || {}
|
162
|
-
@logger = options[:logger] ||
|
163
|
-
|
164
|
-
|
220
|
+
@logger = options[:logger] || begin
|
221
|
+
logger = Logger.new($stderr)
|
222
|
+
logger.level = Logger::INFO
|
223
|
+
logger.formatter = LOG_FORMATTER
|
224
|
+
logger
|
225
|
+
end
|
226
|
+
@env = options[:env] || Env.new
|
165
227
|
|
166
|
-
self.env = config.delete(:env) || config.delete('env')
|
167
228
|
initialize_config(config)
|
168
229
|
end
|
169
230
|
|
170
|
-
# Sets the application environment and validates that env provides an AGET
|
171
|
-
# ([]) and invert method. AGET is used to lookup constants during init;
|
172
|
-
# it receives the 'class' parameter and should return a corresponding
|
173
|
-
# class. Invert should return an object that reverses the AGET lookup.
|
174
|
-
# Tap::Env and a regular Hash both satisfy this api.
|
175
|
-
#
|
176
|
-
# Env can be set to nil and is set to nil by default, but initialization
|
177
|
-
# is constrained without it.
|
178
|
-
def env=(env)
|
179
|
-
Validation.validate_api(env, [:[], :invert]) unless env.nil?
|
180
|
-
@env = env
|
181
|
-
end
|
182
|
-
|
183
231
|
# Sets the application stack.
|
184
232
|
def stack=(stack)
|
185
233
|
synchronize do
|
@@ -187,11 +235,6 @@ module Tap
|
|
187
235
|
end
|
188
236
|
end
|
189
237
|
|
190
|
-
# True if the debug config or the global variable $DEBUG is true.
|
191
|
-
def debug?
|
192
|
-
debug || $DEBUG
|
193
|
-
end
|
194
|
-
|
195
238
|
# Logs the action and message at the input level (default INFO). The
|
196
239
|
# message may be generated by a block; in that case leave the message
|
197
240
|
# unspecified as nil.
|
@@ -214,37 +257,23 @@ module Tap
|
|
214
257
|
# log(:action, "but a message with #{a}, #{b}, #{c}, and #{d}")
|
215
258
|
# log(:action) { "may be #{best} in a block because you can #{turn} #{it} #{off}" }
|
216
259
|
#
|
217
|
-
def log(action, msg=nil, level=Logger::INFO)
|
260
|
+
def log(action='', msg=nil, level=Logger::INFO)
|
218
261
|
if !quiet || verbose
|
219
|
-
msg
|
220
|
-
logger.add(level, msg, action.to_s)
|
262
|
+
msg = yield if msg.nil? && block_given?
|
263
|
+
logger.add(level, msg.to_s, action.to_s)
|
221
264
|
end
|
222
265
|
end
|
223
266
|
|
224
|
-
#
|
225
|
-
def
|
226
|
-
|
267
|
+
# Enques the task with the input. Returns the task.
|
268
|
+
def enq(task, input=[])
|
269
|
+
queue.enq(task, input)
|
270
|
+
task
|
227
271
|
end
|
228
272
|
|
229
|
-
#
|
230
|
-
def
|
231
|
-
queue.
|
232
|
-
|
233
|
-
end
|
234
|
-
|
235
|
-
# Generates a node from the block and enques. Returns the new node.
|
236
|
-
def bq(*inputs, &block) # :yields: *inputs
|
237
|
-
node = self.node(&block)
|
238
|
-
queue.enq(node, inputs)
|
239
|
-
node
|
240
|
-
end
|
241
|
-
|
242
|
-
# Adds the specified middleware to the stack. The argv will be used as
|
243
|
-
# extra arguments to initialize the middleware.
|
244
|
-
def use(middleware, *argv)
|
245
|
-
synchronize do
|
246
|
-
@stack = middleware.new(@stack, *argv)
|
247
|
-
end
|
273
|
+
# Priority-enques (unshifts) the task with the input. Returns the task.
|
274
|
+
def pq(task, input=[])
|
275
|
+
queue.unshift(task, input)
|
276
|
+
task
|
248
277
|
end
|
249
278
|
|
250
279
|
# Sets the object to the specified variable and returns obj. Provide nil
|
@@ -321,13 +350,20 @@ module Tap
|
|
321
350
|
# signal.call(args) # call the signal with args
|
322
351
|
#
|
323
352
|
# Call returns the result of the signal call.
|
324
|
-
#
|
325
353
|
def call(args, &block)
|
326
|
-
|
327
|
-
|
354
|
+
log(:call, nil, Logger::DEBUG) { args.inspect } if debug
|
355
|
+
|
356
|
+
obj = args['obj']
|
357
|
+
sig = args['sig']
|
328
358
|
args = args['args'] || args
|
329
359
|
|
330
|
-
|
360
|
+
# nil obj routes back to app, so optimize by evaluating signal directly
|
361
|
+
(obj.nil? ? signal(sig, &block) : route(obj, sig, &block)).call(args)
|
362
|
+
end
|
363
|
+
|
364
|
+
def signal(sig, &block)
|
365
|
+
sig = sig.to_s
|
366
|
+
sig =~ OBJECT ? route($1, $2, &block) : super(sig, &block)
|
331
367
|
end
|
332
368
|
|
333
369
|
def route(obj, sig, &block)
|
@@ -341,41 +377,22 @@ module Tap
|
|
341
377
|
|
342
378
|
object.signal(sig, &block)
|
343
379
|
end
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
380
|
+
|
381
|
+
# Resolves the class in env and initializes a new instance with the args
|
382
|
+
# and block. Note that the app is not appended to args by default.
|
383
|
+
def init(clas, *args, &block)
|
384
|
+
env.constant(clas).new(*args, &block)
|
348
385
|
end
|
349
386
|
|
350
|
-
def build(spec)
|
351
|
-
var
|
387
|
+
def build(spec, &block)
|
388
|
+
var = spec['var']
|
352
389
|
clas = spec['class']
|
353
390
|
spec = spec['spec'] || spec
|
354
|
-
obj = nil
|
355
|
-
|
356
|
-
if clas.nil?
|
357
|
-
unless spec.empty?
|
358
|
-
raise "no class specified"
|
359
|
-
end
|
360
|
-
else
|
361
|
-
clas = resolve(clas)
|
362
|
-
|
363
|
-
case spec
|
364
|
-
when Array
|
365
|
-
parse = bang ? :parse! : :parse
|
366
|
-
obj, args = clas.send(parse, spec, self)
|
367
391
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
end
|
373
|
-
|
374
|
-
when Hash
|
375
|
-
obj = clas.build(spec, self)
|
376
|
-
else
|
377
|
-
raise "invalid spec: #{spec.inspect}"
|
378
|
-
end
|
392
|
+
obj = nil
|
393
|
+
unless clas.nil?
|
394
|
+
method_name = spec.kind_of?(Array) ? :parse : :build
|
395
|
+
obj = env.constant(clas).send(method_name, spec, self, &block)
|
379
396
|
end
|
380
397
|
|
381
398
|
unless var.nil?
|
@@ -389,76 +406,12 @@ module Tap
|
|
389
406
|
obj
|
390
407
|
end
|
391
408
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
parser = Parser.new
|
398
|
-
argv = parser.parse!(argv)
|
399
|
-
|
400
|
-
# The queue API does not provide a delete method, so picking out the
|
401
|
-
# deque jobs requires the whole queue be cleared, then re-enqued.
|
402
|
-
# Safety (and speed) is improved with synchronization.
|
403
|
-
queue.synchronize do
|
404
|
-
deque = []
|
405
|
-
blocks = {}
|
406
|
-
|
407
|
-
if auto_enque
|
408
|
-
blocks[:node] = lambda do |obj, args|
|
409
|
-
queue.enq(obj, args)
|
410
|
-
args = nil
|
411
|
-
end
|
412
|
-
|
413
|
-
blocks[:join] = lambda do |obj, args|
|
414
|
-
unless obj.respond_to?(:outputs)
|
415
|
-
# warning
|
416
|
-
end
|
417
|
-
deque.concat obj.outputs
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
parser.specs.each do |spec|
|
422
|
-
if block_given?
|
423
|
-
next unless yield(spec)
|
424
|
-
end
|
425
|
-
|
426
|
-
type, obj, sig, *args = spec
|
427
|
-
|
428
|
-
sig_block = case sig
|
429
|
-
when 'set'
|
430
|
-
blocks[type]
|
431
|
-
when 'parse'
|
432
|
-
block
|
433
|
-
else
|
434
|
-
nil
|
435
|
-
end
|
436
|
-
|
437
|
-
call('obj' => obj, 'sig' => sig, 'args' => args, &sig_block)
|
438
|
-
end
|
439
|
-
|
440
|
-
deque.uniq!
|
441
|
-
queue.clear.each do |(obj, args)|
|
442
|
-
if deque.delete(obj)
|
443
|
-
warn_ignored_args(args)
|
444
|
-
else
|
445
|
-
queue.enq(obj, args)
|
446
|
-
end
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
|
-
argv
|
451
|
-
end
|
452
|
-
|
453
|
-
# Enques the application object specified by var with args. Raises
|
454
|
-
# an error if no such application object exists.
|
455
|
-
def enque(var, *args)
|
456
|
-
unless node = get(var)
|
457
|
-
raise "unknown object: #{var.inspect}"
|
409
|
+
# Adds the specified middleware to the stack. The argv will be used as
|
410
|
+
# extra arguments to initialize the middleware.
|
411
|
+
def use(clas, *argv)
|
412
|
+
synchronize do
|
413
|
+
@stack = init(clas, @stack, *argv)
|
458
414
|
end
|
459
|
-
|
460
|
-
queue.enq(node, args)
|
461
|
-
node
|
462
415
|
end
|
463
416
|
|
464
417
|
# Returns an array of middlware in use by self.
|
@@ -478,7 +431,7 @@ module Tap
|
|
478
431
|
visited << current
|
479
432
|
|
480
433
|
if circular_stack
|
481
|
-
visited.collect! {|
|
434
|
+
visited.collect! {|m| m.class.to_s }.join(', ')
|
482
435
|
raise "circular stack detected:\n[#{visited}]"
|
483
436
|
end
|
484
437
|
end
|
@@ -503,53 +456,41 @@ module Tap
|
|
503
456
|
objects.clear
|
504
457
|
queue.clear
|
505
458
|
end
|
459
|
+
self
|
506
460
|
end
|
507
461
|
|
508
|
-
#
|
509
|
-
# rather than provided as an array.
|
510
|
-
def execute(node, *inputs)
|
511
|
-
dispatch(node, inputs)
|
512
|
-
end
|
513
|
-
|
514
|
-
# Dispatch does the following in order:
|
462
|
+
# Executes tasks by doing the following.
|
515
463
|
#
|
516
|
-
# - call stack with the
|
517
|
-
# - call the
|
464
|
+
# - call stack with the task and input
|
465
|
+
# - call the task joins (task.joins)
|
518
466
|
#
|
519
|
-
#
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
def dispatch(node, inputs=[])
|
525
|
-
result = stack.call(node, inputs)
|
467
|
+
# Returns the stack result.
|
468
|
+
def exe(task, input=[])
|
469
|
+
log("#{var(task)} <<", "#{input.inspect} (#{task.class})", Logger::DEBUG) if debug
|
470
|
+
output = stack.call(task, input)
|
471
|
+
log("#{var(task)} >>", "#{output.inspect} (#{task.class})", Logger::DEBUG) if debug
|
526
472
|
|
527
|
-
if
|
528
|
-
if joins =
|
529
|
-
|
530
|
-
if joins.empty?
|
531
|
-
joins = self.joins
|
532
|
-
end
|
533
|
-
|
473
|
+
if task.respond_to?(:joins)
|
474
|
+
if joins = task.joins
|
534
475
|
joins.each do |join|
|
535
|
-
join.call(
|
476
|
+
join.call(output)
|
536
477
|
end
|
537
478
|
end
|
538
479
|
end
|
539
480
|
|
540
|
-
|
481
|
+
output
|
541
482
|
end
|
542
483
|
|
543
|
-
# Sequentially
|
544
|
-
#
|
484
|
+
# Sequentially executes each enqued job (a [task, input] pair). A run
|
485
|
+
# continues until the queue is empty.
|
545
486
|
#
|
546
|
-
# Run checks the state of self before
|
487
|
+
# Run checks the state of self before executing a task. If the state
|
547
488
|
# changes from RUN, the following behaviors result:
|
548
|
-
#
|
549
|
-
# STOP No more
|
489
|
+
#
|
490
|
+
# STOP No more tasks will be executed; the current task
|
550
491
|
# will continute to completion.
|
551
|
-
# TERMINATE No more
|
552
|
-
# running
|
492
|
+
# TERMINATE No more tasks will be executed and the currently
|
493
|
+
# running task will be discontinued as described in
|
553
494
|
# terminate.
|
554
495
|
#
|
555
496
|
# Calls to run when the state is not READY do nothing and return
|
@@ -564,12 +505,12 @@ module Tap
|
|
564
505
|
|
565
506
|
begin
|
566
507
|
while state == State::RUN
|
567
|
-
break unless
|
568
|
-
|
508
|
+
break unless job = queue.deq
|
509
|
+
exe(*job)
|
569
510
|
end
|
570
511
|
rescue(TerminateError)
|
571
512
|
# gracefully fail for termination errors
|
572
|
-
queue.unshift(*
|
513
|
+
queue.unshift(*job)
|
573
514
|
ensure
|
574
515
|
synchronize { @state = State::READY }
|
575
516
|
end
|
@@ -577,8 +518,8 @@ module Tap
|
|
577
518
|
self
|
578
519
|
end
|
579
520
|
|
580
|
-
# Signals a running app to stop
|
581
|
-
# by setting state to STOP. The
|
521
|
+
# Signals a running app to stop executing tasks to the application stack
|
522
|
+
# by setting state to STOP. The task currently in the stack will continue
|
582
523
|
# to completion.
|
583
524
|
#
|
584
525
|
# Does nothing unless state is RUN.
|
@@ -593,7 +534,7 @@ module Tap
|
|
593
534
|
# them quietly.
|
594
535
|
#
|
595
536
|
# Nodes can set breakpoints that call check_terminate to invoke
|
596
|
-
#
|
537
|
+
# task-specific termination. If a task never calls check_terminate, then
|
597
538
|
# it will continue to completion.
|
598
539
|
#
|
599
540
|
# Does nothing if state is READY.
|
@@ -622,42 +563,10 @@ module Tap
|
|
622
563
|
"state: #{state} (#{State.state_str(state)}) queue: #{queue.size}"
|
623
564
|
end
|
624
565
|
|
625
|
-
#
|
626
|
-
#
|
627
|
-
|
628
|
-
|
629
|
-
# Platforms that use {Syck}[http://whytheluckystiff.net/syck/] (ex MRI)
|
630
|
-
# require a fix because Syck misformats certain dumps, such that they
|
631
|
-
# cannot be reloaded (even by Syck). Specifically:
|
632
|
-
#
|
633
|
-
# &id001 !ruby/object:Tap::Task ?
|
634
|
-
#
|
635
|
-
# should be:
|
636
|
-
#
|
637
|
-
# ? &id001 !ruby/object:Tap::Task
|
638
|
-
#
|
639
|
-
# Dump fixes this error and, in addition, removes Thread and Proc dumps
|
640
|
-
# because they can't be allocated on load.
|
641
|
-
def dump(target=$stdout, options={})
|
642
|
-
synchronize do
|
643
|
-
options = {
|
644
|
-
:date_format => '%Y-%m-%d %H:%M:%S',
|
645
|
-
:date => true,
|
646
|
-
:info => true
|
647
|
-
}.merge(options)
|
648
|
-
|
649
|
-
# print basic headers
|
650
|
-
target.puts "# date: #{Time.now.strftime(options[:date_format])}" if options[:date]
|
651
|
-
target.puts "# info: #{info}" if options[:info]
|
652
|
-
|
653
|
-
# dump yaml, fixing as necessary
|
654
|
-
yaml = YAML.dump(self)
|
655
|
-
yaml.gsub!(/\&(.*!ruby\/object:.*?)\s*\?/) {"? &#{$1} " } if YAML.const_defined?(:Syck)
|
656
|
-
yaml.gsub!(/!ruby\/object:(Thread|Proc) \{\}/, '')
|
657
|
-
target << yaml
|
658
|
-
end
|
659
|
-
|
660
|
-
target
|
566
|
+
# Sets self as instance in the current context, for the duration of the
|
567
|
+
# block (see App.with_context).
|
568
|
+
def scope
|
569
|
+
App.with_context(CURRENT => self) { yield }
|
661
570
|
end
|
662
571
|
|
663
572
|
# Converts the self to a schema that can be used to build a new app with
|
@@ -677,8 +586,8 @@ module Tap
|
|
677
586
|
order = []
|
678
587
|
|
679
588
|
# collect enque signals to setup queue
|
680
|
-
signals = queue.to_a.collect do |(
|
681
|
-
{'sig' => '
|
589
|
+
signals = queue.to_a.collect do |(task, input)|
|
590
|
+
{'sig' => 'enq', 'args' => {'var' => var(task), 'input' => input}}
|
682
591
|
end
|
683
592
|
|
684
593
|
# collect and trace application objects
|
@@ -708,7 +617,6 @@ module Tap
|
|
708
617
|
(variables[obj] ||= []) << var
|
709
618
|
end
|
710
619
|
|
711
|
-
invert_env = env ? env.invert : nil
|
712
620
|
specs.keys.each do |obj|
|
713
621
|
spec = {'sig' => 'set'}
|
714
622
|
|
@@ -722,9 +630,7 @@ module Tap
|
|
722
630
|
end
|
723
631
|
|
724
632
|
# assign the class
|
725
|
-
|
726
|
-
klass = invert_env[klass] if invert_env
|
727
|
-
spec['class'] = klass.to_s
|
633
|
+
spec['class'] = obj.class.to_s
|
728
634
|
|
729
635
|
# merge obj_spec if possible
|
730
636
|
obj_spec = specs[obj]
|
@@ -770,17 +676,10 @@ module Tap
|
|
770
676
|
def inspect
|
771
677
|
"#<#{self.class}:#{object_id} #{info}>"
|
772
678
|
end
|
773
|
-
|
679
|
+
|
774
680
|
private
|
775
|
-
|
776
|
-
# warns of ignored args
|
777
|
-
def warn_ignored_args(args) # :nodoc:
|
778
|
-
if args && debug? && !args.empty?
|
779
|
-
warn "ignoring args: #{args.inspect}"
|
780
|
-
end
|
781
|
-
end
|
782
681
|
|
783
|
-
# Traces each object backwards and forwards for
|
682
|
+
# Traces each object backwards and forwards for tasks, joins, etc. and adds
|
784
683
|
# each to specs as needed. The trace determines and returns the order in
|
785
684
|
# which these specs must be initialized to make sense. Circular traces
|
786
685
|
# are detected.
|
@@ -812,6 +711,10 @@ module Tap
|
|
812
711
|
# obj must exist before brefs (back-references)
|
813
712
|
if obj.respond_to?(:associations)
|
814
713
|
refs, brefs = obj.associations
|
714
|
+
|
715
|
+
unless array_or_nil?(refs) && array_or_nil?(brefs)
|
716
|
+
raise "invalid associations on object (refs, brefs must be an array or nil): #{obj.inspect}"
|
717
|
+
end
|
815
718
|
|
816
719
|
refs.each {|ref| trace(ref, specs, order) } if refs
|
817
720
|
order << obj
|
@@ -823,12 +726,16 @@ module Tap
|
|
823
726
|
order
|
824
727
|
end
|
825
728
|
|
729
|
+
def array_or_nil?(obj) # :nodoc:
|
730
|
+
obj.nil? || obj.kind_of?(Array)
|
731
|
+
end
|
732
|
+
|
826
733
|
def self_to_spec # :nodoc:
|
827
734
|
config = self.config.to_hash {|hash, key, value| hash[key.to_s] = value }
|
828
735
|
{'config' => config, 'self' => true}
|
829
736
|
end
|
830
737
|
|
831
|
-
# TerminateErrors are raised to kill executing
|
738
|
+
# TerminateErrors are raised to kill executing tasks when terminate is
|
832
739
|
# called on an running App. They are handled by the run rescue code.
|
833
740
|
class TerminateError < RuntimeError
|
834
741
|
end
|