diff options
author | John Keiser <john@johnkeiser.com> | 2015-05-06 07:59:33 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-06-02 09:53:39 -0700 |
commit | 9028823f7b046c4c081b1cb1df005d61fbfa1db2 (patch) | |
tree | 74d1fa52adb202a6841148be3b82638a578b1b4e /lib/chef | |
parent | 93f7b74349362d0c698a1080177b94e64248dac6 (diff) | |
download | chef-9028823f7b046c4c081b1cb1df005d61fbfa1db2.tar.gz |
Use the central priority map for `provides`
Diffstat (limited to 'lib/chef')
-rw-r--r-- | lib/chef/chef_class.rb | 33 | ||||
-rw-r--r-- | lib/chef/client.rb | 7 | ||||
-rw-r--r-- | lib/chef/mixin/provides.rb | 27 | ||||
-rw-r--r-- | lib/chef/node_map.rb | 18 | ||||
-rw-r--r-- | lib/chef/platform/provider_priority_map.rb | 72 | ||||
-rw-r--r-- | lib/chef/platform/resource_priority_map.rb | 19 | ||||
-rw-r--r-- | lib/chef/provider.rb | 13 | ||||
-rw-r--r-- | lib/chef/provider/file.rb | 1 | ||||
-rw-r--r-- | lib/chef/provider/package.rb | 5 | ||||
-rw-r--r-- | lib/chef/provider/service.rb | 74 | ||||
-rw-r--r-- | lib/chef/provider/service/init.rb | 1 | ||||
-rw-r--r-- | lib/chef/resource.rb | 10 | ||||
-rw-r--r-- | lib/chef/resource/package.rb | 5 | ||||
-rw-r--r-- | lib/chef/resource_resolver.rb | 5 | ||||
-rw-r--r-- | lib/chef/util/path_helper.rb | 205 |
15 files changed, 366 insertions, 129 deletions
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index d3f7ee55c7..96c0899d5d 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -26,6 +26,9 @@ # injected" into this class by other objects and do not reference the class symbols in those files # directly and we do not need to require those files here. +require 'chef/platform/provider_priority_map' +require 'chef/platform/resource_priority_map' + class Chef class << self @@ -48,7 +51,7 @@ class Chef # @param resource_name [Symbol] name of the resource as a symbol # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node def get_provider_priority_array(resource_name) - @provider_priority_map.get_priority_array(node, resource_name).dup + provider_priority_map.get_priority_array(node, resource_name).dup end # Get the array of resources associated with a resource_name for the current node @@ -56,27 +59,27 @@ class Chef # @param resource_name [Symbol] name of the resource as a symbol # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node def get_resource_priority_array(resource_name) - @resource_priority_map.get_priority_array(node, resource_name).dup + resource_priority_map.get_priority_array(node, resource_name).dup end # Set the array of providers associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol - # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node + # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node # @param filter [Hash] Chef::Nodearray-style filter # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node def set_provider_priority_array(resource_name, priority_array, *filter) - @provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup + provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup end # Get the array of resources associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol - # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node + # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node # @param filter [Hash] Chef::Nodearray-style filter # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node def set_resource_priority_array(resource_name, priority_array, *filter) - @resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup + resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup end # @@ -126,5 +129,23 @@ class Chef @provider_priority_map = nil @resource_priority_map = nil end + + private + + def provider_priority_map + @provider_priority_map ||= begin + # these slurp in the resource+provider world, so be exceedingly lazy about requiring them + require 'chef/platform/provider_priority_map' + Chef::Platform::ProviderPriorityMap.instance + end + end + def resource_priority_map + @resource_priority_map ||= begin + require 'chef/platform/resource_priority_map' + Chef::Platform::ResourcePriorityMap.instance + end + end end + + reset! end diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 141827e65b..86e92585e3 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -159,13 +159,6 @@ class Chef if new_runlist = args.delete(:runlist) @json_attribs["run_list"] = new_runlist end - - # these slurp in the resource+provider world, so be exceedingly lazy about requiring them - require 'chef/platform/provider_priority_map' unless defined? Chef::Platform::ProviderPriorityMap - require 'chef/platform/resource_priority_map' unless defined? Chef::Platform::ResourcePriorityMap - - Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance) - Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance) end # diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb index c39c53a190..c95cb753a8 100644 --- a/lib/chef/mixin/provides.rb +++ b/lib/chef/mixin/provides.rb @@ -4,30 +4,21 @@ require 'chef/mixin/descendants_tracker' class Chef module Mixin module Provides + # TODO no longer needed, remove or deprecate? include Chef::Mixin::DescendantsTracker - def node_map - @node_map ||= Chef::NodeMap.new - end - def provides(short_name, opts={}, &block) - if !short_name.kind_of?(Symbol) - # YAGNI: this is probably completely unnecessary and can be removed? - Chef::Log.deprecation "Passing a non-Symbol to Chef::Resource#provides will be removed" - if short_name.kind_of?(String) - short_name.downcase! - short_name.gsub!(/\s/, "_") - end - short_name = short_name.to_sym - end - node_map.set(short_name, true, opts, &block) + provides_priority_map.priority(short_name, self, opts, &block) end # Check whether this resource provides the resource_name DSL for the given - # node - def provides?(node, resource_name) - resource_name = resource_name.resource_name if resource_name.is_a?(Chef::Resource) - node_map.get(node, resource_name) + # node. + def provides?(node, short_name) + provides_priority_map.list(node, short_name).include?(self) + end + + def provides_priority_map + raise NotImplementedError, :provides_priority_map end # Get the list of recipe DSL this resource is responsible for on the given diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index 2ca6d9ba17..7c25cb841a 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -68,14 +68,16 @@ class Chef def get(node, key) # FIXME: real exception raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) - return nil unless @map.has_key?(key) - @map[key].each do |matcher| - if filters_match?(node, matcher[:filters]) && - block_matches?(node, matcher[:block]) - return matcher[:value] - end - end - nil + list(node, key).first + end + + def list(node, key) + # FIXME: real exception + raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) + return [] unless @map.has_key?(key) + @map[key].select do |matcher| + filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block]) + end.map { |matcher| matcher[:value] } end private diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb index 1539f61900..2d250b5006 100644 --- a/lib/chef/platform/provider_priority_map.rb +++ b/lib/chef/platform/provider_priority_map.rb @@ -1,88 +1,28 @@ +require 'singleton' class Chef class Platform class ProviderPriorityMap include Singleton - def initialize - load_default_map - end - def get_priority_array(node, resource_name) priority_map.get(node, resource_name.to_sym) end def set_priority_array(resource_name, priority_array, *filter) - priority(resource_name.to_sym, priority_array.to_a, *filter) + priority(resource_name.to_sym, Array(priority_array), *filter) end def priority(*args) priority_map.set(*args) end - private - - def load_default_map - require 'chef/providers' - - # - # Linux - # - - # default block for linux O/Sen must come before platform_family exceptions - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Insserv, - Chef::Provider::Service::Redhat, - ], os: "linux" - - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Arch, - ], platform_family: "arch" - - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Gentoo, - ], platform_family: "gentoo" - - priority :service, [ - # we can determine what systemd supports accurately - Chef::Provider::Service::Systemd, - # on debian-ish system if an upstart script exists that must win over sysv types - Chef::Provider::Service::Upstart, - Chef::Provider::Service::Insserv, - Chef::Provider::Service::Debian, - Chef::Provider::Service::Invokercd, - ], platform_family: "debian" - - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Insserv, - Chef::Provider::Service::Redhat, - ], platform_family: [ "rhel", "fedora", "suse" ] - - # - # BSDen - # - - priority :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ] - priority :service, Chef::Provider::Service::Openbsd, os: [ "openbsd" ] - - # - # Solaris-en - # - - priority :service, Chef::Provider::Service::Solaris, os: "solaris2" - - # - # Mac - # - - priority :service, Chef::Provider::Service::Macosx, os: "darwin" - priority :package, Chef::Provider::Package::Homebrew, os: "darwin" + def list(node, resource_name) + priority_map.list(node, resource_name).flatten(1).uniq end + private + def priority_map require 'chef/node_map' @priority_map ||= Chef::NodeMap.new diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb index fc43b3e7db..e692cbb800 100644 --- a/lib/chef/platform/resource_priority_map.rb +++ b/lib/chef/platform/resource_priority_map.rb @@ -1,33 +1,28 @@ +require 'singleton' + class Chef class Platform class ResourcePriorityMap include Singleton - def initialize - load_default_map - end - def get_priority_array(node, resource_name) priority_map.get(node, resource_name.to_sym) end def set_priority_array(resource_name, priority_array, *filter) - priority resource_name.to_sym, priority_array.to_a, *filter + priority resource_name.to_sym, Array(priority_array), *filter end def priority(*args) priority_map.set(*args) end - private - - def load_default_map - require 'chef/resources' - - # MacOSX - priority :package, Chef::Resource::HomebrewPackage, os: "darwin" + def list(*args) + priority_map.list(*args).flatten(1).uniq end + private + def priority_map require 'chef/node_map' @priority_map ||= Chef::NodeMap.new diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 42347a230e..52082123cb 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -29,6 +29,9 @@ require 'chef/node_map' class Chef class Provider + require 'chef/mixin/why_run' + require 'chef/mixin/shell_out' + require 'chef/mixin/provides' include Chef::Mixin::WhyRun include Chef::Mixin::ShellOut include Chef::Mixin::PowershellOut @@ -174,6 +177,10 @@ class Chef protected + def self.provides_priority_map + Chef::Platform::ResourcePriorityMap.instance + end + def converge_actions @converge_actions ||= ConvergeActions.new(@new_resource, run_context, @action) end @@ -228,3 +235,9 @@ class Chef extend DeprecatedLWRPClass end end + +# Requiring things at the bottom breaks cycles +require 'chef/chef_class' +require 'chef/mixin/why_run' +require 'chef/resource_collection' +require 'chef/runner' diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb index 4ce2c040a4..5ed7c6ac5b 100644 --- a/lib/chef/provider/file.rb +++ b/lib/chef/provider/file.rb @@ -26,6 +26,7 @@ require 'fileutils' require 'chef/scan_access_control' require 'chef/mixin/checksum' require 'chef/mixin/file_class' +require 'chef/mixin/enforce_ownership_and_permissions' require 'chef/util/backup' require 'chef/util/diff' require 'chef/util/selinux' diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index bdc96cd070..86c158f23d 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -493,3 +493,8 @@ class Chef end end end + +require 'chef/chef_class' +require 'chef/provider/package/homebrew' + +Chef.set_provider_priority_array :package, Chef::Provider::Package::Homebrew, os: "darwin" diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index 75da2ddb31..4cd7f335dd 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -171,3 +171,77 @@ class Chef end end end + +# +# Platform-specific versions +# + +# +# Linux +# + +require 'chef/chef_class' +require 'chef/provider/service/systemd' +require 'chef/provider/service/insserv' +require 'chef/provider/service/redhat' +require 'chef/provider/service/arch' +require 'chef/provider/service/gentoo' +require 'chef/provider/service/upstart' +require 'chef/provider/service/debian' +require 'chef/provider/service/invokercd' +require 'chef/provider/service/freebsd' +require 'chef/provider/service/openbsd' +require 'chef/provider/service/solaris' +require 'chef/provider/service/macosx' + +# default block for linux O/Sen must come before platform_family exceptions +Chef.set_provider_priority_array :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Insserv, + Chef::Provider::Service::Redhat, +], os: "linux" + +Chef.set_provider_priority_array :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Arch, +], platform_family: "arch" + +Chef.set_provider_priority_array :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Gentoo, +], platform_family: "gentoo" + +Chef.set_provider_priority_array :service, [ + # we can determine what systemd supports accurately + Chef::Provider::Service::Systemd, + # on debian-ish system if an upstart script exists that must win over sysv types + Chef::Provider::Service::Upstart, + Chef::Provider::Service::Insserv, + Chef::Provider::Service::Debian, + Chef::Provider::Service::Invokercd, +], platform_family: "debian" + +Chef.set_provider_priority_array :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Insserv, + Chef::Provider::Service::Redhat, +], platform_family: [ "rhel", "fedora", "suse" ] + +# +# BSDen +# + +Chef.set_provider_priority_array :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ] +Chef.set_provider_priority_array :service, Chef::Provider::Service::Openbsd, os: [ "openbsd" ] + +# +# Solaris-en +# + +Chef.set_provider_priority_array :service, Chef::Provider::Service::Solaris, os: "solaris2" + +# +# Mac +# + +Chef.set_provider_priority_array :service, Chef::Provider::Service::Macosx, os: "darwin" diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb index 0a219a69e1..355e98a0eb 100644 --- a/lib/chef/provider/service/init.rb +++ b/lib/chef/provider/service/init.rb @@ -18,6 +18,7 @@ require 'chef/provider/service/simple' require 'chef/mixin/command' +require 'chef/platform/service_helpers' class Chef class Provider diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 070793a7a2..686b212e49 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -1103,7 +1103,7 @@ class Chef end end - def self.provides(name, *args, &block) + def self.provides(name, opts={}, &block) result = super Chef::DSL::Resources.add_resource_dsl(name) result @@ -1270,6 +1270,12 @@ class Chef end end + protected + + def self.provides_priority_map + Chef::Platform::ResourcePriorityMap.instance + end + # Implement deprecated LWRP class module DeprecatedLWRPClass # @api private @@ -1290,8 +1296,6 @@ class Chef end end - private - def deprecated_constants @deprecated_constants ||= {} end diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb index 4148144816..0944b5e9d8 100644 --- a/lib/chef/resource/package.rb +++ b/lib/chef/resource/package.rb @@ -102,3 +102,8 @@ class Chef end end end + +require 'chef/chef_class' +require 'chef/resource/homebrew_package' + +Chef.set_resource_priority_array :package, Chef::Resource::HomebrewPackage, os: "darwin" diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index a987b236c2..47d98154a8 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -75,11 +75,6 @@ class Chef # this magic stack ranks the resources by where they appear in the resource_priority_map priority_list = [ get_priority_array(node, resource) ].flatten.compact handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i } - if priority_list.index(handlers.first).nil? - # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map - # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity. - Chef::Log.warn "Ambiguous resource precedence: #{handlers}, please use Chef.set_resource_priority_array to provide determinism" - end handlers = handlers[0..0] end diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb index 10527f8906..1fddfed9b8 100644 --- a/lib/chef/util/path_helper.rb +++ b/lib/chef/util/path_helper.rb @@ -16,11 +16,208 @@ # limitations under the License. # -require 'chef-config/path_helper' - class Chef class Util - PathHelper = ChefConfig::PathHelper + class PathHelper + # Maximum characters in a standard Windows path (260 including drive letter and NUL) + WIN_MAX_PATH = 259 + + def self.dirname(path) + if Chef::Platform.windows? + # Find the first slash, not counting trailing slashes + end_slash = path.size + loop do + slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1) + if !slash + return end_slash == path.size ? '.' : path_separator + elsif slash == end_slash - 1 + end_slash = slash + else + return path[0..slash-1] + end + end + else + ::File.dirname(path) + end + end + + BACKSLASH = '\\'.freeze + + def self.path_separator + if Chef::Platform.windows? + File::ALT_SEPARATOR || BACKSLASH + else + File::SEPARATOR + end + end + + def self.join(*args) + args.flatten.inject do |joined_path, component| + # Joined path ends with / + joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '') + component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '') + joined_path += "#{path_separator}#{component}" + end + end + + def self.validate_path(path) + if Chef::Platform.windows? + unless printable?(path) + msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings." + Chef::Log.error(msg) + raise Chef::Exceptions::ValidationFailed, msg + end + + if windows_max_length_exceeded?(path) + Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'") + path.insert(0, "\\\\?\\") + end + end + + path + end + + def self.windows_max_length_exceeded?(path) + # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + unless path =~ /^\\\\?\\/ + if path.length > WIN_MAX_PATH + return true + end + end + + false + end + + def self.printable?(string) + # returns true if string is free of non-printable characters (escape sequences) + # this returns false for whitespace escape sequences as well, e.g. \n\t + if string =~ /[^[:print:]]/ + false + else + true + end + end + + # Produces a comparable path. + def self.canonical_path(path, add_prefix=true) + # First remove extra separators and resolve any relative paths + abs_path = File.absolute_path(path) + + if Chef::Platform.windows? + # Add the \\?\ API prefix on Windows unless add_prefix is false + # Downcase on Windows where paths are still case-insensitive + abs_path.gsub!(::File::SEPARATOR, path_separator) + if add_prefix && abs_path !~ /^\\\\?\\/ + abs_path.insert(0, "\\\\?\\") + end + + abs_path.downcase! + end + + abs_path + end + + def self.cleanpath(path) + path = Pathname.new(path).cleanpath.to_s + # ensure all forward slashes are backslashes + if Chef::Platform.windows? + path = path.gsub(File::SEPARATOR, path_separator) + end + path + end + + def self.paths_eql?(path1, path2) + canonical_path(path1) == canonical_path(path2) + end + + # Paths which may contain glob-reserved characters need + # to be escaped before globbing can be done. + # http://stackoverflow.com/questions/14127343 + def self.escape_glob(*parts) + path = cleanpath(join(*parts)) + path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x } + end + + def self.relative_path_from(from, to) + pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from))) + end + + # Retrieves the "home directory" of the current user while trying to ascertain the existence + # of said directory. The path returned uses / for all separators (the ruby standard format). + # If the home directory doesn't exist or an error is otherwise encountered, nil is returned. + # + # If a set of path elements is provided, they are appended as-is to the home path if the + # homepath exists. + # + # If an optional block is provided, the joined path is passed to that block if the home path is + # valid and the result of the block is returned instead. + # + # Home-path discovery is performed once. If a path is discovered, that value is memoized so + # that subsequent calls to home_dir don't bounce around. + # + # See self.all_homes. + def self.home(*args) + @@home_dir ||= self.all_homes { |p| break p } + if @@home_dir + path = File.join(@@home_dir, *args) + block_given? ? (yield path) : path + end + end + + # See self.home. This method performs a similar operation except that it yields all the different + # possible values of 'HOME' that one could have on this platform. Hence, on windows, if + # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice. + # This method goes out and checks the existence of each location at the time of the call. + # + # The return is a list of all the returned values from each block invocation or a list of paths + # if no block is provided. + def self.all_homes(*args) + paths = [] + if Chef::Platform.windows? + # By default, Ruby uses the the following environment variables to determine Dir.home: + # HOME + # HOMEDRIVE HOMEPATH + # USERPROFILE + # Ruby only checks to see if the variable is specified - not if the directory actually exists. + # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive) + # while USERPROFILE points to the location where the user application settings and profile are stored. HOME + # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is + # HOMESHARE instead of HOMEDRIVE. + # + # We instead walk down the following and only include paths that actually exist. + # HOME + # HOMEDRIVE HOMEPATH + # HOMESHARE HOMEPATH + # USERPROFILE + + paths << ENV['HOME'] + paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] + paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH'] + paths << ENV['USERPROFILE'] + end + paths << Dir.home if ENV['HOME'] + + # Depending on what environment variables we're using, the slashes can go in any which way. + # Just change them all to / to keep things consistent. + # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on + # the particular brand of kool-aid you consume. This code assumes that \ and / are both + # path separators on any system being used. + paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path } + + # Filter out duplicate paths and paths that don't exist. + valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) } + valid_paths = valid_paths.uniq + + # Join all optional path elements at the end. + # If a block is provided, invoke it - otherwise just return what we've got. + joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) } + if block_given? + joined_paths.each { |p| yield p } + else + joined_paths + end + end + end end end - |