tap 0.12.4 → 0.17.0

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