tap 0.12.4 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/tap/joins.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Tap
|
2
|
+
module Joins
|
3
|
+
|
4
|
+
# A Switch join allows a block to determine which output from an array
|
5
|
+
# of outputs will receive the results of the input.
|
6
|
+
#
|
7
|
+
#--
|
8
|
+
# Note that switch is NOT identified as a join that can be created from
|
9
|
+
# the command line. Switch inherently requires a block to select which
|
10
|
+
# output receives the input, and so cannot be loaded from data alone.
|
11
|
+
#
|
12
|
+
# Switch facilitates in-code switch joins.
|
13
|
+
class Switch < Join
|
14
|
+
|
15
|
+
# An object responding to call that return the index of the output
|
16
|
+
# to that receives the result.
|
17
|
+
attr_accessor :selector
|
18
|
+
|
19
|
+
def initialize(config={}, app=Tap::App.instance, &block)
|
20
|
+
super(config, app)
|
21
|
+
@selector = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def join(inputs, outputs, &block)
|
25
|
+
@selector = block
|
26
|
+
super(inputs, outputs)
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(result)
|
30
|
+
index = selector.call(result)
|
31
|
+
|
32
|
+
unless index && output = outputs[index]
|
33
|
+
raise SwitchError, "no switch target at index: #{index}"
|
34
|
+
end
|
35
|
+
|
36
|
+
dispatch(output, result)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Raised by a Switch join to indicate when a switch index is out of bounds.
|
40
|
+
class SwitchError < RuntimeError # :nodoc:
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Tap
|
2
|
+
module Joins
|
3
|
+
|
4
|
+
# :startdoc::join a synchronized multi-way join
|
5
|
+
#
|
6
|
+
# Sync works the same as Join, but passes the collected results of the
|
7
|
+
# inputs (ie an array) to the outputs. The results will not be passed
|
8
|
+
# until all of inputs have returned. A collision results if a single
|
9
|
+
# input completes twice before the group completes as a whole.
|
10
|
+
#
|
11
|
+
class Sync < Join
|
12
|
+
|
13
|
+
# NIL_VALUE is used to mark empty slots (nil itself cannot be used
|
14
|
+
# because it is a valid result value).
|
15
|
+
NIL_VALUE = Object.new
|
16
|
+
|
17
|
+
# An array holding results until the batch is ready to execute.
|
18
|
+
attr_reader :results
|
19
|
+
|
20
|
+
def initialize(config={}, app=Tap::App.instance)
|
21
|
+
super
|
22
|
+
@results = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Resets results. Normally there is no reason to call this method as it
|
26
|
+
# will shuffle the arguments being passed through self.
|
27
|
+
def reset
|
28
|
+
@results = inputs ? Array.new(inputs.length, NIL_VALUE) : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# A synchronized join sets a Callback as the join of each input. The
|
32
|
+
# callback is responsible for setting the result of each input into the
|
33
|
+
# correct 'results' slot.
|
34
|
+
def join(inputs, outputs)
|
35
|
+
@inputs.each do |input|
|
36
|
+
input.joins.delete(self)
|
37
|
+
end if @inputs
|
38
|
+
|
39
|
+
@inputs = inputs
|
40
|
+
|
41
|
+
index = 0
|
42
|
+
inputs.each do |input|
|
43
|
+
input.joins << Callback.new(self, index)
|
44
|
+
index += 1
|
45
|
+
end if inputs
|
46
|
+
reset
|
47
|
+
|
48
|
+
@outputs = outputs
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Call is called by a Callback and stores the result at the specified
|
53
|
+
# index in results. If the results have all been set, then they are
|
54
|
+
# sent to each output.
|
55
|
+
def call(result, index)
|
56
|
+
if result == NIL_VALUE
|
57
|
+
raise "NIL_VALUE cannot be passed as a result"
|
58
|
+
end
|
59
|
+
|
60
|
+
unless results[index] == NIL_VALUE
|
61
|
+
raise SynchronizeError, "already got a result for: #{inputs[index]}"
|
62
|
+
end
|
63
|
+
results[index] = result
|
64
|
+
|
65
|
+
unless results.include?(NIL_VALUE)
|
66
|
+
outputs.each {|output| dispatch(output, results) }
|
67
|
+
reset
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Callbacks are set as the join for each input to a Sync join, and
|
72
|
+
# allow the result of an input to be stored in the correct slot of
|
73
|
+
# the results array.
|
74
|
+
class Callback
|
75
|
+
|
76
|
+
# A backreference to the parent Sync join.
|
77
|
+
attr_reader :join
|
78
|
+
|
79
|
+
# The results index where result should be stored.
|
80
|
+
attr_reader :index
|
81
|
+
|
82
|
+
def initialize(join, index)
|
83
|
+
@join = join
|
84
|
+
@index = index
|
85
|
+
end
|
86
|
+
|
87
|
+
# Calls back to a Sync join to store the result at index.
|
88
|
+
def call(result)
|
89
|
+
join.call(result, index)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Raised by a Sync join to indicate when an input returns twice before
|
94
|
+
# the group is ready to execute.
|
95
|
+
class SynchronizeError < RuntimeError
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/tap/root.rb
CHANGED
@@ -1,42 +1,37 @@
|
|
1
1
|
require 'configurable'
|
2
|
-
require 'tap/
|
3
|
-
autoload(:FileUtils, 'fileutils')
|
2
|
+
require 'tap/root/utils'
|
4
3
|
|
5
4
|
module Tap
|
6
5
|
|
7
|
-
# Root
|
8
|
-
#
|
9
|
-
# full filepaths. Root also simplifies operations on filepaths.
|
6
|
+
# Root abstracts a directory to standardize access to resources organized
|
7
|
+
# within variable directory structures.
|
10
8
|
#
|
11
9
|
# # define a root directory with aliased relative paths
|
12
|
-
#
|
10
|
+
# root = Root.new(
|
11
|
+
# :root => '/root_dir',
|
12
|
+
# :relative_paths => {:input => 'in', :output => 'out'})
|
13
13
|
#
|
14
|
-
# #
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# # access aliased paths
|
15
|
+
# root[:input] # => '/root_dir/in'
|
16
|
+
# root[:output] # => '/root_dir/out'
|
17
|
+
# root['implicit'] # => '/root_dir/implicit'
|
18
18
|
#
|
19
|
-
# #
|
20
|
-
#
|
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'
|
21
22
|
#
|
22
|
-
# #
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# r.translate(fp, :input, :output) # => '/root_dir/out/path/to/file.txt'
|
26
|
-
#
|
27
|
-
# # version filepaths
|
28
|
-
# r.version('path/to/config.yml', 1.0) # => 'path/to/config-1.0.yml'
|
29
|
-
# r.increment('path/to/config-1.0.yml', 0.1) # => 'path/to/config-1.1.yml'
|
30
|
-
# r.deversion('path/to/config-1.1.yml') # => ['path/to/config.yml', "1.1"]
|
23
|
+
# # expanded paths are returned unchanged
|
24
|
+
# path = File.expand_path('expanded')
|
25
|
+
# root[path] # => path
|
31
26
|
#
|
32
|
-
# #
|
33
|
-
#
|
34
|
-
#
|
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'
|
35
31
|
#
|
36
32
|
# By default, Roots are initialized to the present working directory
|
37
33
|
# (Dir.pwd).
|
38
34
|
#
|
39
|
-
#--
|
40
35
|
# === Implementation Notes
|
41
36
|
#
|
42
37
|
# Internally Root expands and stores all aliased paths in the 'paths' hash.
|
@@ -50,436 +45,62 @@ module Tap
|
|
50
45
|
# 'relative_paths', whereas relative paths have aliases in both.
|
51
46
|
#
|
52
47
|
# These features may be important to note when subclassing Root:
|
53
|
-
# - root and all
|
48
|
+
# - root and all paths in 'paths' are expanded
|
54
49
|
# - relative paths are stored in 'relative_paths'
|
55
50
|
# - absolute paths are present in 'paths' but not in 'relative_paths'
|
56
51
|
#
|
57
52
|
class Root
|
58
|
-
# Regexp to match a windows-style root filepath.
|
59
|
-
WIN_ROOT_PATTERN = /^[A-z]:\//
|
60
|
-
|
61
|
-
class << self
|
62
|
-
include Support::Versions
|
63
|
-
|
64
|
-
# Returns the filepath of path relative to dir. Both dir and path are
|
65
|
-
# expanded before the relative filepath is determined. Returns nil if
|
66
|
-
# the path is not relative to dir.
|
67
|
-
#
|
68
|
-
# Root.relative_filepath('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
|
69
|
-
#
|
70
|
-
def relative_filepath(dir, path, dir_string=Dir.pwd)
|
71
|
-
expanded_dir = File.expand_path(dir, dir_string)
|
72
|
-
expanded_path = File.expand_path(path, dir_string)
|
73
|
-
|
74
|
-
return nil unless expanded_path.index(expanded_dir) == 0
|
75
|
-
|
76
|
-
# use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
|
77
|
-
# as in: relative_filepath('/path', '/path') then the first arg returns nil, and an
|
78
|
-
# empty string is returned
|
79
|
-
expanded_path[(expanded_dir.chomp("/").length + 1)..-1] || ""
|
80
|
-
end
|
81
|
-
|
82
|
-
# Generates a target filepath translated from the source_dir to the
|
83
|
-
# target_dir. Raises an error if the filepath is not relative to the
|
84
|
-
# source_dir.
|
85
|
-
#
|
86
|
-
# Root.translate("/path/to/file.txt", "/path", "/another/path") # => '/another/path/to/file.txt'
|
87
|
-
#
|
88
|
-
def translate(path, source_dir, target_dir)
|
89
|
-
unless relative_path = relative_filepath(source_dir, path)
|
90
|
-
raise ArgumentError, "\n#{path}\nis not relative to:\n#{source_dir}"
|
91
|
-
end
|
92
|
-
File.join(target_dir, relative_path)
|
93
|
-
end
|
94
|
-
|
95
|
-
# Returns the path, exchanging the extension with extname. Extname may
|
96
|
-
# optionally omit the leading period.
|
97
|
-
#
|
98
|
-
# Root.exchange('path/to/file.txt', '.html') # => 'path/to/file.html'
|
99
|
-
# Root.exchange('path/to/file.txt', 'rb') # => 'path/to/file.rb'
|
100
|
-
#
|
101
|
-
def exchange(path, extname)
|
102
|
-
"#{path.chomp(File.extname(path))}#{extname[0] == ?. ? '' : '.'}#{extname}"
|
103
|
-
end
|
104
|
-
|
105
|
-
# Lists all unique paths matching the input glob patterns.
|
106
|
-
def glob(*patterns)
|
107
|
-
patterns.collect do |pattern|
|
108
|
-
Dir.glob(pattern)
|
109
|
-
end.flatten.uniq
|
110
|
-
end
|
111
|
-
|
112
|
-
# Lists all unique versions of path matching the glob version patterns. If
|
113
|
-
# no patterns are specified, then all versions of path will be returned.
|
114
|
-
def vglob(path, *vpatterns)
|
115
|
-
vpatterns << "*" if vpatterns.empty?
|
116
|
-
vpatterns.collect do |vpattern|
|
117
|
-
results = Dir.glob(version(path, vpattern))
|
118
|
-
|
119
|
-
# extra work to include the default version path for any version
|
120
|
-
results << path if vpattern == "*" && File.exists?(path)
|
121
|
-
results
|
122
|
-
end.flatten.uniq
|
123
|
-
end
|
124
|
-
|
125
|
-
# Path suffix glob. Globs along the base paths for paths that match the
|
126
|
-
# specified suffix pattern.
|
127
|
-
def sglob(suffix_pattern, *base_paths)
|
128
|
-
base_paths.collect do |base|
|
129
|
-
base = File.expand_path(base)
|
130
|
-
Dir.glob(File.join(base, suffix_pattern))
|
131
|
-
end.flatten.uniq
|
132
|
-
end
|
133
|
-
|
134
|
-
# Like Dir.chdir but makes the directory, if necessary, when mkdir is
|
135
|
-
# specified. chdir raises an error for non-existant directories, as well
|
136
|
-
# as non-directory inputs.
|
137
|
-
def chdir(dir, mkdir=false, &block)
|
138
|
-
dir = File.expand_path(dir)
|
139
|
-
|
140
|
-
unless File.directory?(dir)
|
141
|
-
if !File.exists?(dir) && mkdir
|
142
|
-
FileUtils.mkdir_p(dir)
|
143
|
-
else
|
144
|
-
raise ArgumentError, "not a directory: #{dir}"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
Dir.chdir(dir, &block)
|
149
|
-
end
|
150
|
-
|
151
|
-
# Prepares the input path by making the parent directory for path. If a
|
152
|
-
# block is given, a file is created at path and passed to it; in this
|
153
|
-
# way files with non-existant parent directories are readily made.
|
154
|
-
#
|
155
|
-
# Returns path.
|
156
|
-
def prepare(path, &block)
|
157
|
-
dirname = File.dirname(path)
|
158
|
-
FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
|
159
|
-
File.open(path, "w", &block) if block_given?
|
160
|
-
path
|
161
|
-
end
|
162
|
-
|
163
|
-
# The path root type indicating windows, *nix, or some unknown style of
|
164
|
-
# filepaths (:win, :nix, :unknown).
|
165
|
-
def path_root_type
|
166
|
-
@path_root_type ||= case
|
167
|
-
when RUBY_PLATFORM =~ /mswin/ && File.expand_path(".") =~ WIN_ROOT_PATTERN then :win
|
168
|
-
when File.expand_path(".")[0] == ?/ then :nix
|
169
|
-
else :unknown
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
# Returns true if the input path appears to be an expanded path, based on
|
174
|
-
# Root.path_root_type.
|
175
|
-
#
|
176
|
-
# If root_type == :win returns true if the path matches WIN_ROOT_PATTERN.
|
177
|
-
#
|
178
|
-
# Root.expanded?('C:/path') # => true
|
179
|
-
# Root.expanded?('c:/path') # => true
|
180
|
-
# Root.expanded?('D:/path') # => true
|
181
|
-
# Root.expanded?('path') # => false
|
182
|
-
#
|
183
|
-
# If root_type == :nix, then expanded? returns true if the path begins
|
184
|
-
# with '/'.
|
185
|
-
#
|
186
|
-
# Root.expanded?('/path') # => true
|
187
|
-
# Root.expanded?('path') # => false
|
188
|
-
#
|
189
|
-
# Otherwise expanded? always returns nil.
|
190
|
-
def expanded?(path, root_type=path_root_type)
|
191
|
-
case root_type
|
192
|
-
when :win
|
193
|
-
path =~ WIN_ROOT_PATTERN ? true : false
|
194
|
-
when :nix
|
195
|
-
path[0] == ?/
|
196
|
-
else
|
197
|
-
nil
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
# Trivial indicates when a path does not have content to load. Returns
|
202
|
-
# true if the file at path is empty, non-existant, a directory, or nil.
|
203
|
-
def trivial?(path)
|
204
|
-
path == nil || !File.file?(path) || File.size(path) == 0
|
205
|
-
end
|
206
|
-
|
207
|
-
# Empty returns true when dir is an existing directory that has no files.
|
208
|
-
def empty?(dir)
|
209
|
-
File.directory?(dir) && (Dir.entries(dir) - ['.', '..']).empty?
|
210
|
-
end
|
211
|
-
|
212
|
-
# Minimizes a set of paths to the set of shortest basepaths that unqiuely
|
213
|
-
# identify the paths. The path extension and versions are removed from
|
214
|
-
# the basepath if possible. For example:
|
215
|
-
#
|
216
|
-
# Tap::Root.minimize ['path/to/a.rb', 'path/to/b.rb']
|
217
|
-
# # => ['a', 'b']
|
218
|
-
#
|
219
|
-
# Tap::Root.minimize ['path/to/a-0.1.0.rb', 'path/to/b-0.1.0.rb']
|
220
|
-
# # => ['a', 'b']
|
221
|
-
#
|
222
|
-
# Tap::Root.minimize ['path/to/file.rb', 'path/to/file.txt']
|
223
|
-
# # => ['file.rb', 'file.txt']
|
224
|
-
#
|
225
|
-
# Tap::Root.minimize ['path-0.1/to/file.rb', 'path-0.2/to/file.rb']
|
226
|
-
# # => ['path-0.1/to/file', 'path-0.2/to/file']
|
227
|
-
#
|
228
|
-
# Minimized paths that carry their extension will always carry
|
229
|
-
# their version as well, but the converse is not true; paths
|
230
|
-
# can be minimized to carry just the version and not the path
|
231
|
-
# extension.
|
232
|
-
#
|
233
|
-
# Tap::Root.minimize ['path/to/a-0.1.0.rb', 'path/to/a-0.1.0.txt']
|
234
|
-
# # => ['a-0.1.0.rb', 'a-0.1.0.txt']
|
235
|
-
#
|
236
|
-
# Tap::Root.minimize ['path/to/a-0.1.0.rb', 'path/to/a-0.2.0.rb']
|
237
|
-
# # => ['a-0.1.0', 'a-0.2.0']
|
238
|
-
#
|
239
|
-
# If a block is given, each (path, mini-path) pair will be passed
|
240
|
-
# to it after minimization.
|
241
|
-
def minimize(paths) # :yields: path, mini_path
|
242
|
-
unless block_given?
|
243
|
-
mini_paths = []
|
244
|
-
minimize(paths) {|p, mp| mini_paths << mp }
|
245
|
-
return mini_paths
|
246
|
-
end
|
247
|
-
|
248
|
-
splits = paths.uniq.collect do |path|
|
249
|
-
extname = File.extname(path)
|
250
|
-
extname = '' if extname =~ /^\.\d+$/
|
251
|
-
base = File.basename(path.chomp(extname))
|
252
|
-
version = base =~ /(-\d+(\.\d+)*)$/ ? $1 : ''
|
253
|
-
|
254
|
-
[dirname_or_array(path), base.chomp(version), extname, version, false, path]
|
255
|
-
end
|
256
|
-
|
257
|
-
while !splits.empty?
|
258
|
-
index = 0
|
259
|
-
splits = splits.collect do |(dir, base, extname, version, flagged, path)|
|
260
|
-
index += 1
|
261
|
-
case
|
262
|
-
when !flagged && just_one?(splits, index, base)
|
263
|
-
|
264
|
-
# found just one
|
265
|
-
yield(path, base)
|
266
|
-
nil
|
267
|
-
when dir.kind_of?(Array)
|
268
|
-
|
269
|
-
# no more path segments to use, try to add
|
270
|
-
# back version and extname
|
271
|
-
if dir.empty?
|
272
|
-
dir << File.dirname(base)
|
273
|
-
base = File.basename(base)
|
274
|
-
end
|
275
|
-
|
276
|
-
case
|
277
|
-
when !version.empty?
|
278
|
-
# add back version (occurs first)
|
279
|
-
[dir, "#{base}#{version}", extname, '', false, path]
|
280
|
-
|
281
|
-
when !extname.empty?
|
282
|
-
|
283
|
-
# add back extension (occurs second)
|
284
|
-
[dir, "#{base}#{extname}", '', version, false, path]
|
285
|
-
else
|
286
|
-
|
287
|
-
# nothing more to distinguish... path is minimized (occurs third)
|
288
|
-
yield(path, min_join(dir[0], base))
|
289
|
-
nil
|
290
|
-
end
|
291
|
-
else
|
292
|
-
|
293
|
-
# shift path segment. dirname_or_array returns an
|
294
|
-
# array if this is the last path segment to shift.
|
295
|
-
[dirname_or_array(dir), min_join(File.basename(dir), base), extname, version, false, path]
|
296
|
-
end
|
297
|
-
end.compact
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
# Returns true if the mini_path matches path. Matching logic reverses
|
302
|
-
# that of minimize:
|
303
|
-
#
|
304
|
-
# * a match occurs when path ends with mini_path
|
305
|
-
# * if mini_path doesn't specify an extension, then mini_path
|
306
|
-
# must only match path up to the path extension
|
307
|
-
# * if mini_path doesn't specify a version, then mini_path
|
308
|
-
# must only match path up to the path basename (minus the
|
309
|
-
# version and extname)
|
310
|
-
#
|
311
|
-
# For example:
|
312
|
-
#
|
313
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file') # => true
|
314
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'dir/file') # => true
|
315
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file-0.1.0') # => true
|
316
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file-0.1.0.rb') # => true
|
317
|
-
#
|
318
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file.rb') # => false
|
319
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file-0.2.0') # => false
|
320
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'another') # => false
|
321
|
-
#
|
322
|
-
# In matching, partial basenames are not allowed but partial directories
|
323
|
-
# are allowed. Hence:
|
324
|
-
#
|
325
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.txt', 'file') # => true
|
326
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.txt', 'ile') # => false
|
327
|
-
# Tap::Root.minimal_match?('dir/file-0.1.0.txt', 'r/file') # => true
|
328
|
-
#
|
329
|
-
def minimal_match?(path, mini_path)
|
330
|
-
extname = non_version_extname(mini_path)
|
331
|
-
version = mini_path =~ /(-\d+(\.\d+)*)#{extname}$/ ? $1 : ''
|
332
|
-
|
333
|
-
match_path = case
|
334
|
-
when !extname.empty?
|
335
|
-
# force full match
|
336
|
-
path
|
337
|
-
when !version.empty?
|
338
|
-
# match up to version
|
339
|
-
path.chomp(non_version_extname(path))
|
340
|
-
else
|
341
|
-
# match up base
|
342
|
-
path.chomp(non_version_extname(path)).sub(/(-\d+(\.\d+)*)$/, '')
|
343
|
-
end
|
344
|
-
|
345
|
-
# key ends with pattern AND basenames of each are equal...
|
346
|
-
# the last check ensures that a full path segment has
|
347
|
-
# been specified
|
348
|
-
match_path[-mini_path.length, mini_path.length] == mini_path && File.basename(match_path) == File.basename(mini_path)
|
349
|
-
end
|
350
|
-
|
351
|
-
# Returns the path segments for the given path, splitting along the path
|
352
|
-
# divider. Root paths are always represented by a string, if only an
|
353
|
-
# empty string.
|
354
|
-
#
|
355
|
-
# os divider example
|
356
|
-
# windows '\' Root.split('C:\path\to\file') # => ["C:", "path", "to", "file"]
|
357
|
-
# *nix '/' Root.split('/path/to/file') # => ["", "path", "to", "file"]
|
358
|
-
#
|
359
|
-
# The path is always expanded relative to the expand_dir; so '.' and
|
360
|
-
# '..' are resolved. However, unless expand_path == true, only the
|
361
|
-
# segments relative to the expand_dir are returned.
|
362
|
-
#
|
363
|
-
# On windows (note that expanding paths allows the use of slashes or
|
364
|
-
# backslashes):
|
365
|
-
#
|
366
|
-
# Dir.pwd # => 'C:/'
|
367
|
-
# Root.split('path\to\..\.\to\file') # => ["C:", "path", "to", "file"]
|
368
|
-
# Root.split('path/to/.././to/file', false) # => ["path", "to", "file"]
|
369
|
-
#
|
370
|
-
# On *nix (or more generally systems with '/' roots):
|
371
|
-
#
|
372
|
-
# Dir.pwd # => '/'
|
373
|
-
# Root.split('path/to/.././to/file') # => ["", "path", "to", "file"]
|
374
|
-
# Root.split('path/to/.././to/file', false) # => ["path", "to", "file"]
|
375
|
-
#
|
376
|
-
def split(path, expand_path=true, expand_dir=Dir.pwd)
|
377
|
-
path = if expand_path
|
378
|
-
File.expand_path(path, expand_dir)
|
379
|
-
else
|
380
|
-
# normalize the path by expanding it, then
|
381
|
-
# work back to the relative filepath as needed
|
382
|
-
expanded_dir = File.expand_path(expand_dir)
|
383
|
-
expanded_path = File.expand_path(path, expand_dir)
|
384
|
-
expanded_path.index(expanded_dir) != 0 ? expanded_path : Tap::Root.relative_filepath(expanded_dir, expanded_path)
|
385
|
-
end
|
386
|
-
|
387
|
-
segments = path.scan(/[^\/]+/)
|
388
|
-
|
389
|
-
# add back the root filepath as needed on *nix
|
390
|
-
segments.unshift "" if path[0] == ?/
|
391
|
-
segments
|
392
|
-
end
|
393
|
-
|
394
|
-
private
|
395
|
-
|
396
|
-
# utility method for minimize -- joins the
|
397
|
-
# dir and path, preventing results like:
|
398
|
-
#
|
399
|
-
# "./path"
|
400
|
-
# "//path"
|
401
|
-
#
|
402
|
-
def min_join(dir, path) # :nodoc:
|
403
|
-
case dir
|
404
|
-
when "." then path
|
405
|
-
when "/" then "/#{path}"
|
406
|
-
else "#{dir}/#{path}"
|
407
|
-
end
|
408
|
-
end
|
409
|
-
|
410
|
-
# utility method for minimize -- returns the
|
411
|
-
# dirname of path, or an array if the dirname
|
412
|
-
# is effectively empty.
|
413
|
-
def dirname_or_array(path) # :nodoc:
|
414
|
-
dir = File.dirname(path)
|
415
|
-
case dir
|
416
|
-
when path, '.' then []
|
417
|
-
else dir
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
# utility method for minimize -- determines if there
|
422
|
-
# is just one of the base in splits, while flagging
|
423
|
-
# all matching entries.
|
424
|
-
def just_one?(splits, index, base) # :nodoc:
|
425
|
-
just_one = true
|
426
|
-
index.upto(splits.length-1) do |i|
|
427
|
-
if splits[i][1] == base
|
428
|
-
splits[i][4] = true
|
429
|
-
just_one = false
|
430
|
-
end
|
431
|
-
end
|
432
|
-
|
433
|
-
just_one
|
434
|
-
end
|
435
|
-
|
436
|
-
# utility method for minimal_match -- returns a non-version
|
437
|
-
# extname, or an empty string if the path ends in a version.
|
438
|
-
def non_version_extname(path) # :nodoc:
|
439
|
-
extname = File.extname(path)
|
440
|
-
extname =~ /^\.\d+$/ ? '' : extname
|
441
|
-
end
|
442
|
-
|
443
|
-
end
|
444
|
-
|
445
53
|
include Configurable
|
446
|
-
include
|
447
|
-
|
54
|
+
include Utils
|
55
|
+
|
448
56
|
# The root directory.
|
449
|
-
config_attr(:root, '.', :writer => false)
|
450
|
-
|
57
|
+
config_attr(:root, '.', :writer => false, :set_default => false)
|
58
|
+
|
451
59
|
# A hash of (alias, relative path) pairs for aliased paths relative
|
452
60
|
# to root.
|
453
|
-
config_attr(:relative_paths, {}, :writer => false)
|
454
|
-
|
61
|
+
config_attr(:relative_paths, {}, :writer => false, :set_default => false, :type => :hash)
|
62
|
+
|
455
63
|
# A hash of (alias, relative path) pairs for aliased absolute paths.
|
456
|
-
config_attr(:absolute_paths, {}, :reader => false, :writer => false)
|
457
|
-
|
64
|
+
config_attr(:absolute_paths, {}, :reader => false, :writer => false, :set_default => false, :type => :hash)
|
65
|
+
|
458
66
|
# A hash of (alias, expanded path) pairs for expanded relative and
|
459
67
|
# absolute paths.
|
460
68
|
attr_reader :paths
|
461
|
-
|
69
|
+
|
462
70
|
# The filesystem root, inferred from self.root
|
463
71
|
# (ex '/' on *nix or something like 'C:/' on Windows).
|
464
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)
|
90
|
+
end
|
465
91
|
|
466
|
-
|
467
|
-
# and absolute paths. By default root is the present working directory
|
468
|
-
# and no aliased relative or absolute paths are specified.
|
469
|
-
def initialize(root=Dir.pwd, relative_paths={}, absolute_paths={})
|
470
|
-
assign_paths(root, relative_paths, absolute_paths)
|
471
|
-
@config = DelegateHash.new(self.class.configurations, {}, self)
|
92
|
+
initialize_config(config_or_dir)
|
472
93
|
end
|
473
|
-
|
94
|
+
|
474
95
|
# Sets the root directory. All paths are reassigned accordingly.
|
475
96
|
def root=(path)
|
476
97
|
assign_paths(path, relative_paths, absolute_paths)
|
477
98
|
end
|
478
|
-
|
99
|
+
|
479
100
|
# Sets the relative_paths to those provided. 'root' and :root are reserved
|
480
101
|
# aliases and cannot be set using this method (use root= instead).
|
481
102
|
#
|
482
|
-
# r =
|
103
|
+
# r = Root.new
|
483
104
|
# r['alt'] # => File.join(r.root, 'alt')
|
484
105
|
# r.relative_paths = {'alt' => 'dir'}
|
485
106
|
# r['alt'] # => File.join(r.root, 'dir')
|
@@ -488,11 +109,11 @@ module Tap
|
|
488
109
|
paths = Validation::HASH[paths]
|
489
110
|
assign_paths(root, paths, absolute_paths)
|
490
111
|
end
|
491
|
-
|
112
|
+
|
492
113
|
# Sets the absolute paths to those provided. 'root' and :root are reserved
|
493
114
|
# aliases and cannot be set using this method (use root= instead).
|
494
115
|
#
|
495
|
-
# r =
|
116
|
+
# r = Root.new
|
496
117
|
# r['abs'] # => File.join(r.root, 'abs')
|
497
118
|
# r.absolute_paths = {'abs' => '/path/to/dir'}
|
498
119
|
# r['abs'] # => '/path/to/dir'
|
@@ -501,7 +122,7 @@ module Tap
|
|
501
122
|
paths = Validation::HASH[paths]
|
502
123
|
assign_paths(root, relative_paths, paths)
|
503
124
|
end
|
504
|
-
|
125
|
+
|
505
126
|
# Returns the absolute paths registered with self.
|
506
127
|
def absolute_paths
|
507
128
|
abs_paths = {}
|
@@ -515,7 +136,7 @@ module Tap
|
|
515
136
|
|
516
137
|
# Sets an alias for the path relative to the root directory. The aliases
|
517
138
|
# 'root' and :root cannot be set with this method (use root= instead).
|
518
|
-
# Absolute
|
139
|
+
# Absolute paths can be set using the second syntax.
|
519
140
|
#
|
520
141
|
# r = Root.new '/root_dir'
|
521
142
|
# r[:dir] = 'path/to/dir'
|
@@ -523,25 +144,23 @@ module Tap
|
|
523
144
|
#
|
524
145
|
# r[:abs, true] = '/abs/path/to/dir'
|
525
146
|
# r[:abs] # => '/abs/path/to/dir'
|
526
|
-
#
|
527
|
-
#--
|
147
|
+
#
|
148
|
+
#--
|
528
149
|
# Implementation Note:
|
529
150
|
#
|
530
|
-
# The syntax for setting an absolute
|
531
|
-
# In fact the method
|
532
|
-
# rather than (:dir, '/abs/path/to/dir', true)
|
533
|
-
# and absolute are switched when setting an absolute
|
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.
|
534
155
|
#
|
535
156
|
def []=(als, path, absolute=false)
|
536
157
|
raise ArgumentError, "the alias #{als.inspect} is reserved" if als.to_s == 'root'
|
537
|
-
|
158
|
+
|
538
159
|
# switch the paths if absolute was provided
|
539
160
|
unless absolute == false
|
540
|
-
|
541
|
-
path = absolute
|
542
|
-
absolute = switch
|
161
|
+
path, absolute = absolute, path
|
543
162
|
end
|
544
|
-
|
163
|
+
|
545
164
|
case
|
546
165
|
when path.nil?
|
547
166
|
@relative_paths.delete(als)
|
@@ -554,7 +173,7 @@ module Tap
|
|
554
173
|
@paths[als] = File.expand_path(File.join(root, path))
|
555
174
|
end
|
556
175
|
end
|
557
|
-
|
176
|
+
|
558
177
|
# Returns the expanded path for the specified alias. If the alias has not
|
559
178
|
# been set, then the path is inferred to be 'root/als'. Expanded paths
|
560
179
|
# are returned directly.
|
@@ -569,79 +188,70 @@ module Tap
|
|
569
188
|
def [](als)
|
570
189
|
path = self.paths[als]
|
571
190
|
return path unless path == nil
|
572
|
-
|
191
|
+
|
573
192
|
als = als.to_s
|
574
|
-
|
193
|
+
expanded?(als) ? als : File.expand_path(File.join(root, als))
|
575
194
|
end
|
576
|
-
|
577
|
-
# Resolves the specified alias, joins
|
578
|
-
# resulting
|
579
|
-
def
|
195
|
+
|
196
|
+
# Resolves the specified alias, joins paths together, and expands the
|
197
|
+
# resulting path.
|
198
|
+
def path(als, *paths)
|
580
199
|
File.expand_path(File.join(self[als], *paths))
|
581
200
|
end
|
582
201
|
|
583
|
-
#
|
584
|
-
def
|
585
|
-
|
202
|
+
# Returns true if the path is relative to the specified alias.
|
203
|
+
def relative?(als, path)
|
204
|
+
super(self[als], path)
|
586
205
|
end
|
587
|
-
|
588
|
-
#
|
589
|
-
|
590
|
-
|
591
|
-
dir = self[als]
|
592
|
-
path = filepath(als, *paths)
|
593
|
-
|
594
|
-
if path.rindex(dir, 0) != 0
|
595
|
-
raise "not a subpath: #{path} (#{dir})"
|
596
|
-
end
|
597
|
-
|
598
|
-
path
|
206
|
+
|
207
|
+
# Returns the part of path relative to the specified alias.
|
208
|
+
def relative_path(als, path)
|
209
|
+
super(self[als], path)
|
599
210
|
end
|
600
211
|
|
601
|
-
# Generates a
|
602
|
-
#
|
603
|
-
# to the source dir.
|
212
|
+
# Generates a path translated from the aliased source to the aliased target.
|
213
|
+
# Raises an error if path is not relative to the source.
|
604
214
|
#
|
605
|
-
# r =
|
606
|
-
# path = r.
|
607
|
-
# r.translate(path, :in, :out)
|
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'
|
608
218
|
#
|
609
219
|
def translate(path, source_als, target_als)
|
610
|
-
|
220
|
+
super(path, self[source_als], self[target_als])
|
611
221
|
end
|
612
|
-
|
613
|
-
#
|
222
|
+
|
223
|
+
# Globs for paths along the aliased path matching the input patterns.
|
614
224
|
# Patterns should join with the aliased path make valid globs for
|
615
225
|
# Dir.glob. If no patterns are specified, glob returns all paths
|
616
226
|
# matching 'als/**/*'.
|
617
227
|
def glob(als, *patterns)
|
618
228
|
patterns << "**/*" if patterns.empty?
|
619
|
-
patterns.collect! {|pattern|
|
620
|
-
|
229
|
+
patterns.collect! {|pattern| path(als, pattern)}
|
230
|
+
super(*patterns)
|
621
231
|
end
|
622
|
-
|
232
|
+
|
623
233
|
# Lists all versions of path in the aliased dir matching the version
|
624
234
|
# patterns. If no patterns are specified, then all versions of path
|
625
235
|
# will be returned.
|
626
|
-
def
|
627
|
-
|
236
|
+
def version_glob(als, path, *vpatterns)
|
237
|
+
super(path(als, path), *vpatterns)
|
628
238
|
end
|
629
|
-
|
239
|
+
|
630
240
|
# Changes pwd to the specified directory using Root.chdir.
|
631
241
|
def chdir(als, mkdir=false, &block)
|
632
|
-
|
242
|
+
super(self[als], mkdir, &block)
|
633
243
|
end
|
634
|
-
|
635
|
-
# Constructs a path from the inputs
|
244
|
+
|
245
|
+
# Constructs a path from the inputs and prepares it using
|
636
246
|
# Root.prepare. Returns the path.
|
637
247
|
def prepare(als, *paths, &block)
|
638
|
-
|
248
|
+
super(path(als, *paths), &block)
|
639
249
|
end
|
640
|
-
|
250
|
+
|
641
251
|
private
|
642
252
|
|
643
253
|
# reassigns all paths with the input root, relative_paths, and absolute_paths
|
644
|
-
def assign_paths(root, relative_paths, absolute_paths)
|
254
|
+
def assign_paths(root, relative_paths, absolute_paths) # :nodoc:
|
645
255
|
@root = File.expand_path(root)
|
646
256
|
@relative_paths = {}
|
647
257
|
@paths = {'root' => @root, :root => @root}
|
@@ -650,10 +260,9 @@ module Tap
|
|
650
260
|
while @path_root != (parent = File.dirname(@path_root))
|
651
261
|
@path_root = parent
|
652
262
|
end
|
653
|
-
|
263
|
+
|
654
264
|
relative_paths.each_pair {|dir, path| self[dir] = path }
|
655
265
|
absolute_paths.each_pair {|dir, path| self[dir, true] = path }
|
656
266
|
end
|
657
|
-
|
658
267
|
end
|
659
268
|
end
|