root 0.0.1

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.
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