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
data/lib/tap/joins.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'tap/join'
2
+ require 'tap/joins/switch'
3
+ require 'tap/joins/sync'
4
+
5
+ module Tap
6
+ # A module of standard Join classes supported by Tap.
7
+ module Joins
8
+ end
9
+ end
@@ -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/support/versions'
3
- autoload(:FileUtils, 'fileutils')
2
+ require 'tap/root/utils'
4
3
 
5
4
  module Tap
6
5
 
7
- # Root allows you to define a root directory and alias relative paths, so
8
- # that you can conceptualize what filepaths you need without predefining the
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
- # r = Root.new '/root_dir', :input => 'in', :output => 'out'
10
+ # root = Root.new(
11
+ # :root => '/root_dir',
12
+ # :relative_paths => {:input => 'in', :output => 'out'})
13
13
  #
14
- # # work with aliases
15
- # r[:input] # => '/root_dir/in'
16
- # r[:output] # => '/root_dir/out'
17
- # r['implicit'] # => '/root_dir/implicit'
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
- # # expanded paths are returned unchanged
20
- # r[File.expand_path('expanded')] # => File.expand_path('expanded')
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
- # # work with filepaths
23
- # fp = r.filepath(:input, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
24
- # r.relative_filepath(:input, fp) # => 'path/to/file.txt'
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
- # # absolute paths can also be aliased
33
- # r[:abs, true] = "/absolute/path"
34
- # r.filepath(:abs, "to", "file.txt") # => '/absolute/path/to/file.txt'
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 filepaths in 'paths' are expanded
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 Support::Versions
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
- # Creates a new Root with the given root directory, aliased relative paths
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 = Tap::Root.new
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 = Tap::Root.new
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 filepaths can be set using the second syntax.
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 filepath requires an odd use []=.
531
- # In fact the method recieves the arguments (:dir, true, '/abs/path/to/dir')
532
- # rather than (:dir, '/abs/path/to/dir', true), meaning that internally path
533
- # and absolute are switched when setting an absolute filepath.
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
- switch = path
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
- Root.expanded?(als) ? als : File.expand_path(File.join(root, als))
193
+ expanded?(als) ? als : File.expand_path(File.join(root, als))
575
194
  end
576
-
577
- # Resolves the specified alias, joins the paths together, and expands the
578
- # resulting filepath.
579
- def filepath(als, *paths)
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
- # Retrieves the filepath relative to the path of the specified alias.
584
- def relative_filepath(als, path)
585
- Root.relative_filepath(self[als], path)
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
- # Same as filepath but raises an error if the result is not a subpath of
589
- # the aliased directory.
590
- def subpath(als, *paths)
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 filepath translated from the aliased source dir to the
602
- # aliased target dir. Raises an error if the filepath is not relative
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 = Tap::Root.new '/root_dir'
606
- # path = r.filepath(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
607
- # r.translate(path, :in, :out) # => '/root_dir/out/path/to/file.txt'
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
- Root.translate(path, self[source_als], self[target_als])
220
+ super(path, self[source_als], self[target_als])
611
221
  end
612
-
613
- # Lists all files along the aliased path matching the input patterns.
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| filepath(als, pattern)}
620
- Root.glob(*patterns)
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 vglob(als, path, *vpatterns)
627
- Root.vglob(filepath(als, path), *vpatterns)
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
- Root.chdir(self[als], mkdir, &block)
242
+ super(self[als], mkdir, &block)
633
243
  end
634
-
635
- # Constructs a path from the inputs (using filepath) and prepares it using
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
- Root.prepare(filepath(als, *paths), &block)
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