require 'fileutils' require 'pathname' require 'uri' module Smash module CloudPowers module PathHelp # Offer a common "path" delimiter. This is designed to allow Storage keep # a common delimiter. It's not required but it makes things a little more # human friendly # # Returns # +String+ def common_delimiter '/'.freeze end # Expand a +Pathname variable # # Parameters # * arg +String+ # # Returns # * +Pathname+ - Absolute path # # Notes: # * This method doesn't guarantee that the path actually exists; It just # gives you a correct, absolute path def expand_path(arg) to_pathname(arg).expand_path end # Check if the URL exists # # Parameters # * file +String+ # # Returns # +Boolean+ def file_exists?(*args) File.exist?(*args.join('/').to_s) end # Determine if this path or name seems like a file or a directory # # Parameters # * +Pathname+|+String+ # # Returns # * +Boolean+ def filename?(pathname) !!(/[A-Za-z]*\.[A-Za-z]+$/ =~ pathname.to_s) end # Check if the job file exists in the job directory # # Parameters # * file +String+ # # Returns # +Boolean+ # # Notes # * See #job_home() # * See #path_exists?() def job_exist?(file) file_exists?(job_home, file) end # Gives the path from the project root to lib/jobs[/#{file}.rb] # # Parameters # * file +String+ (optional) (default is '') - name of a file # # Returns # * path/file +String+ if +file+ parameter is given. return has # '.rb' extension included # * file +String+ if +file+ parameter is not given it will return the # #job_require_path() # # Notes # * See #job_home def job_path(file = '') return job_home if file.empty? to_pathname(job_home, file) end # Gives the path from the project root to lib/jobs[/file] # # Parameters String (optional) # * file_name name of a file # # Returns # * path/file +String+ if +file_name+ was given # * path to job_directory if +file_name+ was not given # # Notes # * Neither path nor file will have a file extension # * See #job_home def job_require_path(file_name = '') # TODO: refactor to use Pathname as much as possible begin file_sans_extension = File.basename(file_name, '.*') (Pathname.new(job_home) + file_sans_extension).to_s rescue Errno::ENOENT nil end end # Find all directories that match the given string. # # Returns # * +Pathname+ - absolute path # # Notes # * This method is su-looooOOOOOOOw but it's pretty thorough. Fixing that # while keeping it thorough is a high priority but it works. Your best # bet is to try to set the location for things you're looking for, before # you use them. Another alternative is to use a jailed root to run your # project from. def file_search(name, scope = '/**') Pathname.glob("#{scope}/#{name}").select(&:exist?).reject(&:directory?).map(&:realpath) end def paths_lcd(*paths) unpacked_paths = paths.map { |path| path.to_s.split('/') }.sort shortest_path_dirs = unpacked_paths.first.count # this is the shortest path common_path = [] (0..shortest_path_dirs).each do |i| if unpacked_paths.first[i] == unpacked_paths.last[i] common_path << unpacked_paths.first[i] end end to_pathname(common_path.join('/')) end def paths_gcd(*paths) start_from = paths_lcd(*paths) paths.sort.last.relative_path_from(start_from) end # Find all directories that match the given string. # # Returns # * +Pathname+ - absolute path # # Notes # * This method is su-looooOOOOOOOw but it's pretty thorough. Fixing that # while keeping it thorough is a high priority but it works. Your best # bet is to try to set the location for things you're looking for, before # you use them. Another alternative is to use a jailed root to run your # project from. def path_search(name, scope = default_scope) Pathname.glob("#{scope}/#{name}").select(&:exist?).select(&:directory?).map(&:realpath) end # Convert a list of arguments to a path-like +String+ object. # # Parameters # * [+Object+[,...]] # # Returns # * +String+ def to_path(*args) to_pathname(*args).to_s end # Convert a list of arguments to a +Pathname+ object. # # Parameters # * [+String+[,...]] # # Returns # * +Pathname+ def to_pathname(*args) args = args.flatten.compact # #join() will create a path that looks like this `//your/path` if the # first argument is the same as the path seperator on your system. first = /^\/+\W*$/ =~ args.first.to_s ? args.shift : '' path_string = args.join('/') path_string = first + path_string Pathname.new(path_string) end # Create and/or get the full path to the argument(s) # # Parameters # * +String+(s) - a path or chunks of a path, in order, as +String+s # # Returns # * +Pathname+ # # Notes: # * !This method creates paths, even nested paths, if they don't exist! # * !Use expand_path if you don't want to create anything but still # need the full path # * See #to_pathname # * See #touch def to_realpath(*args) begin pathname = to_pathname(*args) pathname.realpath rescue Errno::ENOENT => e logger.debug("#to_realpath(#{pathname}) called from " + "#{caller.first} but the path doesn't exist") to_realpath(touch(pathname)) end end # Create a file or directory. You don't have to worry if the path exists. # # Parameters # * path +Pathname+|+String+ - path that you would like created # # Returns # * +Pathname+ - path to your new object # # Notes: # * This method assumes you are careful enough not to break your machine # because you might accidentally over-write an existing, important path # with an empty file or directory. That's no different than any other # library, like +FileUtils+ or +Pathname+ but it's worht being said, # just for good measure. def touch(path) pathname = to_pathname(path) filename?(pathname) ? ::FileUtils.touch(pathname) : ::FileUtils.mkdir_p(pathname) to_realpath(pathname) end # Offer a common place to operate from, so CloudPowers stays tidy. # This method will find out if your project responds to a method called # 'project_root'. If the method exist, it uses it, otherwise, your new # files etc. will be places at `pwd`/zlib. # # Returns # * +Pathname+ # # Notes: # * See to_realpath def zlib_path(object = '') return zlib_home if (object.nil? || object.empty?) to_path(zlib_home, object) end private def has_project_root_defined? self.respond_to? :project_root end # Gives a common home for jobs to live so they can be easily grouped and # found. This method will create nested directories, based on the # #project_root() method and an additional 'lib/jobs' directory. # If no project root has been set by the time this method is called, a new # directory will be created relative to the gem's project root. # # Returns # +Pathname+ def job_home path_string = "#{zlib_path}/jobs" @job_home ||= path_search(path_string).first || to_realpath(path_string) end # Gives a common home for this project to store or read from. This method # will create nested directories, based on the #project_root # method if it exists, or it will create the +zlib+ directory wherever this # method was called from. # # Returns # * +Pathname+ # # Notes # * # If no project root has been set by the time this method is called, a new # directory will be created relative to the gem's project root. This might # have deeper implications than you want to deal with so it's always a good # idea to set your project root as soon as you can. # * TODO: find a way to have this method figure out the actual project's # root, as opposed to just making common "good" assumptions. def zlib_home @zlib_home ||= to_realpath( has_project_root_defined? ? project_root + 'zlib' : './zlib' ) end private def default_scope if self.respond_to? :origin origin elsif self.respond_to? :project_root project_root || proc_cwd || ps_cwd else to_realpath(called_from).parent end end end end end