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/root.rb
CHANGED
@@ -1,268 +1,191 @@
|
|
1
|
-
|
2
|
-
require 'tap/root/utils'
|
1
|
+
autoload(:FileUtils, 'fileutils')
|
3
2
|
|
4
3
|
module Tap
|
5
|
-
|
6
|
-
# Root abstracts a directory to standardize access to resources organized
|
7
|
-
# within variable directory structures.
|
8
|
-
#
|
9
|
-
# # define a root directory with aliased relative paths
|
10
|
-
# root = Root.new(
|
11
|
-
# :root => '/root_dir',
|
12
|
-
# :relative_paths => {:input => 'in', :output => 'out'})
|
13
|
-
#
|
14
|
-
# # access aliased paths
|
15
|
-
# root[:input] # => '/root_dir/in'
|
16
|
-
# root[:output] # => '/root_dir/out'
|
17
|
-
# root['implicit'] # => '/root_dir/implicit'
|
18
|
-
#
|
19
|
-
# # absolute paths can also be aliased
|
20
|
-
# root[:abs, true] = "/absolute/path"
|
21
|
-
# root.path(:abs, "to", "file.txt") # => '/absolute/path/to/file.txt'
|
22
|
-
#
|
23
|
-
# # expanded paths are returned unchanged
|
24
|
-
# path = File.expand_path('expanded')
|
25
|
-
# root[path] # => path
|
26
|
-
#
|
27
|
-
# # work with paths
|
28
|
-
# path = root.path(:input, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
|
29
|
-
# root.relative_path(:input, path) # => 'path/to/file.txt'
|
30
|
-
# root.translate(path, :input, :output) # => '/root_dir/out/path/to/file.txt'
|
31
|
-
#
|
32
|
-
# By default, Roots are initialized to the present working directory
|
33
|
-
# (Dir.pwd).
|
34
|
-
#
|
35
|
-
# === Implementation Notes
|
36
|
-
#
|
37
|
-
# Internally Root expands and stores all aliased paths in the 'paths' hash.
|
38
|
-
# Expanding paths ensures they remain constant even when the present working
|
39
|
-
# directory (Dir.pwd) changes.
|
40
|
-
#
|
41
|
-
# Root keeps a separate 'relative_paths' hash mapping aliases to their
|
42
|
-
# relative paths. This hash allow reassignment if and when the root directory
|
43
|
-
# changes. By contrast, there is no separate data structure storing the
|
44
|
-
# absolute paths. An absolute path thus has an alias in 'paths' but not
|
45
|
-
# 'relative_paths', whereas relative paths have aliases in both.
|
46
|
-
#
|
47
|
-
# These features may be important to note when subclassing Root:
|
48
|
-
# - root and all paths in 'paths' are expanded
|
49
|
-
# - relative paths are stored in 'relative_paths'
|
50
|
-
# - absolute paths are present in 'paths' but not in 'relative_paths'
|
51
|
-
#
|
52
4
|
class Root
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
attr_reader :path_root
|
73
|
-
|
74
|
-
# Creates a new Root from the specified configurations. A directory may be
|
75
|
-
# provided instead of a configuration hash; in that case no aliased relative
|
76
|
-
# or absolute paths are specified. By default root is the present working
|
77
|
-
# directory.
|
78
|
-
def initialize(config_or_dir=Dir.pwd)
|
79
|
-
# root, relative_paths, and absolute_paths are assigned manually as
|
80
|
-
# an optimization (otherwise assign_paths would get called once for
|
81
|
-
# each configuration)
|
82
|
-
if config_or_dir.kind_of?(String)
|
83
|
-
assign_paths(config_or_dir, {}, {})
|
84
|
-
config_or_dir = {}
|
85
|
-
else
|
86
|
-
root = config_or_dir.delete(:root) || Dir.pwd
|
87
|
-
relative_paths = config_or_dir.delete(:relative_paths) || {}
|
88
|
-
absolute_paths = config_or_dir.delete(:absolute_paths) || {}
|
89
|
-
assign_paths(root, relative_paths, absolute_paths)
|
5
|
+
class << self
|
6
|
+
# The path root type indicating windows, *nix, or some unknown style of
|
7
|
+
# paths (:win, :nix, :unknown).
|
8
|
+
def type
|
9
|
+
@path_root_type ||= begin
|
10
|
+
pwd = File.expand_path('.')
|
11
|
+
|
12
|
+
case
|
13
|
+
when pwd =~ WIN_ROOT_PATTERN then :win
|
14
|
+
when pwd[0] == ?/ then :nix
|
15
|
+
else :unknown
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Trivial indicates when a path does not have content to load. Returns
|
21
|
+
# true if the file at path is empty, non-existant, a directory, or nil.
|
22
|
+
def trivial?(path)
|
23
|
+
path == nil || !File.file?(path) || File.size(path) == 0
|
90
24
|
end
|
91
25
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
def root=(path)
|
97
|
-
assign_paths(path, relative_paths, absolute_paths)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Sets the relative_paths to those provided. 'root' and :root are reserved
|
101
|
-
# aliases and cannot be set using this method (use root= instead).
|
102
|
-
#
|
103
|
-
# r = Root.new
|
104
|
-
# r['alt'] # => File.join(r.root, 'alt')
|
105
|
-
# r.relative_paths = {'alt' => 'dir'}
|
106
|
-
# r['alt'] # => File.join(r.root, 'dir')
|
107
|
-
#
|
108
|
-
def relative_paths=(paths)
|
109
|
-
paths = Validation::HASH[paths]
|
110
|
-
assign_paths(root, paths, absolute_paths)
|
26
|
+
# Empty returns true when dir is an existing directory that has no files.
|
27
|
+
def empty?(dir)
|
28
|
+
File.directory?(dir) && (Dir.entries(dir) - ['.', '..']).empty?
|
29
|
+
end
|
111
30
|
end
|
112
|
-
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
# r.absolute_paths = {'abs' => '/path/to/dir'}
|
119
|
-
# r['abs'] # => '/path/to/dir'
|
120
|
-
#
|
121
|
-
def absolute_paths=(paths)
|
122
|
-
paths = Validation::HASH[paths]
|
123
|
-
assign_paths(root, relative_paths, paths)
|
31
|
+
|
32
|
+
# Regexp to match a windows-style root path.
|
33
|
+
WIN_ROOT_PATTERN = /\A[A-z]:\//
|
34
|
+
|
35
|
+
def initialize(path=Dir.pwd, dir=Dir.pwd)
|
36
|
+
@path_root = File.expand_path(path.to_s, dir.to_s)
|
124
37
|
end
|
125
|
-
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
unless relative_paths.include?(als) || als.to_s == 'root'
|
131
|
-
abs_paths[als] = path
|
132
|
-
end
|
133
|
-
end
|
134
|
-
abs_paths
|
38
|
+
|
39
|
+
# Stringifies and expands the path relative to self. Paths are turned
|
40
|
+
# into strings using to_s.
|
41
|
+
def expand(path)
|
42
|
+
File.expand_path(path.to_s, @path_root)
|
135
43
|
end
|
136
|
-
|
137
|
-
# Sets an alias for the path relative to the root directory. The aliases
|
138
|
-
# 'root' and :root cannot be set with this method (use root= instead).
|
139
|
-
# Absolute paths can be set using the second syntax.
|
140
|
-
#
|
141
|
-
# r = Root.new '/root_dir'
|
142
|
-
# r[:dir] = 'path/to/dir'
|
143
|
-
# r[:dir] # => '/root_dir/path/to/dir'
|
144
|
-
#
|
145
|
-
# r[:abs, true] = '/abs/path/to/dir'
|
146
|
-
# r[:abs] # => '/abs/path/to/dir'
|
147
|
-
#
|
148
|
-
#--
|
149
|
-
# Implementation Note:
|
150
|
-
#
|
151
|
-
# The syntax for setting an absolute path requires an odd use []=.
|
152
|
-
# In fact the method receives the arguments (:dir, true, '/abs/path/to/dir')
|
153
|
-
# rather than (:dir, '/abs/path/to/dir', true) meaning that internally path
|
154
|
-
# and absolute are switched when setting an absolute path.
|
155
|
-
#
|
156
|
-
def []=(als, path, absolute=false)
|
157
|
-
raise ArgumentError, "the alias #{als.inspect} is reserved" if als.to_s == 'root'
|
158
|
-
|
159
|
-
# switch the paths if absolute was provided
|
160
|
-
unless absolute == false
|
161
|
-
path, absolute = absolute, path
|
162
|
-
end
|
163
44
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
@relative_paths.delete(als)
|
170
|
-
@paths[als] = File.expand_path(path)
|
171
|
-
else
|
172
|
-
@relative_paths[als] = path
|
173
|
-
@paths[als] = File.expand_path(File.join(root, path))
|
174
|
-
end
|
45
|
+
# Joins and expands the path segments relative to self. Segments are
|
46
|
+
# turned to strings using to_s.
|
47
|
+
def path(*segments)
|
48
|
+
segments.collect! {|seg| seg.to_s }
|
49
|
+
expand(File.join(*segments))
|
175
50
|
end
|
176
|
-
|
177
|
-
# Returns the expanded path for the specified alias. If the alias has not
|
178
|
-
# been set, then the path is inferred to be 'root/als'. Expanded paths
|
179
|
-
# are returned directly.
|
180
|
-
#
|
181
|
-
# r = Root.new '/root_dir', :dir => 'path/to/dir'
|
182
|
-
# r[:dir] # => '/root_dir/path/to/dir'
|
183
|
-
#
|
184
|
-
# r.path_root # => '/'
|
185
|
-
# r['relative/path'] # => '/root_dir/relative/path'
|
186
|
-
# r['/expanded/path'] # => '/expanded/path'
|
187
|
-
#
|
188
|
-
def [](als)
|
189
|
-
path = self.paths[als]
|
190
|
-
return path unless path == nil
|
191
51
|
|
192
|
-
|
193
|
-
|
52
|
+
# Returns true if the expanded path is relative to self.
|
53
|
+
def relative?(path)
|
54
|
+
expand(path).rindex(@path_root, 0) == 0
|
194
55
|
end
|
195
56
|
|
196
|
-
#
|
197
|
-
#
|
198
|
-
def path
|
199
|
-
|
200
|
-
|
57
|
+
# Returns the part of the expanded path relative to self, or nil if the
|
58
|
+
# path is not relative to self.
|
59
|
+
def relative_path(path)
|
60
|
+
path = expand(path)
|
61
|
+
return nil unless relative?(path)
|
201
62
|
|
202
|
-
|
203
|
-
|
204
|
-
|
63
|
+
# if path_root_length > path.length then the first arg
|
64
|
+
# returns nil, and an empty string is returned
|
65
|
+
path[path_root_length, path.length - path_root_length] || ""
|
205
66
|
end
|
67
|
+
alias rp relative_path
|
206
68
|
|
207
|
-
# Returns
|
208
|
-
def
|
209
|
-
|
69
|
+
# Returns a new Root for the path, relative to self.
|
70
|
+
def root(path)
|
71
|
+
Root.new(path, self)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a new Root for the path, relative to self. Same as root but
|
75
|
+
# raises an error if the path is not relative to self.
|
76
|
+
def sub(path)
|
77
|
+
sub = root(path)
|
78
|
+
unless relative?(sub)
|
79
|
+
raise ArgumentError, "not a sub path: #{sub} (#{self})"
|
80
|
+
end
|
81
|
+
sub
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a new Root for the parent directory for self.
|
85
|
+
def parent
|
86
|
+
root File.dirname(@path_root)
|
210
87
|
end
|
211
88
|
|
212
|
-
#
|
213
|
-
#
|
214
|
-
#
|
215
|
-
# r = Root.new '/root_dir'
|
216
|
-
# path = r.path(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
|
217
|
-
# r.translate(path, :in, :out) # => '/root_dir/out/path/to/file.txt'
|
89
|
+
# Returns the expanded path, exchanging the extension with extname.
|
90
|
+
# Extname may optionally omit the leading period.
|
218
91
|
#
|
219
|
-
|
220
|
-
|
92
|
+
# root = Root.new("/root")
|
93
|
+
# root.exchange('path/to/file.txt', '.html') # => '/root/path/to/file.html'
|
94
|
+
# root.exchange('path/to/file.txt', 'rb') # => '/root/path/to/file.rb'
|
95
|
+
#
|
96
|
+
def exchange(path, extname)
|
97
|
+
path = expand(path)
|
98
|
+
"#{path.chomp(File.extname(path))}#{extname[0] == ?. ? '' : '.'}#{extname}"
|
221
99
|
end
|
222
|
-
|
223
|
-
|
224
|
-
#
|
225
|
-
#
|
226
|
-
|
227
|
-
|
100
|
+
alias ex exchange
|
101
|
+
|
102
|
+
# Generates a path translated from the source to the target. Raises an
|
103
|
+
# error if path is not relative to the source.
|
104
|
+
def translate(path, source, target)
|
105
|
+
path = expand(path)
|
106
|
+
source = root(source)
|
107
|
+
target = root(target)
|
108
|
+
|
109
|
+
rp = source.relative_path(path)
|
110
|
+
if rp.nil?
|
111
|
+
raise ArgumentError, "\n#{path}\nis not relative to:\n#{source}"
|
112
|
+
end
|
113
|
+
|
114
|
+
target.path(rp)
|
115
|
+
end
|
116
|
+
alias tr translate
|
117
|
+
|
118
|
+
# Globs for unique paths matching the input patterns expanded relative to
|
119
|
+
# self. If no patterns are specified, glob returns all paths matching
|
120
|
+
# './**/*'.
|
121
|
+
def glob(*patterns)
|
228
122
|
patterns << "**/*" if patterns.empty?
|
229
|
-
patterns.collect! {|pattern|
|
230
|
-
|
123
|
+
patterns.collect! {|pattern| expand(pattern) }
|
124
|
+
Dir[*patterns].uniq
|
231
125
|
end
|
232
|
-
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
|
237
|
-
|
126
|
+
|
127
|
+
# Changes to the specified directory using Dir.chdir, keeping the same
|
128
|
+
# block semantics as that method. The directory will be created if
|
129
|
+
# necessary and mkdir is specified. Raises an error for non-existant
|
130
|
+
# directories, as well as non-directory inputs.
|
131
|
+
def chdir(dir, mkdir=false, &block)
|
132
|
+
dir = expand(dir)
|
133
|
+
|
134
|
+
unless File.directory?(dir)
|
135
|
+
if !File.exists?(dir) && mkdir
|
136
|
+
FileUtils.mkdir_p(dir)
|
137
|
+
else
|
138
|
+
raise ArgumentError, "not a directory: #{dir}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
Dir.chdir(dir, &block)
|
238
143
|
end
|
239
|
-
|
240
|
-
#
|
241
|
-
def
|
242
|
-
|
144
|
+
|
145
|
+
# Makes the specified directory and parent directories (as required).
|
146
|
+
def mkdir(*path)
|
147
|
+
path = self.path(*path)
|
148
|
+
FileUtils.mkdir_p(path) unless File.directory?(path)
|
149
|
+
path
|
243
150
|
end
|
244
|
-
|
245
|
-
#
|
246
|
-
#
|
247
|
-
def
|
248
|
-
|
151
|
+
|
152
|
+
# Opens the path in the specified mode, using the same semantics as
|
153
|
+
# File.open.
|
154
|
+
def open(path, mode='r', &block)
|
155
|
+
path = expand(path)
|
156
|
+
File.open(path, mode, &block)
|
249
157
|
end
|
250
|
-
|
158
|
+
|
159
|
+
# Prepares a file at the path by making paths's parent directory. The file
|
160
|
+
# is opened in the mode and passed to the block, if given. The mode is
|
161
|
+
# ignored if no block is given.
|
162
|
+
#
|
163
|
+
# Returns path.
|
164
|
+
def prepare(*path)
|
165
|
+
path = self.path(*path)
|
166
|
+
dirname = File.dirname(path)
|
167
|
+
FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
|
168
|
+
File.open(path, 'w') {|io| yield(io) } if block_given?
|
169
|
+
path
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns path.
|
173
|
+
def to_s
|
174
|
+
@path_root
|
175
|
+
end
|
176
|
+
|
251
177
|
private
|
252
|
-
|
253
|
-
#
|
254
|
-
|
255
|
-
|
256
|
-
@
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
178
|
+
|
179
|
+
# helper to memoize and return the length of path root, plus a trailing
|
180
|
+
# separator; used in determining relative paths
|
181
|
+
def path_root_length # :nodoc:
|
182
|
+
@path_root_length ||= begin
|
183
|
+
length = @path_root.length
|
184
|
+
unless @path_root == File::SEPARATOR
|
185
|
+
length += File::SEPARATOR.length
|
186
|
+
end
|
187
|
+
length
|
262
188
|
end
|
263
|
-
|
264
|
-
relative_paths.each_pair {|dir, path| self[dir] = path }
|
265
|
-
absolute_paths.each_pair {|dir, path| self[dir, true] = path }
|
266
|
-
end
|
189
|
+
end
|
267
190
|
end
|
268
191
|
end
|
data/lib/tap/signal.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'tap/utils'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
# Signal attaches an object and allows a specific method to be triggered
|
5
|
+
# through a standard interface.
|
6
|
+
class Signal
|
7
|
+
class << self
|
8
|
+
# A description of self
|
9
|
+
attr_accessor :desc
|
10
|
+
end
|
11
|
+
|
12
|
+
# The object receiving signals through self.
|
13
|
+
attr_reader :obj
|
14
|
+
|
15
|
+
# An optional block, used at the signal's discretion (normally passed to
|
16
|
+
# the method the signal targets on obj).
|
17
|
+
attr_reader :block
|
18
|
+
|
19
|
+
def initialize(obj, &block)
|
20
|
+
@obj = obj
|
21
|
+
@block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
# Calls process with the input args and returns the result.
|
25
|
+
def call(args)
|
26
|
+
process(args)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Simply returns the input args.
|
30
|
+
def process(args)
|
31
|
+
args
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"#<#{self.class}:#{object_id}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def convert_to_array(obj, signature=[], options=false)
|
41
|
+
return obj if obj.kind_of?(Array)
|
42
|
+
|
43
|
+
argv = signature.collect {|key| obj[key] }
|
44
|
+
|
45
|
+
if options
|
46
|
+
opts = {}
|
47
|
+
(obj.keys - signature).each do |key|
|
48
|
+
opts[key] = obj[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
argv << opts
|
52
|
+
end
|
53
|
+
|
54
|
+
argv
|
55
|
+
end
|
56
|
+
|
57
|
+
def convert_to_hash(obj, signature=[], remainder=nil)
|
58
|
+
return obj if obj.kind_of?(Hash)
|
59
|
+
|
60
|
+
args, argh = obj, {}
|
61
|
+
signature.each do |key|
|
62
|
+
argh[key] = args.shift
|
63
|
+
end
|
64
|
+
|
65
|
+
if remainder
|
66
|
+
argh[remainder] = args
|
67
|
+
end
|
68
|
+
|
69
|
+
argh
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|