diff options
Diffstat (limited to 'lib/chef/chef_fs/path_utils.rb')
-rw-r--r-- | lib/chef/chef_fs/path_utils.rb | 99 |
1 files changed, 65 insertions, 34 deletions
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb index 9ef75ce2e5..595f966378 100644 --- a/lib/chef/chef_fs/path_utils.rb +++ b/lib/chef/chef_fs/path_utils.rb @@ -23,31 +23,31 @@ class Chef module ChefFS class PathUtils - # If you are in 'source', this is what you would have to type to reach 'dest' - # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e' - # relative_to('/a/b', '/a/b') == '.' - def self.relative_to(dest, source) - # Skip past the common parts - source_parts = Chef::ChefFS::PathUtils.split(source) - dest_parts = Chef::ChefFS::PathUtils.split(dest) - i = 0 - until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i] - i+=1 - end - # dot-dot up from 'source' to the common ancestor, then - # descend to 'dest' from the common ancestor - result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i])) - result == '' ? '.' : result - end + # A Chef-FS path is a path in a chef-repository that can be used to address + # both files on a local file-system as well as objects on a chef server. + # These paths are stricter than file-system paths allowed on various OSes. + # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well). + # "/" is used as the path element separator (on windows, "\" is acceptable as well). + # No directory/path element may contain a literal "\" character. Any such characters + # encountered are either dealt with as separators (on windows) or as escape + # characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but + # may never use these to back-out of the root of a Chef-FS path. Any such extraneous + # ".."s are ignored. + # Chef-FS paths are case sensitive (since the paths on the server are). + # On OSes with case insensitive paths, you may be unable to locally deal with two + # objects whose server paths only differ by case. OTOH, the case of path segments + # that are outside the Chef-FS root (such as when looking at a file-system absolute + # path to discover the Chef-FS root path) are handled in accordance to the rules + # of the local file-system and OS. def self.join(*parts) return "" if parts.length == 0 # Determine if it started with a slash absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/ # Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away) - parts = parts.map { |part| part.gsub(/^\/|\/$/, "") } + parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') } # Don't join empty bits - result = parts.select { |part| part != "" }.join("/") + result = parts.select { |part| part != '' }.join('/') # Put the / back on absolute ? "/#{result}" : result end @@ -60,36 +60,67 @@ class Chef Chef::ChefFS::windows? ? '[\/\\\\]' : '/' end + # Given a server path, determines if it is absolute. + def self.is_absolute?(path) + !!(path =~ /^#{regexp_path_separator}/) + end # Given a path which may only be partly real (i.e. /x/y/z when only /x exists, # or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest - # part that actually exists. + # part that actually exists. The paths operated on here are not Chef-FS paths. + # These are OS paths that may contain symlinks but may not also fully exist. # # If /x is a symlink to /blarghle, and has no subdirectories, then: # PathUtils.realest_path('/x/y/z') == '/blarghle/y/z' # PathUtils.realest_path('/x/*/z') == '/blarghle/*/z' # PathUtils.realest_path('/*/y/z') == '/*/y/z' - def self.realest_path(path) - path = Pathname.new(path) - begin - path.realpath.to_s - rescue Errno::ENOENT - dirname = path.dirname - if dirname - PathUtils.join(realest_path(dirname), path.basename.to_s) - else - path.to_s + # + # TODO: Move this to wherever util/path_helper is these days. + def self.realest_path(path, cwd = Dir.pwd) + path = File.expand_path(path, cwd) + parent_path = File.dirname(path) + suffix = [] + + # File.dirname happens to return the path as its own dirname if you're + # at the root (such as at \\foo\bar, C:\ or /) + until parent_path == path do + # This can occur if a path such as "C:" is given. Ruby gives the parent as "C:." + # for reasons only it knows. + raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length + begin + path = File.realpath(path) + break + rescue Errno::ENOENT + suffix << File.basename(path) + path = parent_path + parent_path = File.dirname(path) end end + File.join(path, *suffix.reverse) end - def self.descendant_of?(path, ancestor) - path[0,ancestor.length] == ancestor && - (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/) + # Compares two path fragments according to the case-sentitivity of the host platform. + def self.os_path_eq?(left, right) + Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right end - def self.is_absolute?(path) - path =~ /^#{regexp_path_separator}/ + # Given two general OS-dependent file paths, determines the relative path of the + # child with respect to the ancestor. Both child and ancestor must exist and be + # fully resolved - this is strictly a lexical comparison. No trailing slashes + # and other shenanigans are allowed. + # + # TODO: Move this to util/path_helper. + def self.descendant_path(path, ancestor) + candidate_fragment = path[0, ancestor.length] + return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor) + if ancestor.length == path.length + '' + elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/ + path[ancestor.length+1..-1] + else + nil + end end + end end end |