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.
Files changed (102) hide show
  1. data/History +34 -0
  2. data/README +62 -41
  3. data/bin/tap +36 -40
  4. data/cmd/console.rb +14 -6
  5. data/cmd/manifest.rb +62 -58
  6. data/cmd/run.rb +49 -31
  7. data/doc/API +84 -0
  8. data/doc/Class Reference +83 -115
  9. data/doc/Examples/Command Line +36 -0
  10. data/doc/Examples/Workflow +40 -0
  11. data/lib/tap/app.rb +293 -214
  12. data/lib/tap/app/node.rb +43 -0
  13. data/lib/tap/app/queue.rb +77 -0
  14. data/lib/tap/app/stack.rb +16 -0
  15. data/lib/tap/app/state.rb +22 -0
  16. data/lib/tap/constants.rb +2 -2
  17. data/lib/tap/env.rb +400 -314
  18. data/lib/tap/env/constant.rb +227 -0
  19. data/lib/tap/env/gems.rb +63 -0
  20. data/lib/tap/env/manifest.rb +89 -0
  21. data/lib/tap/env/minimap.rb +292 -0
  22. data/lib/tap/{support → env}/string_ext.rb +2 -2
  23. data/lib/tap/exe.rb +113 -125
  24. data/lib/tap/join.rb +175 -0
  25. data/lib/tap/joins.rb +9 -0
  26. data/lib/tap/joins/switch.rb +44 -0
  27. data/lib/tap/joins/sync.rb +99 -0
  28. data/lib/tap/root.rb +100 -491
  29. data/lib/tap/root/utils.rb +220 -0
  30. data/lib/tap/{support → root}/versions.rb +31 -29
  31. data/lib/tap/schema.rb +248 -0
  32. data/lib/tap/schema/parser.rb +413 -0
  33. data/lib/tap/schema/utils.rb +82 -0
  34. data/lib/tap/support/intern.rb +19 -6
  35. data/lib/tap/support/templater.rb +8 -3
  36. data/lib/tap/task.rb +175 -171
  37. data/lib/tap/tasks/dump.rb +58 -0
  38. data/lib/tap/tasks/load.rb +62 -0
  39. metadata +30 -73
  40. data/cmd/destroy.rb +0 -27
  41. data/cmd/generate.rb +0 -27
  42. data/doc/Command Reference +0 -105
  43. data/doc/Syntax Reference +0 -234
  44. data/doc/Tutorial +0 -348
  45. data/lib/tap/dump.rb +0 -142
  46. data/lib/tap/file_task.rb +0 -384
  47. data/lib/tap/generator/arguments.rb +0 -13
  48. data/lib/tap/generator/base.rb +0 -176
  49. data/lib/tap/generator/destroy.rb +0 -60
  50. data/lib/tap/generator/generate.rb +0 -93
  51. data/lib/tap/generator/generators/command/command_generator.rb +0 -21
  52. data/lib/tap/generator/generators/command/templates/command.erb +0 -32
  53. data/lib/tap/generator/generators/config/config_generator.rb +0 -98
  54. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
  55. data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
  56. data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
  57. data/lib/tap/generator/generators/root/root_generator.rb +0 -84
  58. data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
  59. data/lib/tap/generator/generators/root/templates/README +0 -14
  60. data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
  61. data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
  62. data/lib/tap/generator/generators/root/templates/gemspec +0 -27
  63. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
  64. data/lib/tap/generator/generators/task/task_generator.rb +0 -25
  65. data/lib/tap/generator/generators/task/templates/task.erb +0 -14
  66. data/lib/tap/generator/generators/task/templates/test.erb +0 -19
  67. data/lib/tap/generator/manifest.rb +0 -20
  68. data/lib/tap/generator/preview.rb +0 -69
  69. data/lib/tap/load.rb +0 -64
  70. data/lib/tap/spec.rb +0 -41
  71. data/lib/tap/support/aggregator.rb +0 -65
  72. data/lib/tap/support/audit.rb +0 -333
  73. data/lib/tap/support/constant.rb +0 -143
  74. data/lib/tap/support/constant_manifest.rb +0 -126
  75. data/lib/tap/support/dependencies.rb +0 -54
  76. data/lib/tap/support/dependency.rb +0 -44
  77. data/lib/tap/support/executable.rb +0 -198
  78. data/lib/tap/support/executable_queue.rb +0 -125
  79. data/lib/tap/support/gems.rb +0 -43
  80. data/lib/tap/support/join.rb +0 -144
  81. data/lib/tap/support/joins.rb +0 -12
  82. data/lib/tap/support/joins/switch.rb +0 -27
  83. data/lib/tap/support/joins/sync_merge.rb +0 -38
  84. data/lib/tap/support/manifest.rb +0 -171
  85. data/lib/tap/support/minimap.rb +0 -90
  86. data/lib/tap/support/node.rb +0 -176
  87. data/lib/tap/support/parser.rb +0 -450
  88. data/lib/tap/support/schema.rb +0 -385
  89. data/lib/tap/support/shell_utils.rb +0 -67
  90. data/lib/tap/test.rb +0 -77
  91. data/lib/tap/test/assertions.rb +0 -38
  92. data/lib/tap/test/env_vars.rb +0 -29
  93. data/lib/tap/test/extensions.rb +0 -73
  94. data/lib/tap/test/file_test.rb +0 -362
  95. data/lib/tap/test/file_test_class.rb +0 -15
  96. data/lib/tap/test/regexp_escape.rb +0 -87
  97. data/lib/tap/test/script_test.rb +0 -46
  98. data/lib/tap/test/script_tester.rb +0 -115
  99. data/lib/tap/test/subset_test.rb +0 -260
  100. data/lib/tap/test/subset_test_class.rb +0 -99
  101. data/lib/tap/test/tap_test.rb +0 -109
  102. 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
- module Support
3
-
2
+ class Root
4
3
  # Version provides methods for adding, removing, and incrementing versions
5
- # at the end of filepaths. Versions are all formatted like:
6
- # 'filepath-version.extension'.
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 filepath. Versioned filepaths follow the format:
11
- # 'path-version.extension'. If no version is specified, then the filepath
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 filepath by the specified increment.
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
- version.concat Array.new(increment.length - version.length, 0) if increment.length > version.length
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 version.
53
- # If no version is specified, then the returned version will be nil.
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
- # to an array of integers. Splits version string on "."
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