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