root 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History +3 -0
- data/MIT-LICENSE +22 -0
- data/README +14 -0
- data/lib/root.rb +268 -0
- data/lib/root/constant.rb +140 -0
- data/lib/root/constant_manifest.rb +126 -0
- data/lib/root/gems.rb +40 -0
- data/lib/root/intern.rb +36 -0
- data/lib/root/manifest.rb +168 -0
- data/lib/root/minimap.rb +88 -0
- data/lib/root/string_ext.rb +57 -0
- data/lib/root/utils.rb +391 -0
- data/lib/root/versions.rb +134 -0
- metadata +72 -0
data/History
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009, Regents of the University of Colorado.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
= Root[http://tap.rubyforge.org/root]
|
2
|
+
|
3
|
+
== Installation
|
4
|
+
|
5
|
+
Root is available as a gem on RubyForge[http://rubyforge.org/projects/tap]. Use:
|
6
|
+
|
7
|
+
% gem install root
|
8
|
+
|
9
|
+
== Info
|
10
|
+
|
11
|
+
Copyright (c) 2009, Regents of the University of Colorado.
|
12
|
+
Developer:: {Simon Chiang}[http://bahuvrihi.wordpress.com], {Biomolecular Structure Program}[http://biomol.uchsc.edu/], {Hansen Lab}[http://hsc-proteomics.uchsc.edu/hansenlab/]
|
13
|
+
Support:: CU Denver School of Medicine Deans Academic Enrichment Fund
|
14
|
+
License:: {MIT-Style}[link:files/MIT-LICENSE.html]
|
data/lib/root.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'configurable'
|
2
|
+
require 'root/utils'
|
3
|
+
|
4
|
+
# Root allows you to define a root directory and alias relative paths, so
|
5
|
+
# that you can conceptualize what filepaths you need without predefining the
|
6
|
+
# full filepaths. Root also simplifies operations on filepaths.
|
7
|
+
#
|
8
|
+
# # define a root directory with aliased relative paths
|
9
|
+
# r = Root.new '/root_dir', :input => 'in', :output => 'out'
|
10
|
+
#
|
11
|
+
# # work with aliases
|
12
|
+
# r[:input] # => '/root_dir/in'
|
13
|
+
# r[:output] # => '/root_dir/out'
|
14
|
+
# r['implicit'] # => '/root_dir/implicit'
|
15
|
+
#
|
16
|
+
# # expanded paths are returned unchanged
|
17
|
+
# r[File.expand_path('expanded')] # => File.expand_path('expanded')
|
18
|
+
#
|
19
|
+
# # work with filepaths
|
20
|
+
# fp = r.filepath(:input, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
|
21
|
+
# r.relative_filepath(:input, fp) # => 'path/to/file.txt'
|
22
|
+
# r.translate(fp, :input, :output) # => '/root_dir/out/path/to/file.txt'
|
23
|
+
#
|
24
|
+
# # version filepaths
|
25
|
+
# r.version('path/to/config.yml', 1.0) # => 'path/to/config-1.0.yml'
|
26
|
+
# r.increment('path/to/config-1.0.yml', 0.1) # => 'path/to/config-1.1.yml'
|
27
|
+
# r.deversion('path/to/config-1.1.yml') # => ['path/to/config.yml', "1.1"]
|
28
|
+
#
|
29
|
+
# # absolute paths can also be aliased
|
30
|
+
# r[:abs, true] = "/absolute/path"
|
31
|
+
# r.filepath(:abs, "to", "file.txt") # => '/absolute/path/to/file.txt'
|
32
|
+
#
|
33
|
+
# By default, Roots are initialized to the present working directory
|
34
|
+
# (Dir.pwd).
|
35
|
+
#
|
36
|
+
#--
|
37
|
+
# === Implementation Notes
|
38
|
+
#
|
39
|
+
# Internally Root expands and stores all aliased paths in the 'paths' hash.
|
40
|
+
# Expanding paths ensures they remain constant even when the present working
|
41
|
+
# directory (Dir.pwd) changes.
|
42
|
+
#
|
43
|
+
# Root keeps a separate 'relative_paths' hash mapping aliases to their
|
44
|
+
# relative paths. This hash allow reassignment if and when the root directory
|
45
|
+
# changes. By contrast, there is no separate data structure storing the
|
46
|
+
# absolute paths. An absolute path thus has an alias in 'paths' but not
|
47
|
+
# 'relative_paths', whereas relative paths have aliases in both.
|
48
|
+
#
|
49
|
+
# These features may be important to note when subclassing Root:
|
50
|
+
# - root and all filepaths in 'paths' are expanded
|
51
|
+
# - relative paths are stored in 'relative_paths'
|
52
|
+
# - absolute paths are present in 'paths' but not in 'relative_paths'
|
53
|
+
#
|
54
|
+
class Root
|
55
|
+
include Configurable
|
56
|
+
include Utils
|
57
|
+
|
58
|
+
# The root directory.
|
59
|
+
config_attr(:root, '.', :writer => false)
|
60
|
+
|
61
|
+
# A hash of (alias, relative path) pairs for aliased paths relative
|
62
|
+
# to root.
|
63
|
+
config_attr(:relative_paths, {}, :writer => false)
|
64
|
+
|
65
|
+
# A hash of (alias, relative path) pairs for aliased absolute paths.
|
66
|
+
config_attr(:absolute_paths, {}, :reader => false, :writer => false)
|
67
|
+
|
68
|
+
# A hash of (alias, expanded path) pairs for expanded relative and
|
69
|
+
# absolute paths.
|
70
|
+
attr_reader :paths
|
71
|
+
|
72
|
+
# The filesystem root, inferred from self.root
|
73
|
+
# (ex '/' on *nix or something like 'C:/' on Windows).
|
74
|
+
attr_reader :path_root
|
75
|
+
|
76
|
+
# Creates a new Root with the given root directory, aliased relative paths
|
77
|
+
# and absolute paths. By default root is the present working directory
|
78
|
+
# and no aliased relative or absolute paths are specified.
|
79
|
+
def initialize(root=Dir.pwd, relative_paths={}, absolute_paths={})
|
80
|
+
assign_paths(root, relative_paths, absolute_paths)
|
81
|
+
@config = DelegateHash.new(self.class.configurations, {}, self)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Sets the root directory. All paths are reassigned accordingly.
|
85
|
+
def root=(path)
|
86
|
+
assign_paths(path, relative_paths, absolute_paths)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Sets the relative_paths to those provided. 'root' and :root are reserved
|
90
|
+
# aliases and cannot be set using this method (use root= instead).
|
91
|
+
#
|
92
|
+
# r = Root.new
|
93
|
+
# r['alt'] # => File.join(r.root, 'alt')
|
94
|
+
# r.relative_paths = {'alt' => 'dir'}
|
95
|
+
# r['alt'] # => File.join(r.root, 'dir')
|
96
|
+
#
|
97
|
+
def relative_paths=(paths)
|
98
|
+
paths = Validation::HASH[paths]
|
99
|
+
assign_paths(root, paths, absolute_paths)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Sets the absolute paths to those provided. 'root' and :root are reserved
|
103
|
+
# aliases and cannot be set using this method (use root= instead).
|
104
|
+
#
|
105
|
+
# r = Root.new
|
106
|
+
# r['abs'] # => File.join(r.root, 'abs')
|
107
|
+
# r.absolute_paths = {'abs' => '/path/to/dir'}
|
108
|
+
# r['abs'] # => '/path/to/dir'
|
109
|
+
#
|
110
|
+
def absolute_paths=(paths)
|
111
|
+
paths = Validation::HASH[paths]
|
112
|
+
assign_paths(root, relative_paths, paths)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the absolute paths registered with self.
|
116
|
+
def absolute_paths
|
117
|
+
abs_paths = {}
|
118
|
+
paths.each do |als, path|
|
119
|
+
unless relative_paths.include?(als) || als.to_s == 'root'
|
120
|
+
abs_paths[als] = path
|
121
|
+
end
|
122
|
+
end
|
123
|
+
abs_paths
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sets an alias for the path relative to the root directory. The aliases
|
127
|
+
# 'root' and :root cannot be set with this method (use root= instead).
|
128
|
+
# Absolute filepaths can be set using the second syntax.
|
129
|
+
#
|
130
|
+
# r = Root.new '/root_dir'
|
131
|
+
# r[:dir] = 'path/to/dir'
|
132
|
+
# r[:dir] # => '/root_dir/path/to/dir'
|
133
|
+
#
|
134
|
+
# r[:abs, true] = '/abs/path/to/dir'
|
135
|
+
# r[:abs] # => '/abs/path/to/dir'
|
136
|
+
#
|
137
|
+
#--
|
138
|
+
# Implementation Note:
|
139
|
+
#
|
140
|
+
# The syntax for setting an absolute filepath requires an odd use []=.
|
141
|
+
# In fact the method recieves the arguments (:dir, true, '/abs/path/to/dir')
|
142
|
+
# rather than (:dir, '/abs/path/to/dir', true), meaning that internally path
|
143
|
+
# and absolute are switched when setting an absolute filepath.
|
144
|
+
#
|
145
|
+
def []=(als, path, absolute=false)
|
146
|
+
raise ArgumentError, "the alias #{als.inspect} is reserved" if als.to_s == 'root'
|
147
|
+
|
148
|
+
# switch the paths if absolute was provided
|
149
|
+
unless absolute == false
|
150
|
+
switch = path
|
151
|
+
path = absolute
|
152
|
+
absolute = switch
|
153
|
+
end
|
154
|
+
|
155
|
+
case
|
156
|
+
when path.nil?
|
157
|
+
@relative_paths.delete(als)
|
158
|
+
@paths.delete(als)
|
159
|
+
when absolute
|
160
|
+
@relative_paths.delete(als)
|
161
|
+
@paths[als] = File.expand_path(path)
|
162
|
+
else
|
163
|
+
@relative_paths[als] = path
|
164
|
+
@paths[als] = File.expand_path(File.join(root, path))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns the expanded path for the specified alias. If the alias has not
|
169
|
+
# been set, then the path is inferred to be 'root/als'. Expanded paths
|
170
|
+
# are returned directly.
|
171
|
+
#
|
172
|
+
# r = Root.new '/root_dir', :dir => 'path/to/dir'
|
173
|
+
# r[:dir] # => '/root_dir/path/to/dir'
|
174
|
+
#
|
175
|
+
# r.path_root # => '/'
|
176
|
+
# r['relative/path'] # => '/root_dir/relative/path'
|
177
|
+
# r['/expanded/path'] # => '/expanded/path'
|
178
|
+
#
|
179
|
+
def [](als)
|
180
|
+
path = self.paths[als]
|
181
|
+
return path unless path == nil
|
182
|
+
|
183
|
+
als = als.to_s
|
184
|
+
expanded?(als) ? als : File.expand_path(File.join(root, als))
|
185
|
+
end
|
186
|
+
|
187
|
+
# Resolves the specified alias, joins the paths together, and expands the
|
188
|
+
# resulting filepath.
|
189
|
+
def filepath(als, *paths)
|
190
|
+
File.expand_path(File.join(self[als], *paths))
|
191
|
+
end
|
192
|
+
|
193
|
+
# Retrieves the filepath relative to the path of the specified alias.
|
194
|
+
def relative_filepath(als, path)
|
195
|
+
super(self[als], path)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Same as filepath but raises an error if the result is not a subpath of
|
199
|
+
# the aliased directory.
|
200
|
+
def subpath(als, *paths)
|
201
|
+
dir = self[als]
|
202
|
+
path = filepath(als, *paths)
|
203
|
+
|
204
|
+
if path.rindex(dir, 0) != 0
|
205
|
+
raise "not a subpath: #{path} (#{dir})"
|
206
|
+
end
|
207
|
+
|
208
|
+
path
|
209
|
+
end
|
210
|
+
|
211
|
+
# Generates a filepath translated from the aliased source dir to the
|
212
|
+
# aliased target dir. Raises an error if the filepath is not relative
|
213
|
+
# to the source dir.
|
214
|
+
#
|
215
|
+
# r = Root.new '/root_dir'
|
216
|
+
# path = r.filepath(: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'
|
218
|
+
#
|
219
|
+
def translate(path, source_als, target_als)
|
220
|
+
super(path, self[source_als], self[target_als])
|
221
|
+
end
|
222
|
+
|
223
|
+
# Lists all files along the aliased path matching the input patterns.
|
224
|
+
# Patterns should join with the aliased path make valid globs for
|
225
|
+
# Dir.glob. If no patterns are specified, glob returns all paths
|
226
|
+
# matching 'als/**/*'.
|
227
|
+
def glob(als, *patterns)
|
228
|
+
patterns << "**/*" if patterns.empty?
|
229
|
+
patterns.collect! {|pattern| filepath(als, pattern)}
|
230
|
+
super(*patterns)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Lists all versions of path in the aliased dir matching the version
|
234
|
+
# patterns. If no patterns are specified, then all versions of path
|
235
|
+
# will be returned.
|
236
|
+
def vglob(als, path, *vpatterns)
|
237
|
+
super(filepath(als, path), *vpatterns)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Changes pwd to the specified directory using Root.chdir.
|
241
|
+
def chdir(als, mkdir=false, &block)
|
242
|
+
super(self[als], mkdir, &block)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Constructs a path from the inputs (using filepath) and prepares it using
|
246
|
+
# Root.prepare. Returns the path.
|
247
|
+
def prepare(als, *paths, &block)
|
248
|
+
super(filepath(als, *paths), &block)
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# reassigns all paths with the input root, relative_paths, and absolute_paths
|
254
|
+
def assign_paths(root, relative_paths, absolute_paths)
|
255
|
+
@root = File.expand_path(root)
|
256
|
+
@relative_paths = {}
|
257
|
+
@paths = {'root' => @root, :root => @root}
|
258
|
+
|
259
|
+
@path_root = File.dirname(@root)
|
260
|
+
while @path_root != (parent = File.dirname(@path_root))
|
261
|
+
@path_root = parent
|
262
|
+
end
|
263
|
+
|
264
|
+
relative_paths.each_pair {|dir, path| self[dir] = path }
|
265
|
+
absolute_paths.each_pair {|dir, path| self[dir, true] = path }
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'root/string_ext'
|
2
|
+
|
3
|
+
class Root
|
4
|
+
|
5
|
+
# A Constant serves as a placeholder for an actual constant, sort of like
|
6
|
+
# autoload. Use the constantize method to retrieve the actual constant; if
|
7
|
+
# it doesn't exist, constantize requires require_path and tries again.
|
8
|
+
#
|
9
|
+
# Object.const_defined?(:Net) # => false
|
10
|
+
# $".include?('net/http') # => false
|
11
|
+
#
|
12
|
+
# http = Constant.new('Net::HTTP', 'net/http')
|
13
|
+
# http.constantize # => Net::HTTP
|
14
|
+
# $".include?('net/http') # => true
|
15
|
+
#
|
16
|
+
class Constant
|
17
|
+
class << self
|
18
|
+
|
19
|
+
# Tries to find a declared constant under base with the specified
|
20
|
+
# const_name. When a constant is missing, constantize yields the
|
21
|
+
# current base and any non-existant constant names the block, if given,
|
22
|
+
# or raises a NameError. The block is expected to return the desired
|
23
|
+
# constant; in the example 'Non::Existant' is effectively mapping to
|
24
|
+
# ConstName.
|
25
|
+
#
|
26
|
+
# module ConstName; end
|
27
|
+
#
|
28
|
+
# Constant.constantize('ConstName') # => ConstName
|
29
|
+
# Constant.constantize('Non::Existant') { ConstName } # => ConstName
|
30
|
+
#
|
31
|
+
def constantize(const_name, base=Object) # :yields: base, missing_const_names
|
32
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ const_name
|
33
|
+
raise NameError, "#{const_name.inspect} is not a valid constant name!"
|
34
|
+
end
|
35
|
+
|
36
|
+
constants = $1.split(/::/)
|
37
|
+
while !constants.empty?
|
38
|
+
unless const_is_defined?(base, constants[0])
|
39
|
+
if block_given?
|
40
|
+
return yield(base, constants)
|
41
|
+
else
|
42
|
+
raise NameError.new("uninitialized constant #{const_name}", constants[0])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
base = base.const_get(constants.shift)
|
46
|
+
end
|
47
|
+
base
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# helper method. Determines if a constant named name is defined in
|
53
|
+
# const. The implementation (annoyingly) has to be different for ruby
|
54
|
+
# 1.9 due to changes in the API.
|
55
|
+
case RUBY_VERSION
|
56
|
+
when /^1.9/
|
57
|
+
def const_is_defined?(const, name) # :nodoc:
|
58
|
+
const.const_defined?(name, false)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
def const_is_defined?(const, name) # :nodoc:
|
62
|
+
const.const_defined?(name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The constant name
|
68
|
+
attr_reader :name
|
69
|
+
|
70
|
+
# The path to load to initialize a missing constant
|
71
|
+
attr_reader :require_path
|
72
|
+
|
73
|
+
# Initializes a new Constant with the specified constant name and
|
74
|
+
# require_path. The name should be a valid constant name.
|
75
|
+
def initialize(name, require_path=nil)
|
76
|
+
@name = name
|
77
|
+
@require_path = require_path
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the underscored name.
|
81
|
+
def path
|
82
|
+
@path ||= name.underscore
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the basename of path.
|
86
|
+
def basename
|
87
|
+
@basename ||= File.basename(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the path, minus the basename of path.
|
91
|
+
def dirname
|
92
|
+
@dirname ||= (dirname = File.dirname(path)) == "." ? "" : dirname
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the name of the constant, minus nesting.
|
96
|
+
def const_name
|
97
|
+
@const_name ||= (name =~ /.*::(.*)$/ ? $1 : name)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the nesting constants of name.
|
101
|
+
def nesting
|
102
|
+
@nesting ||= (name =~ /(.*)::.*$/ ? $1 : '')
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the number of constants in nesting.
|
106
|
+
def nesting_depth
|
107
|
+
@nesting_depth ||= nesting.split(/::/).length
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the Lazydoc document for require_path.
|
111
|
+
def document
|
112
|
+
require_path ? Lazydoc[require_path] : nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# True if another is a Constant with the same name
|
116
|
+
# and require_path as self.
|
117
|
+
def ==(another)
|
118
|
+
another.kind_of?(Constant) &&
|
119
|
+
another.name == self.name &&
|
120
|
+
another.require_path == self.require_path
|
121
|
+
end
|
122
|
+
|
123
|
+
# Looks up and returns the constant indicated by name. If the constant
|
124
|
+
# cannot be found, constantize requires require_path and tries again.
|
125
|
+
#
|
126
|
+
# Raises a NameError if the constant cannot be found.
|
127
|
+
def constantize
|
128
|
+
Constant.constantize(name) do
|
129
|
+
require require_path if require_path
|
130
|
+
Constant.constantize(name)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a string like:
|
135
|
+
# "#<Root::Constant:object_id Const::Name (require_path)>"
|
136
|
+
def inspect
|
137
|
+
"#<#{self.class}:#{object_id} #{name}#{@require_path == nil ? "" : " (#{@require_path})"}>"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|