tap 0.19.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|