Class: Root

Inherits:
Object
  • Object
show all
Includes:
Configurable, Utils
Defined in:
lib/root.rb,
lib/root/gems.rb,
lib/root/utils.rb,
lib/root/intern.rb,
lib/root/minimap.rb,
lib/root/constant.rb,
lib/root/manifest.rb,
lib/root/versions.rb,
lib/root/string_ext.rb,
lib/root/constant_manifest.rb

Overview

Root allows you to define a root directory and alias relative paths, so that you can conceptualize what filepaths you need without predefining the full filepaths. Root also simplifies operations on filepaths.

# define a root directory with aliased relative paths
r = Root.new '/root_dir', :input => 'in', :output => 'out'

# work with aliases
r[:input]                                   # => '/root_dir/in'
r[:output]                                  # => '/root_dir/out'
r['implicit']                               # => '/root_dir/implicit'

# expanded paths are returned unchanged
r[File.expand_path('expanded')]             # => File.expand_path('expanded')

# work with filepaths
fp = r.filepath(:input, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
r.relative_filepath(:input, fp)             # => 'path/to/file.txt'
r.translate(fp, :input, :output)            # => '/root_dir/out/path/to/file.txt'

# version filepaths
r.version('path/to/config.yml', 1.0)        # => 'path/to/config-1.0.yml'
r.increment('path/to/config-1.0.yml', 0.1)  # => 'path/to/config-1.1.yml'
r.deversion('path/to/config-1.1.yml')       # => ['path/to/config.yml', "1.1"]

# absolute paths can also be aliased 
r[:abs, true] = "/absolute/path"      
r.filepath(:abs, "to", "file.txt")          # => '/absolute/path/to/file.txt'

By default, Roots are initialized to the present working directory (Dir.pwd).

Implementation Notes

Internally Root expands and stores all aliased paths in the ‘paths’ hash. Expanding paths ensures they remain constant even when the present working directory (Dir.pwd) changes.

Root keeps a separate ‘relative_paths’ hash mapping aliases to their relative paths. This hash allow reassignment if and when the root directory changes. By contrast, there is no separate data structure storing the absolute paths. An absolute path thus has an alias in ‘paths’ but not ‘relative_paths’, whereas relative paths have aliases in both.

These features may be important to note when subclassing Root:

  • root and all filepaths in ‘paths’ are expanded

  • relative paths are stored in ‘relative_paths’

  • absolute paths are present in ‘paths’ but not in ‘relative_paths’

Defined Under Namespace

Modules: Gems, Minimap, StringExt, Utils, Versions Classes: Constant, ConstantManifest, Manifest

Constant Summary collapse

INTERN_MODULES =

An array of already-declared intern modules, keyed by method_name.

{}

Constants included from Utils

Utils::WIN_ROOT_PATTERN

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

chdir, dirname_or_array, empty?, exchange, expanded?, glob, just_one?, min_join, minimal_match?, minimize, non_version_extname, path_root_type, prepare, relative_filepath, sglob, split, translate, trivial?, vglob

Methods included from Versions

#compare_versions, #deversion, #increment, #version, #vniq

Constructor Details

#initialize(root = Dir.pwd, relative_paths = {}, absolute_paths = {}) ⇒ Root

Creates a new Root with the given root directory, aliased relative paths and absolute paths. By default root is the present working directory and no aliased relative or absolute paths are specified.



79
80
81
82
# File 'lib/root.rb', line 79

def initialize(root=Dir.pwd, relative_paths={}, absolute_paths={})
  assign_paths(root, relative_paths, absolute_paths)
  @config = DelegateHash.new(self.class.configurations, {}, self)
end

Instance Attribute Details

#path_rootObject (readonly)

The filesystem root, inferred from self.root (ex ‘/’ on *nix or something like ‘C:/’ on Windows).



74
75
76
# File 'lib/root.rb', line 74

def path_root
  @path_root
end

#pathsObject (readonly)

A hash of (alias, expanded path) pairs for expanded relative and absolute paths.



70
71
72
# File 'lib/root.rb', line 70

def paths
  @paths
end

Class Method Details

.Intern(method_name) ⇒ Object

Generates an Intern module for the specified method_name.

An Intern module:

  • adds an accessor for <method_name>_block

  • overrides <method_name> to call the block



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/root/intern.rb', line 9

def self.Intern(method_name)
  mod = INTERN_MODULES[method_name.to_sym]
  return mod unless mod == nil
  
  mod = INTERN_MODULES[method_name.to_sym] = Module.new
  mod.module_eval %Q{
  attr_accessor :#{method_name}_block

  def #{method_name}(*inputs)
    raise "no #{method_name} block set" unless #{method_name}_block
    inputs.unshift(self)
  
    arity = #{method_name}_block.arity
    n = inputs.length
    unless n == arity || (arity < 0 && (-1-n) <= arity) 
      raise ArgumentError.new("wrong number of arguments (\#{n} for \#{arity})")
    end
  
    #{method_name}_block.call(*inputs)
  end
  }
  mod
end

Instance Method Details

#[](als) ⇒ Object

Returns the expanded path for the specified alias. If the alias has not been set, then the path is inferred to be ‘root/als’. Expanded paths are returned directly.

r = Root.new '/root_dir', :dir => 'path/to/dir'
r[:dir]                             # => '/root_dir/path/to/dir'

r.path_root                         # => '/'
r['relative/path']                  # => '/root_dir/relative/path'
r['/expanded/path']                 # => '/expanded/path'


179
180
181
182
183
184
185
# File 'lib/root.rb', line 179

def [](als)
  path = self.paths[als] 
  return path unless path == nil
  
  als = als.to_s 
  expanded?(als) ? als : File.expand_path(File.join(root, als))
end

#[]=(als, path, absolute = false) ⇒ Object

Sets an alias for the path relative to the root directory. The aliases ‘root’ and :root cannot be set with this method (use root= instead). Absolute filepaths can be set using the second syntax.

r = Root.new '/root_dir'
r[:dir] = 'path/to/dir'
r[:dir]                             # => '/root_dir/path/to/dir'

r[:abs, true] = '/abs/path/to/dir'  
r[:abs]                             # => '/abs/path/to/dir'

– Implementation Note:

The syntax for setting an absolute filepath requires an odd use []=.

In fact the method recieves the arguments (:dir, true, ‘/abs/path/to/dir’) rather than (:dir, ‘/abs/path/to/dir’, true), meaning that internally path and absolute are switched when setting an absolute filepath.

Raises:

  • (ArgumentError)


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/root.rb', line 145

def []=(als, path, absolute=false)
  raise ArgumentError, "the alias #{als.inspect} is reserved" if als.to_s == 'root'

  # switch the paths if absolute was provided
  unless absolute == false
    switch = path
    path = absolute
    absolute = switch
  end
  
  case
  when path.nil? 
    @relative_paths.delete(als)
    @paths.delete(als)
  when absolute
    @relative_paths.delete(als)
    @paths[als] = File.expand_path(path)
  else
    @relative_paths[als] = path
    @paths[als] = File.expand_path(File.join(root, path))
  end 
end

#absolute_pathsObject

Returns the absolute paths registered with self.



116
117
118
119
120
121
122
123
124
# File 'lib/root.rb', line 116

def absolute_paths
  abs_paths = {}
  paths.each do |als, path|
    unless relative_paths.include?(als) || als.to_s == 'root'
      abs_paths[als] = path
    end
  end
  abs_paths
end

#absolute_paths=(paths) ⇒ Object

Sets the absolute paths to those provided. ‘root’ and :root are reserved aliases and cannot be set using this method (use root= instead).

r = Root.new
r['abs']                            # => File.join(r.root, 'abs')
r.absolute_paths = {'abs' => '/path/to/dir'}
r['abs']                            # => '/path/to/dir'


110
111
112
113
# File 'lib/root.rb', line 110

def absolute_paths=(paths)
  paths = Validation::HASH[paths]
  assign_paths(root, relative_paths, paths)
end

#chdir(als, mkdir = false, &block) ⇒ Object

Changes pwd to the specified directory using Root.chdir.



241
242
243
# File 'lib/root.rb', line 241

def chdir(als, mkdir=false, &block)
  super(self[als], mkdir, &block)
end

#filepath(als, *paths) ⇒ Object

Resolves the specified alias, joins the paths together, and expands the resulting filepath.



189
190
191
# File 'lib/root.rb', line 189

def filepath(als, *paths)
  File.expand_path(File.join(self[als], *paths))
end

#glob(als, *patterns) ⇒ Object

Lists all files along the aliased path matching the input patterns. Patterns should join with the aliased path make valid globs for Dir.glob. If no patterns are specified, glob returns all paths matching ‘als/**/*’.



227
228
229
230
231
# File 'lib/root.rb', line 227

def glob(als, *patterns)
  patterns << "**/*" if patterns.empty?
  patterns.collect! {|pattern| filepath(als, pattern)}
  super(*patterns)
end

#prepare(als, *paths, &block) ⇒ Object

Constructs a path from the inputs (using filepath) and prepares it using Root.prepare. Returns the path.



247
248
249
# File 'lib/root.rb', line 247

def prepare(als, *paths, &block)
  super(filepath(als, *paths), &block)
end

#relative_filepath(als, path) ⇒ Object

Retrieves the filepath relative to the path of the specified alias.



194
195
196
# File 'lib/root.rb', line 194

def relative_filepath(als, path)
  super(self[als], path)
end

#relative_paths=(paths) ⇒ Object

Sets the relative_paths to those provided. ‘root’ and :root are reserved aliases and cannot be set using this method (use root= instead).

r = Root.new
r['alt']                            # => File.join(r.root, 'alt')
r.relative_paths = {'alt' => 'dir'}
r['alt']                            # => File.join(r.root, 'dir')


97
98
99
100
# File 'lib/root.rb', line 97

def relative_paths=(paths)
  paths = Validation::HASH[paths]
  assign_paths(root, paths, absolute_paths)
end

#root=(path) ⇒ Object

Sets the root directory. All paths are reassigned accordingly.



85
86
87
# File 'lib/root.rb', line 85

def root=(path)
  assign_paths(path, relative_paths, absolute_paths)
end

#subpath(als, *paths) ⇒ Object

Same as filepath but raises an error if the result is not a subpath of the aliased directory.



200
201
202
203
204
205
206
207
208
209
# File 'lib/root.rb', line 200

def subpath(als, *paths)
  dir = self[als]
  path = filepath(als, *paths)
  
  if path.rindex(dir, 0) != 0
    raise "not a subpath: #{path} (#{dir})"
  end
  
  path
end

#translate(path, source_als, target_als) ⇒ Object

Generates a filepath translated from the aliased source dir to the aliased target dir. Raises an error if the filepath is not relative to the source dir.

r = Root.new '/root_dir'
path = r.filepath(:in, 'path/to/file.txt')    # => '/root_dir/in/path/to/file.txt'
r.translate(path, :in, :out)                  # => '/root_dir/out/path/to/file.txt'


219
220
221
# File 'lib/root.rb', line 219

def translate(path, source_als, target_als)
  super(path, self[source_als], self[target_als])
end

#vglob(als, path, *vpatterns) ⇒ Object

Lists all versions of path in the aliased dir matching the version patterns. If no patterns are specified, then all versions of path will be returned.



236
237
238
# File 'lib/root.rb', line 236

def vglob(als, path, *vpatterns)
  super(filepath(als, path), *vpatterns)
end