root 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History ADDED
@@ -0,0 +1,3 @@
1
+ == 0.0.1 / 2009-04-03
2
+
3
+ Initial release, a port of Tap::Root.
@@ -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]
@@ -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