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,220 @@
|
|
1
|
+
require 'tap/root/versions'
|
2
|
+
autoload(:FileUtils, 'fileutils')
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
class Root
|
6
|
+
|
7
|
+
# A variety of utility methods for working with paths.
|
8
|
+
module Utils
|
9
|
+
include Versions
|
10
|
+
|
11
|
+
# Regexp to match a windows-style root path.
|
12
|
+
WIN_ROOT_PATTERN = /^[A-z]:\//
|
13
|
+
|
14
|
+
module_function
|
15
|
+
|
16
|
+
# Returns the path of 'path' relative to dir. Both dir and path will
|
17
|
+
# be expanded to dir_string, if specified, before the relative path is
|
18
|
+
# determined. Returns nil if the path is not relative to dir.
|
19
|
+
#
|
20
|
+
# relative_path('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
|
21
|
+
#
|
22
|
+
def relative_path(dir, path, dir_string=Dir.pwd)
|
23
|
+
if dir_string
|
24
|
+
dir = File.expand_path(dir, dir_string)
|
25
|
+
path = File.expand_path(path, dir_string)
|
26
|
+
end
|
27
|
+
return nil unless Utils.relative?(dir, path, false)
|
28
|
+
|
29
|
+
# use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
|
30
|
+
# as in: relative_path('/path', '/path') then the first arg returns nil, and an
|
31
|
+
# empty string is returned
|
32
|
+
path[(dir.chomp("/").length + 1)..-1] || ""
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generates a target path translated from the source_dir to the
|
36
|
+
# target_dir. Raises an error if the path is not relative to the
|
37
|
+
# source_dir.
|
38
|
+
#
|
39
|
+
# translate("/path/to/file.txt", "/path", "/another/path") # => '/another/path/to/file.txt'
|
40
|
+
#
|
41
|
+
def translate(path, source_dir, target_dir)
|
42
|
+
unless relative_path = relative_path(source_dir, path)
|
43
|
+
raise ArgumentError, "\n#{path}\nis not relative to:\n#{source_dir}"
|
44
|
+
end
|
45
|
+
File.join(target_dir, relative_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the path, exchanging the extension with extname. Extname may
|
49
|
+
# optionally omit the leading period.
|
50
|
+
#
|
51
|
+
# exchange('path/to/file.txt', '.html') # => 'path/to/file.html'
|
52
|
+
# exchange('path/to/file.txt', 'rb') # => 'path/to/file.rb'
|
53
|
+
#
|
54
|
+
def exchange(path, extname)
|
55
|
+
"#{path.chomp(File.extname(path))}#{extname[0] == ?. ? '' : '.'}#{extname}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Lists all unique paths matching the input glob patterns.
|
59
|
+
def glob(*patterns)
|
60
|
+
Dir[*patterns].uniq
|
61
|
+
end
|
62
|
+
|
63
|
+
# Lists all unique versions of path matching the glob version patterns. If
|
64
|
+
# no patterns are specified, then all versions of path will be returned.
|
65
|
+
def version_glob(path, *vpatterns)
|
66
|
+
paths = []
|
67
|
+
|
68
|
+
vpatterns << "*" if vpatterns.empty?
|
69
|
+
vpatterns.each do |vpattern|
|
70
|
+
paths.concat Dir.glob(version(path, vpattern))
|
71
|
+
|
72
|
+
# extra work to include the default version path for any version
|
73
|
+
paths << path if vpattern == "*" && File.exists?(path)
|
74
|
+
end
|
75
|
+
|
76
|
+
paths.uniq
|
77
|
+
end
|
78
|
+
|
79
|
+
# Path suffix glob. Globs along the paths for the specified suffix
|
80
|
+
# pattern.
|
81
|
+
def suffix_glob(suffix_pattern, *paths)
|
82
|
+
paths.collect! {|path| File.join(path, suffix_pattern) }
|
83
|
+
Dir[*paths].uniq
|
84
|
+
end
|
85
|
+
|
86
|
+
# Like Dir.chdir but makes the directory, if necessary, when mkdir is
|
87
|
+
# specified. chdir raises an error for non-existant directories, as well
|
88
|
+
# as non-directory inputs.
|
89
|
+
def chdir(dir, mkdir=false, &block)
|
90
|
+
dir = File.expand_path(dir)
|
91
|
+
|
92
|
+
unless File.directory?(dir)
|
93
|
+
if !File.exists?(dir) && mkdir
|
94
|
+
FileUtils.mkdir_p(dir)
|
95
|
+
else
|
96
|
+
raise ArgumentError, "not a directory: #{dir}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
Dir.chdir(dir, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Prepares the input path by making the parent directory for path. If a
|
104
|
+
# block is given, a file is created at path and passed to it; in this
|
105
|
+
# way files with non-existant parent directories are readily made.
|
106
|
+
#
|
107
|
+
# Returns path.
|
108
|
+
def prepare(path)
|
109
|
+
dirname = File.dirname(path)
|
110
|
+
FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
|
111
|
+
File.open(path, "w") {|io| yield(io) } if block_given?
|
112
|
+
path
|
113
|
+
end
|
114
|
+
|
115
|
+
# The path root type indicating windows, *nix, or some unknown style of
|
116
|
+
# paths (:win, :nix, :unknown).
|
117
|
+
def path_root_type
|
118
|
+
@path_root_type ||= case
|
119
|
+
when RUBY_PLATFORM =~ /mswin/ && File.expand_path(".") =~ WIN_ROOT_PATTERN then :win
|
120
|
+
when File.expand_path(".")[0] == ?/ then :nix
|
121
|
+
else :unknown
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns true if the input path appears to be an expanded path, based on
|
126
|
+
# path_root_type.
|
127
|
+
#
|
128
|
+
# If root_type == :win returns true if the path matches WIN_ROOT_PATTERN.
|
129
|
+
#
|
130
|
+
# expanded?('C:/path') # => true
|
131
|
+
# expanded?('c:/path') # => true
|
132
|
+
# expanded?('D:/path') # => true
|
133
|
+
# expanded?('path') # => false
|
134
|
+
#
|
135
|
+
# If root_type == :nix, then expanded? returns true if the path begins
|
136
|
+
# with '/'.
|
137
|
+
#
|
138
|
+
# expanded?('/path') # => true
|
139
|
+
# expanded?('path') # => false
|
140
|
+
#
|
141
|
+
# Otherwise expanded? always returns nil.
|
142
|
+
def expanded?(path, root_type=path_root_type)
|
143
|
+
case root_type
|
144
|
+
when :win
|
145
|
+
path =~ WIN_ROOT_PATTERN ? true : false
|
146
|
+
when :nix
|
147
|
+
path[0] == ?/
|
148
|
+
else
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns true if path is relative to dir. Both path and dir will be
|
154
|
+
# expanded relative to dir_string, if specified.
|
155
|
+
def relative?(dir, path, dir_string=Dir.pwd)
|
156
|
+
if dir_string
|
157
|
+
dir = File.expand_path(dir, dir_string)
|
158
|
+
path = File.expand_path(path, dir_string)
|
159
|
+
end
|
160
|
+
|
161
|
+
path.rindex(dir, 0) == 0
|
162
|
+
end
|
163
|
+
|
164
|
+
# Trivial indicates when a path does not have content to load. Returns
|
165
|
+
# true if the file at path is empty, non-existant, a directory, or nil.
|
166
|
+
def trivial?(path)
|
167
|
+
path == nil || !File.file?(path) || File.size(path) == 0
|
168
|
+
end
|
169
|
+
|
170
|
+
# Empty returns true when dir is an existing directory that has no files.
|
171
|
+
def empty?(dir)
|
172
|
+
File.directory?(dir) && (Dir.entries(dir) - ['.', '..']).empty?
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns the path segments for the given path, splitting along the path
|
176
|
+
# divider. Env paths are always represented by a string, if only an
|
177
|
+
# empty string.
|
178
|
+
#
|
179
|
+
# os divider example
|
180
|
+
# windows '\' split('C:\path\to\file') # => ["C:", "path", "to", "file"]
|
181
|
+
# *nix '/' split('/path/to/file') # => ["", "path", "to", "file"]
|
182
|
+
#
|
183
|
+
# The path is always expanded relative to the expand_dir; so '.' and
|
184
|
+
# '..' are resolved. However, unless expand_path == true, only the
|
185
|
+
# segments relative to the expand_dir are returned.
|
186
|
+
#
|
187
|
+
# On windows (note that expanding paths allows the use of slashes or
|
188
|
+
# backslashes):
|
189
|
+
#
|
190
|
+
# Dir.pwd # => 'C:/'
|
191
|
+
# split('path\to\..\.\to\file') # => ["C:", "path", "to", "file"]
|
192
|
+
# split('path/to/.././to/file', false) # => ["path", "to", "file"]
|
193
|
+
#
|
194
|
+
# On *nix (or more generally systems with '/' roots):
|
195
|
+
#
|
196
|
+
# Dir.pwd # => '/'
|
197
|
+
# split('path/to/.././to/file') # => ["", "path", "to", "file"]
|
198
|
+
# split('path/to/.././to/file', false) # => ["path", "to", "file"]
|
199
|
+
#
|
200
|
+
def split(path, expand_path=true, expand_dir=Dir.pwd)
|
201
|
+
path = if expand_path
|
202
|
+
File.expand_path(path, expand_dir)
|
203
|
+
else
|
204
|
+
# normalize the path by expanding it, then
|
205
|
+
# work back to the relative path as needed
|
206
|
+
expanded_dir = File.expand_path(expand_dir)
|
207
|
+
expanded_path = File.expand_path(path, expand_dir)
|
208
|
+
expanded_path.index(expanded_dir) != 0 ? expanded_path : relative_path(expanded_dir, expanded_path)
|
209
|
+
end
|
210
|
+
|
211
|
+
segments = path.scan(/[^\/]+/)
|
212
|
+
|
213
|
+
# add back the root path as needed on *nix
|
214
|
+
segments.unshift "" if path[0] == ?/
|
215
|
+
segments
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
module Tap
|
2
|
-
|
3
|
-
|
2
|
+
class Root
|
4
3
|
# Version provides methods for adding, removing, and incrementing versions
|
5
|
-
# at the end of
|
6
|
-
# '
|
4
|
+
# at the end of paths. Versions are all formatted like:
|
5
|
+
# 'path-version.extension'.
|
7
6
|
#
|
8
7
|
module Versions
|
9
|
-
|
10
|
-
# Adds a version to the
|
11
|
-
# 'path-version.extension'. If no version is specified, then the
|
12
|
-
# is returned.
|
8
|
+
|
9
|
+
# Adds a version to the path. Versioned paths follow the format:
|
10
|
+
# 'path-version.extension'. If no version is specified, then the
|
11
|
+
# path is returned.
|
13
12
|
#
|
14
13
|
# version("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
|
15
14
|
#
|
@@ -22,15 +21,15 @@ module Tap
|
|
22
21
|
path.chomp(extname) + '-' + version + extname
|
23
22
|
end
|
24
23
|
end
|
25
|
-
|
26
|
-
# Increments the version of the
|
24
|
+
|
25
|
+
# Increments the version of the path by the specified increment.
|
27
26
|
#
|
28
27
|
# increment("path/to/file-1.0.txt", "0.0.1") # => "path/to/file-1.0.1.txt"
|
29
28
|
# increment("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
|
30
29
|
#
|
31
30
|
def increment(path, increment)
|
32
31
|
path, version = deversion(path)
|
33
|
-
|
32
|
+
|
34
33
|
# split the version and increment into integer arrays of equal length
|
35
34
|
increment, version = [increment, version].collect do |vstr|
|
36
35
|
begin
|
@@ -39,8 +38,11 @@ module Tap
|
|
39
38
|
raise "Bad version or increment: #{vstr}"
|
40
39
|
end
|
41
40
|
end
|
42
|
-
|
43
|
-
|
41
|
+
|
42
|
+
if increment.length > version.length
|
43
|
+
version.concat Array.new(increment.length - version.length, 0)
|
44
|
+
end
|
45
|
+
|
44
46
|
# add the increment to version
|
45
47
|
0.upto(version.length-1) do |i|
|
46
48
|
version[i] += (increment[i] || 0)
|
@@ -48,9 +50,10 @@ module Tap
|
|
48
50
|
|
49
51
|
self.version(path, version.join("."))
|
50
52
|
end
|
51
|
-
|
52
|
-
# Splits the version from the input path, then returns the path and
|
53
|
-
# If no version is specified, then the returned version will be
|
53
|
+
|
54
|
+
# Splits the version from the input path, then returns the path and
|
55
|
+
# version. If no version is specified, then the returned version will be
|
56
|
+
# nil.
|
54
57
|
#
|
55
58
|
# deversion("path/to/file-1.0.txt") # => ["path/to/file.txt", "1.0"]
|
56
59
|
# deversion("path/to/file.txt") # => ["path/to/file.txt", nil]
|
@@ -60,8 +63,8 @@ module Tap
|
|
60
63
|
extname = '' if extname =~ /^\.\d+$/
|
61
64
|
path =~ /^(.*)-(\d(\.?\d)*)#{extname}$/ ? [$1 + extname, $2] : [path, nil]
|
62
65
|
end
|
63
|
-
|
64
|
-
# A <=> comparison for versions. compare_versions can take strings,
|
66
|
+
|
67
|
+
# A <=> comparison for versions. compare_versions can take strings,
|
65
68
|
# integers, or even arrays representing the parts of a version.
|
66
69
|
#
|
67
70
|
# compare_versions("1.0.0", "0.9.9") # => 1
|
@@ -69,17 +72,17 @@ module Tap
|
|
69
72
|
# compare_versions([0,9], [0,9,1]) # => -1
|
70
73
|
def compare_versions(a,b)
|
71
74
|
a, b = [a,b].collect {|item| to_integer_array(item) }
|
72
|
-
|
75
|
+
|
73
76
|
# equalize the lengths of the integer arrays
|
74
77
|
d = b.length - a.length
|
75
78
|
case
|
76
79
|
when d < 0 then b.concat Array.new(-d, 0)
|
77
80
|
when d > 0 then a.concat Array.new(d, 0)
|
78
81
|
end
|
79
|
-
|
82
|
+
|
80
83
|
a <=> b
|
81
84
|
end
|
82
|
-
|
85
|
+
|
83
86
|
# Version unique. Select the latest or earliest versions of each file
|
84
87
|
# in the array. For paths that have no version, vniq considers any
|
85
88
|
# version to beat no version. The order of paths is preserved by
|
@@ -104,25 +107,25 @@ module Tap
|
|
104
107
|
base, version = deversion(path)
|
105
108
|
(unique[base] ||= []) << version
|
106
109
|
end
|
107
|
-
|
110
|
+
|
108
111
|
results = []
|
109
112
|
unique.each_pair do |base, versions|
|
110
113
|
versions = versions.sort {|a, b| compare_versions(a,b) }
|
111
114
|
winner = earliest ? versions.shift : versions.pop
|
112
115
|
results << version(base, winner)
|
113
116
|
end
|
114
|
-
|
117
|
+
|
115
118
|
results = results.sort_by do |path|
|
116
119
|
array.index(path)
|
117
120
|
end if preserve_order
|
118
|
-
|
121
|
+
|
119
122
|
results
|
120
123
|
end
|
121
|
-
|
124
|
+
|
122
125
|
private
|
123
|
-
|
124
|
-
# Converts an input argument (typically a string or an array)
|
125
|
-
#
|
126
|
+
|
127
|
+
# Converts an input argument (typically a string or an array) to an
|
128
|
+
# array of integers. Splits version string on "."
|
126
129
|
def to_integer_array(arg)
|
127
130
|
arr = case arg
|
128
131
|
when Array then arg
|
@@ -130,7 +133,6 @@ module Tap
|
|
130
133
|
end
|
131
134
|
arr.collect {|i| i.to_i}
|
132
135
|
end
|
133
|
-
|
134
136
|
end
|
135
137
|
end
|
136
138
|
end
|
data/lib/tap/schema.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'tap/schema/utils'
|
2
|
+
require 'tap/schema/parser'
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
class Schema
|
6
|
+
class << self
|
7
|
+
def load(str)
|
8
|
+
new(YAML.load(str) || {})
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_file(path)
|
12
|
+
load(File.read(path))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
include Utils
|
17
|
+
|
18
|
+
# A hash of task schema describing individual tasks in a workflow. Tasks
|
19
|
+
# only require a class, but may contain configurations and even arguments
|
20
|
+
# for enque. Individual tasks may be a hash or an array. The tasks are
|
21
|
+
# resolved if they take one of these forms:
|
22
|
+
#
|
23
|
+
# tasks:
|
24
|
+
# key: {:class: TaskClass, ...}
|
25
|
+
# key: [TaskClass, ...]
|
26
|
+
#
|
27
|
+
attr_reader :tasks
|
28
|
+
|
29
|
+
# An array of join schema that describe how to join tasks together. Joins
|
30
|
+
# have arrays of inputs and outputs that reference task keys. Individual
|
31
|
+
# joins may be a hash or an array. The joins are resolved if they take
|
32
|
+
# one of these forms:
|
33
|
+
#
|
34
|
+
# joins:
|
35
|
+
# - [[inputs], [outputs], {:class: JoinClass, ...}]
|
36
|
+
# - [[inputs], [outputs], [JoinClass, ...]]
|
37
|
+
#
|
38
|
+
attr_reader :joins
|
39
|
+
|
40
|
+
# An array of [key, [args]] data that indicates the tasks and arguments
|
41
|
+
# to be added to an application during build. If args are not specified,
|
42
|
+
# then the arguments specified in the task schema are used.
|
43
|
+
#
|
44
|
+
# queue:
|
45
|
+
# - key # uses tasks[key] arguments
|
46
|
+
# - [key, [1, 2, 3]] # enques tasks[key] with [1, 2, 3]
|
47
|
+
#
|
48
|
+
attr_reader :queue
|
49
|
+
|
50
|
+
# An array of middleware to build onto the app.
|
51
|
+
attr_reader :middleware
|
52
|
+
|
53
|
+
def initialize(schema={})
|
54
|
+
@tasks = hashify(schema['tasks'] || {})
|
55
|
+
|
56
|
+
@joins = dehashify(schema['joins'] || []).collect do |join|
|
57
|
+
inputs, outputs, join = dehashify(join)
|
58
|
+
[inputs, outputs, join]
|
59
|
+
end
|
60
|
+
|
61
|
+
@queue = dehashify(schema['queue'] || []).collect do |queue|
|
62
|
+
dehashify(queue)
|
63
|
+
end
|
64
|
+
|
65
|
+
@middleware = dehashify(schema['middleware'] || [])
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolve!
|
69
|
+
tasks.dup.each_pair do |key, task|
|
70
|
+
task ||= {}
|
71
|
+
tasks[key] = resolve(task) do |id, data|
|
72
|
+
yield(:task, id || key, data)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
joins.collect! do |inputs, outputs, join|
|
77
|
+
join ||= {}
|
78
|
+
join = resolve(join) do |id, data|
|
79
|
+
yield(:join, id || 'join', data)
|
80
|
+
end
|
81
|
+
[inputs, outputs, join]
|
82
|
+
end
|
83
|
+
|
84
|
+
middleware.collect! do |m|
|
85
|
+
resolve(m) do |id, data|
|
86
|
+
yield(:middleware, id, data)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate!
|
94
|
+
errors = []
|
95
|
+
tasks.each do |key, task|
|
96
|
+
unless resolved?(task)
|
97
|
+
errors << "unknown task: #{task}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
joins.each do |inputs, outputs, join|
|
102
|
+
unless resolved?(join)
|
103
|
+
errors << "unknown join: #{join}"
|
104
|
+
end
|
105
|
+
|
106
|
+
inputs.each do |key|
|
107
|
+
unless tasks.has_key?(key)
|
108
|
+
errors << "missing join input: #{key}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
outputs.each do |key|
|
113
|
+
unless tasks.has_key?(key)
|
114
|
+
errors << "missing join output: #{key}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# queue.each do |(key, args)|
|
120
|
+
# unless tasks.has_key?(key)
|
121
|
+
# errors << "missing task: #{key}"
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
|
125
|
+
middleware.each do |m|
|
126
|
+
unless resolved?(m)
|
127
|
+
errors << "unknown middleware: #{m}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
unless errors.empty?
|
132
|
+
prefix = if errors.length > 1
|
133
|
+
"#{errors.length} build errors\n"
|
134
|
+
else
|
135
|
+
""
|
136
|
+
end
|
137
|
+
|
138
|
+
raise "#{prefix}#{errors.join("\n")}\n"
|
139
|
+
end
|
140
|
+
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def cleanup!
|
145
|
+
joins.delete_if do |inputs, outputs, join|
|
146
|
+
|
147
|
+
# remove missing inputs
|
148
|
+
inputs.delete_if {|key| !tasks.has_key?(key) }
|
149
|
+
|
150
|
+
# remove missing outputs
|
151
|
+
outputs.delete_if {|key| !tasks.has_key?(key) }
|
152
|
+
|
153
|
+
# remove orphan joins
|
154
|
+
inputs.empty? || outputs.empty?
|
155
|
+
end
|
156
|
+
|
157
|
+
# remove inputs without a task
|
158
|
+
queue.delete_if do |(key, inputs)|
|
159
|
+
!tasks.has_key?(key)
|
160
|
+
end
|
161
|
+
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
def scrub!
|
166
|
+
tasks.each do |key, task|
|
167
|
+
yield(task)
|
168
|
+
end
|
169
|
+
|
170
|
+
joins.each do |inputs, outputs, join|
|
171
|
+
yield(join)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def build(app)
|
176
|
+
# instantiate tasks
|
177
|
+
tasks = {}
|
178
|
+
arguments = {}
|
179
|
+
self.tasks.each_pair do |key, task|
|
180
|
+
instance, args = instantiate(task, app)
|
181
|
+
|
182
|
+
tasks[key] = instance
|
183
|
+
arguments[key] = args
|
184
|
+
end
|
185
|
+
|
186
|
+
# build the workflow
|
187
|
+
self.joins.each do |inputs, outputs, join|
|
188
|
+
inputs = inputs.collect {|key| tasks[key] }
|
189
|
+
outputs = outputs.collect {|key| tasks[key] }
|
190
|
+
instantiate(join, app).join(inputs, outputs)
|
191
|
+
end
|
192
|
+
|
193
|
+
# utilize middleware
|
194
|
+
self.middleware.each do |middleware|
|
195
|
+
instantiate(middleware, app)
|
196
|
+
end
|
197
|
+
|
198
|
+
# enque tasks
|
199
|
+
queue.each do |(key, inputs)|
|
200
|
+
unless inputs
|
201
|
+
inputs = arguments[key]
|
202
|
+
end
|
203
|
+
|
204
|
+
app.enq(tasks[key], *inputs) if inputs
|
205
|
+
end
|
206
|
+
|
207
|
+
tasks
|
208
|
+
end
|
209
|
+
|
210
|
+
def traverse
|
211
|
+
map = {}
|
212
|
+
self.tasks.each_pair do |key, task|
|
213
|
+
map[key] = [[],[]]
|
214
|
+
end
|
215
|
+
|
216
|
+
index = 0
|
217
|
+
self.joins.each do |inputs, outputs, join|
|
218
|
+
inputs.each do |key|
|
219
|
+
map[key][1] << index
|
220
|
+
end
|
221
|
+
|
222
|
+
outputs.each do |key|
|
223
|
+
map[key][0] << index
|
224
|
+
end
|
225
|
+
|
226
|
+
index += 1
|
227
|
+
end
|
228
|
+
|
229
|
+
map.keys.sort.collect do |key|
|
230
|
+
[key, *map[key]]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Creates an hash dump of self.
|
235
|
+
def to_hash
|
236
|
+
{ 'tasks' => tasks,
|
237
|
+
'joins' => joins,
|
238
|
+
'queue' => queue,
|
239
|
+
'middleware' => middleware
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
# Converts self to a hash and serializes it to YAML.
|
244
|
+
def dump(io=nil)
|
245
|
+
YAML.dump(to_hash, io)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|