tap 0.17.1 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +22 -0
- data/README +15 -14
- data/cmd/console.rb +1 -1
- data/cmd/manifest.rb +25 -5
- data/cmd/run.rb +60 -25
- data/doc/API +37 -38
- data/doc/Class Reference +36 -46
- data/doc/Examples/Workflow +1 -1
- data/lib/tap.rb +1 -1
- data/lib/tap/app.rb +15 -80
- data/lib/tap/app/node.rb +0 -14
- data/lib/tap/env.rb +55 -27
- data/lib/tap/env/manifest.rb +2 -2
- data/lib/tap/intern.rb +50 -0
- data/lib/tap/join.rb +12 -9
- data/lib/tap/middleware.rb +56 -0
- data/lib/tap/schema.rb +182 -14
- data/lib/tap/schema/utils.rb +5 -3
- data/lib/tap/task.rb +53 -130
- data/lib/tap/tasks/dump.rb +1 -1
- data/lib/tap/tasks/load.rb +8 -9
- data/lib/tap/templater.rb +203 -0
- data/lib/tap/{constants.rb → version.rb} +2 -2
- metadata +6 -5
- data/lib/tap/support/intern.rb +0 -53
- data/lib/tap/support/templater.rb +0 -207
data/doc/Examples/Workflow
CHANGED
data/lib/tap.rb
CHANGED
data/lib/tap/app.rb
CHANGED
@@ -4,7 +4,6 @@ require 'tap/app/node'
|
|
4
4
|
require 'tap/app/state'
|
5
5
|
require 'tap/app/stack'
|
6
6
|
require 'tap/app/queue'
|
7
|
-
require 'tap/schema'
|
8
7
|
|
9
8
|
module Tap
|
10
9
|
|
@@ -94,26 +93,6 @@ module Tap
|
|
94
93
|
#
|
95
94
|
# Middleware can be nested with multiple calls to use.
|
96
95
|
#
|
97
|
-
# === Dependencies
|
98
|
-
#
|
99
|
-
# Nodes allow the construction of dependency-based workflows. A node only
|
100
|
-
# executes after its dependencies have been resolved (ie executed).
|
101
|
-
#
|
102
|
-
# runlist = []
|
103
|
-
# n0 = app.node { runlist << 0 }
|
104
|
-
# n1 = app.node { runlist << 1 }
|
105
|
-
#
|
106
|
-
# n0.depends_on(n1)
|
107
|
-
# app.enq(n0)
|
108
|
-
#
|
109
|
-
# app.run
|
110
|
-
# runlist # => [1, 0]
|
111
|
-
#
|
112
|
-
# Dependencies are resolved <em>every time</em> a node executes; individual
|
113
|
-
# dependencies can implement single-execution if desired. Dependencies are
|
114
|
-
# not resolved with arguments, ie dependency nodes must be able to execute
|
115
|
-
# without inputs.
|
116
|
-
#
|
117
96
|
class App
|
118
97
|
class << self
|
119
98
|
# Sets the current app instance
|
@@ -148,8 +127,7 @@ module Tap
|
|
148
127
|
# The application queue
|
149
128
|
attr_reader :queue
|
150
129
|
|
151
|
-
# A cache of application-specific data.
|
152
|
-
# instances of tasks. Not recommended for casual use.
|
130
|
+
# A cache of application-specific data.
|
153
131
|
attr_reader :cache
|
154
132
|
|
155
133
|
# The default joins for nodes that have no joins set
|
@@ -171,7 +149,6 @@ module Tap
|
|
171
149
|
@stack = options[:stack] || Stack.new(self)
|
172
150
|
@queue = options[:queue] || Queue.new
|
173
151
|
@cache = options[:cache] || {}
|
174
|
-
@trace = []
|
175
152
|
@default_joins = []
|
176
153
|
on_complete(&block)
|
177
154
|
|
@@ -220,27 +197,24 @@ module Tap
|
|
220
197
|
|
221
198
|
# Adds the specified middleware to the stack.
|
222
199
|
def use(middleware, *argv)
|
223
|
-
|
200
|
+
synchronize do
|
201
|
+
@stack = middleware.new(@stack, *argv)
|
202
|
+
end
|
224
203
|
end
|
225
204
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
end
|
205
|
+
# Returns an array of middlware in use by self.
|
206
|
+
def middleware
|
207
|
+
middleware = []
|
230
208
|
|
231
|
-
|
232
|
-
|
233
|
-
|
209
|
+
synchronize do
|
210
|
+
current = stack
|
211
|
+
until current.kind_of?(Stack)
|
212
|
+
middleware << current
|
213
|
+
current = current.stack
|
234
214
|
end
|
235
215
|
end
|
236
216
|
|
237
|
-
|
238
|
-
|
239
|
-
if options[:clean]
|
240
|
-
reset
|
241
|
-
end
|
242
|
-
|
243
|
-
schema.build!(self)
|
217
|
+
middleware
|
244
218
|
end
|
245
219
|
|
246
220
|
# Clears the cache, the queue, and resets the stack so that no middleware
|
@@ -257,36 +231,6 @@ module Tap
|
|
257
231
|
end
|
258
232
|
end
|
259
233
|
|
260
|
-
# Dispatches each dependency of node. A block can be given to do something
|
261
|
-
# else with the nodes (ex: reset single-execution dependencies). Resolve
|
262
|
-
# will recursively yield dependencies if specified.
|
263
|
-
#
|
264
|
-
# Resolve raises an error for circular dependencies.
|
265
|
-
def resolve(node, recursive=false, &block)
|
266
|
-
node.dependencies.each do |dependency|
|
267
|
-
if @trace.include?(dependency)
|
268
|
-
@trace.push dependency
|
269
|
-
raise DependencyError.new(@trace)
|
270
|
-
end
|
271
|
-
|
272
|
-
# mark the results at the index to prevent
|
273
|
-
# infinite loops with circular dependencies
|
274
|
-
@trace.push dependency
|
275
|
-
|
276
|
-
if recursive
|
277
|
-
resolve(dependency, recursive, &block)
|
278
|
-
end
|
279
|
-
|
280
|
-
if block_given?
|
281
|
-
yield(dependency)
|
282
|
-
else
|
283
|
-
dispatch(dependency)
|
284
|
-
end
|
285
|
-
|
286
|
-
@trace.pop
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
234
|
# Dispatches node to the application stack with the inputs.
|
291
235
|
def execute(node, *inputs)
|
292
236
|
dispatch(node, inputs)
|
@@ -295,13 +239,11 @@ module Tap
|
|
295
239
|
# Dispatch sends the node into the application stack with the inputs.
|
296
240
|
# Dispatch does the following in order:
|
297
241
|
#
|
298
|
-
# - resolve node dependencies using resolve_dependencies
|
299
242
|
# - call stack with the node and inputs
|
300
243
|
# - call the node joins, if set, or the default_joins with the results
|
301
244
|
#
|
302
245
|
# Dispatch returns the node result.
|
303
246
|
def dispatch(node, inputs=[])
|
304
|
-
resolve(node)
|
305
247
|
result = stack.call(node, inputs)
|
306
248
|
|
307
249
|
joins = node.joins.empty? ? default_joins : node.joins
|
@@ -430,8 +372,8 @@ module Tap
|
|
430
372
|
target
|
431
373
|
end
|
432
374
|
|
433
|
-
# Sets the block to receive the
|
434
|
-
# (ie the block is set as default_join).
|
375
|
+
# Sets the block to receive the result of nodes with no joins
|
376
|
+
# (ie the block is set as a default_join).
|
435
377
|
def on_complete(&block) # :yields: _result
|
436
378
|
self.default_joins << block if block
|
437
379
|
self
|
@@ -443,12 +385,5 @@ module Tap
|
|
443
385
|
# called on an running App. They are handled by the run rescue code.
|
444
386
|
class TerminateError < RuntimeError
|
445
387
|
end
|
446
|
-
|
447
|
-
# Raised when Tap::App#resolve detects a circular dependency.
|
448
|
-
class DependencyError < StandardError
|
449
|
-
def initialize(trace)
|
450
|
-
super "circular dependency: [#{trace.join(', ')}]"
|
451
|
-
end
|
452
|
-
end
|
453
388
|
end
|
454
389
|
end
|
data/lib/tap/app/node.rb
CHANGED
@@ -9,9 +9,6 @@ module Tap
|
|
9
9
|
# The joins called when call completes
|
10
10
|
attr_accessor :joins
|
11
11
|
|
12
|
-
# An array of node dependencies
|
13
|
-
attr_reader :dependencies
|
14
|
-
|
15
12
|
# Interns a new node by extending the block with Node.
|
16
13
|
def self.intern(&block)
|
17
14
|
block.extend self
|
@@ -20,7 +17,6 @@ module Tap
|
|
20
17
|
# Sets up required variables for extended objects.
|
21
18
|
def self.extended(obj) # :nodoc:
|
22
19
|
obj.instance_variable_set(:@joins, [])
|
23
|
-
obj.instance_variable_set(:@dependencies, [])
|
24
20
|
end
|
25
21
|
|
26
22
|
# Sets the block as a join for self.
|
@@ -28,16 +24,6 @@ module Tap
|
|
28
24
|
self.joins << block if block
|
29
25
|
self
|
30
26
|
end
|
31
|
-
|
32
|
-
# Adds the dependency to self. Dependencies are resolved by an app
|
33
|
-
# during App#dispatch and must be valid nodes.
|
34
|
-
def depends_on(dependency)
|
35
|
-
raise "cannot depend on self" if dependency == self
|
36
|
-
unless dependencies.include?(dependency)
|
37
|
-
dependencies << dependency
|
38
|
-
end
|
39
|
-
self
|
40
|
-
end
|
41
27
|
end
|
42
28
|
end
|
43
29
|
end
|
data/lib/tap/env.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'tap/root'
|
2
2
|
require 'tap/env/manifest'
|
3
|
-
require 'tap/
|
3
|
+
require 'tap/templater'
|
4
4
|
autoload(:YAML, 'yaml')
|
5
5
|
|
6
6
|
module Tap
|
@@ -65,7 +65,7 @@ module Tap
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
68
|
+
def scan_dir(load_path, pattern='**/*.rb')
|
69
69
|
Dir.chdir(load_path) do
|
70
70
|
Dir.glob(pattern).each do |require_path|
|
71
71
|
next unless File.file?(require_path)
|
@@ -73,15 +73,9 @@ module Tap
|
|
73
73
|
default_const_name = require_path.chomp('.rb').camelize
|
74
74
|
|
75
75
|
# note: the default const name has to be set here to allow for implicit
|
76
|
-
# constant attributes
|
77
|
-
#
|
78
|
-
|
79
|
-
document = Lazydoc[require_path]
|
80
|
-
case document.default_const_name
|
81
|
-
when nil then document.default_const_name = default_const_name
|
82
|
-
when default_const_name
|
83
|
-
else raise "found a conflicting default const name"
|
84
|
-
end
|
76
|
+
# constant attributes. An error can arise if the same path is globed
|
77
|
+
# from two different dirs... no surefire solution.
|
78
|
+
Lazydoc[require_path].default_const_name = default_const_name
|
85
79
|
|
86
80
|
# scan for constants
|
87
81
|
Lazydoc::Document.scan(File.read(require_path)) do |const_name, type, comment|
|
@@ -100,6 +94,27 @@ module Tap
|
|
100
94
|
end
|
101
95
|
end
|
102
96
|
end
|
97
|
+
|
98
|
+
def scan(path, key='[a-z_]+')
|
99
|
+
Lazydoc::Document.scan(File.read(path), key) do |const_name, type, comment|
|
100
|
+
if const_name.empty?
|
101
|
+
unless const_name = Lazydoc[path].default_const_name
|
102
|
+
raise "could not determine a constant name for #{type} in: #{path.inspect}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
constant = Constant.new(const_name, path, comment)
|
107
|
+
yield(type, constant)
|
108
|
+
|
109
|
+
###############################################################
|
110
|
+
# [depreciated] manifest as a task key will be removed at 1.0
|
111
|
+
if type == 'manifest'
|
112
|
+
warn "depreciation: ::task should be used instead of ::manifest as a resource key (#{require_path})"
|
113
|
+
yield('task', constant)
|
114
|
+
end
|
115
|
+
###############################################################
|
116
|
+
end
|
117
|
+
end
|
103
118
|
end
|
104
119
|
self.instance = nil
|
105
120
|
|
@@ -441,9 +456,8 @@ module Tap
|
|
441
456
|
load_paths.each do |load_path|
|
442
457
|
next unless File.directory?(load_path)
|
443
458
|
|
444
|
-
Env.
|
445
|
-
|
446
|
-
entries << constant
|
459
|
+
Env.scan_dir(load_path) do |type, constant|
|
460
|
+
(registry[type.to_sym] ||= []) << constant
|
447
461
|
end
|
448
462
|
end
|
449
463
|
|
@@ -468,6 +482,15 @@ module Tap
|
|
468
482
|
builders[type] = block
|
469
483
|
end
|
470
484
|
|
485
|
+
#--
|
486
|
+
# Potential bug, constants can be added twice.
|
487
|
+
def scan(path, key='[a-z_]+')
|
488
|
+
registry = self.registry
|
489
|
+
Env.scan(path, key) do |type, constant|
|
490
|
+
(registry[type.to_sym] ||= []) << constant
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
471
494
|
def manifest(type) # :yields: env
|
472
495
|
type = type.to_sym
|
473
496
|
|
@@ -488,9 +511,11 @@ module Tap
|
|
488
511
|
registries.clear
|
489
512
|
end
|
490
513
|
|
491
|
-
|
492
|
-
#
|
493
|
-
|
514
|
+
# Searches across each for the first registered object minimatching key. A
|
515
|
+
# single env can be specified by using a compound key like 'env_key:key'.
|
516
|
+
#
|
517
|
+
# Returns nil if no matching object is found.
|
518
|
+
def seek(type, key, value_only=true)
|
494
519
|
key =~ COMPOUND_KEY
|
495
520
|
envs = if $2
|
496
521
|
# compound key, match for env
|
@@ -504,21 +529,24 @@ module Tap
|
|
504
529
|
# traverse envs looking for the first
|
505
530
|
# manifest entry matching key
|
506
531
|
envs.each do |env|
|
507
|
-
if
|
508
|
-
return [env,
|
532
|
+
if value = env.manifest(type).minimatch(key)
|
533
|
+
return value_only ? value : [env, value]
|
509
534
|
end
|
510
535
|
end
|
511
536
|
|
512
537
|
nil
|
513
538
|
end
|
514
539
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
540
|
+
def reverse_seek(type, key_only=true, &block)
|
541
|
+
each do |env|
|
542
|
+
manifest = env.manifest(type)
|
543
|
+
if value = manifest.find(&block)
|
544
|
+
key = manifest.minihash(true)[value]
|
545
|
+
return key_only ? key : "#{minihash(true)[env]}:#{key}"
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
nil
|
522
550
|
end
|
523
551
|
|
524
552
|
# All templaters are yielded to the block before any are built. This
|
@@ -530,7 +558,7 @@ module Tap
|
|
530
558
|
|
531
559
|
env_keys = minihash(true)
|
532
560
|
collect do |env|
|
533
|
-
templater =
|
561
|
+
templater = Templater.new(template, :env => env, :env_key => env_keys[env])
|
534
562
|
yield(templater, globals) if block_given?
|
535
563
|
templater
|
536
564
|
end.collect! do |templater|
|
data/lib/tap/env/manifest.rb
CHANGED
@@ -43,8 +43,8 @@ module Tap
|
|
43
43
|
# env can be specified by using a compound key like 'env_key:key'.
|
44
44
|
#
|
45
45
|
# Returns nil if no matching entry is found.
|
46
|
-
def seek(key)
|
47
|
-
env.seek(type, key)
|
46
|
+
def seek(key, value_only=true)
|
47
|
+
env.seek(type, key, value_only)
|
48
48
|
end
|
49
49
|
|
50
50
|
def [](key)
|
data/lib/tap/intern.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Tap
|
2
|
+
# Generates an Intern module to override the specified method_name. Intern
|
3
|
+
# modules are useful to override a tiny bit of functionality without having
|
4
|
+
# to generate a full subclass.
|
5
|
+
#
|
6
|
+
# An Intern module:
|
7
|
+
#
|
8
|
+
# - adds an accessor for <method_name>_block
|
9
|
+
# - overrides <method_name> to call the block, prepending self to
|
10
|
+
# the input arguments
|
11
|
+
#
|
12
|
+
# For example:
|
13
|
+
#
|
14
|
+
# array = [1,2,3].extend Intern(:last)
|
15
|
+
#
|
16
|
+
# array.last # => 3
|
17
|
+
# array.last_block = lambda {|arr| arr.first }
|
18
|
+
# array.last # => 3
|
19
|
+
#
|
20
|
+
def self.Intern(method_name)
|
21
|
+
mod = INTERN_MODULES[method_name.to_sym]
|
22
|
+
return mod unless mod == nil
|
23
|
+
|
24
|
+
mod = INTERN_MODULES[method_name.to_sym] = Module.new
|
25
|
+
mod.module_eval %Q{
|
26
|
+
attr_accessor :#{method_name}_block
|
27
|
+
|
28
|
+
def #{method_name}(*inputs)
|
29
|
+
return super unless #{method_name}_block
|
30
|
+
inputs.unshift(self)
|
31
|
+
|
32
|
+
arity = #{method_name}_block.arity
|
33
|
+
n = inputs.length
|
34
|
+
unless n == arity || (arity < 0 && (-1-n) <= arity)
|
35
|
+
raise ArgumentError.new("wrong number of arguments (\#{n} for \#{arity})")
|
36
|
+
end
|
37
|
+
|
38
|
+
#{method_name}_block.call(*inputs)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
mod
|
42
|
+
end
|
43
|
+
|
44
|
+
# An array of already-declared intern modules,
|
45
|
+
# keyed by method_name.
|
46
|
+
INTERN_MODULES = {}
|
47
|
+
|
48
|
+
# An Intern module for :process.
|
49
|
+
Intern = Tap::Intern(:process)
|
50
|
+
end
|
data/lib/tap/join.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'tap/app'
|
2
|
-
require 'tap/
|
2
|
+
require 'tap/intern'
|
3
3
|
|
4
4
|
module Tap
|
5
5
|
class App
|
@@ -9,7 +9,7 @@ module Tap
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
# :startdoc::join
|
12
|
+
# :startdoc::join an unsyncrhonized, multi-way join
|
13
13
|
#
|
14
14
|
# Join defines an unsynchronized, multi-way join where n inputs send their
|
15
15
|
# results to m outputs. Flags can augment how the results are passed, in
|
@@ -25,10 +25,7 @@ module Tap
|
|
25
25
|
super
|
26
26
|
end
|
27
27
|
|
28
|
-
# Parses the argv into an
|
29
|
-
# inputs and outputs implicitly define the inputs and output for the
|
30
|
-
# instance. By default parse parses an argh then calls instantiate,
|
31
|
-
# but there is no requirement that this occurs in subclasses.
|
28
|
+
# Parses the argv into an instance of self.
|
32
29
|
def parse(argv=ARGV, app=Tap::App.instance)
|
33
30
|
parse!(argv.dup, app)
|
34
31
|
end
|
@@ -47,8 +44,7 @@ module Tap
|
|
47
44
|
}, app)
|
48
45
|
end
|
49
46
|
|
50
|
-
# Instantiates an instance of self
|
51
|
-
# outputs, instance].
|
47
|
+
# Instantiates an instance of self.
|
52
48
|
def instantiate(argh, app=Tap::App.instance)
|
53
49
|
new(argh[:config] || {}, app)
|
54
50
|
end
|
@@ -61,7 +57,7 @@ module Tap
|
|
61
57
|
def intern(config={}, app=Tap::App.instance, &block) # :yields: join, result
|
62
58
|
instance = new(config, app)
|
63
59
|
if block_given?
|
64
|
-
instance.extend
|
60
|
+
instance.extend Intern(:call)
|
65
61
|
instance.call_block = block
|
66
62
|
end
|
67
63
|
instance
|
@@ -151,6 +147,13 @@ module Tap
|
|
151
147
|
end
|
152
148
|
end
|
153
149
|
|
150
|
+
def to_hash
|
151
|
+
{
|
152
|
+
:class => self.class,
|
153
|
+
:config => config.to_hash
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
154
157
|
protected
|
155
158
|
|
156
159
|
# Dispatches the results to the node.
|