lib/pa.rb
require "pd"
require "tmpdir"
=begin rdoc
Pa(Path) is similary to Pathname, but more powerful.
it combines fileutils, tmpdir, find, tempfile, File, Dir, Pathname
all class methods support Pa as parameter.
support "~/foo" path. Pa("~/foo") is "/home/x/foo"
Filename parts:
---------
/home/guten.ogg
dir: /home
base: guten.ogg
name: guten
ext: .ogg
fext: ogg
Examples:
---------
pa = Pa('/home/a.vim')
pa.dir2 -> '/home'
pa.base2 -> 'a.vim'
pa.name2 -> 'a'
pa.ext2 -> '.vim'
pa.dir -> Pa('/home') # similar, but return <#Pa>
Additional method list
---------------------
* Pa.absolute _alias from `File.absolute_path`_
* Pa.expand _aliss from `File.expand_path`_
=== create, modify path
Example1:
pa = Pa('/home/foo')
pa.join2('a.txt') -> '/home/foo/a.txt'
pa.join('a.txt') -> Pa('/home/foo/a.txt')
Example2:
pa1 = Pa('/home/foo/a.txt')
pa2 = Pa('/home/bar/b.txt')
pa1+'~' #=> new Pa('/home/foo/a.txt~')
Pa.join(pa1.dir, pa2.base) #=> '/home/foo/b.txt'
Example3:
pa1 = Pa('/home/foo/a.txt')
pa2 = Pa('/home/bar')
pa2.join2(pa1.base) -> '/home/bar/a.txt'
**Attributes**
name abbr description
path p
absolute a absolute path
dir d dirname of a path
base b basename of a path
ext e extname of a path
name n filename without ext
fext fe extname without "."
== used with rspec
File.exists?(path).should be_true
Pa(path).should be_exists
=end
class Pa
autoload :Util, "pa/util"
autoload :VERSION, "pa/version"
Error = Class.new Exception
EUnkonwType = Class.new Error
DELEGATE_CLASS_METHODS = [:absolute, :dir, :dir_stict, :name, :ext, :fext]
class << self
# get path of an object.
#
# return obj#path if object has a 'path' instance method
#
# nil -> nil
#
#
# @param [String,#path] obj
# @return [String,nil] path
def get(obj)
if String === obj
obj
elsif obj.respond_to?(:path)
obj.path
elsif obj.nil?
nil
else
raise ArgumentError, "Pa.get() not support type -- #{obj.inspect}(#{obj.class})"
end
end
def absolute2(name, dir=".")
File.absolute_path(get(name), dir)
end
# => ".", "..", "/", "c:"
#
# "foo" => "."
# "./foo" => "."
# "../../foo" => "../.."
#
def dir2(path)
File.dirname(get(path))
end
# Pa("foo") => ""
# Pa("./foo") => "."
def dir_strict2(path)
dir = File.dirname(get(path))
if %w[.].include?(dir) && path !~ %r~^\./~
""
else
dir
end
end
# get a basename of a path
#
# @example
# Pa.basename("foo.bar.c", ext: true) #=> \["foo.bar", "c"]
#
# @param [String,Pa] name
# @param [Hash] o options
# @option o [Boolean, String] :ext (false) return \[name, ext] if true
#
# @return [String] basename of a path unless o[:ext]
# @return [Array<String>] \[name, ext] if o[:ext].
def base2(name, o={})
name = File.basename(get(name))
if o[:ext]
name, ext = name.match(/^(.+?)(?:\.([^.]+))?$/).captures
[ name, (ext || "")]
else
name
end
end
def base(*args, &blk)
ret = base2(*args, &blk)
if Array === ret
[ Pa(ret[0]), ret[1] ]
else
Pa(ret)
end
end
def name2(path)
File.basename(get(path)).match(/^(.+?)(?:\.([^.]+))?$/)[1]
end
# -> ".ogg", ""
def ext2(path)
File.extname(get(path))
end
# Return path without ext.
#
# Pa.head2("/foo/a.txt") -> "/foo/a")
#
def head2(path)
p = get(path)
ext = File.extname(p)
ext.empty? ? p : p[0...-ext.length]
end
# => "ogg", ""
def fext2(path)
File.extname(get(path)).gsub(/^\./, "")
end
# split path
#
# @example
#
# path="/home/a/file"
# split2(path) -> ["/home/a", "file"]
# split2(path, :all => true) -> ["/", "home", "a", "file"]
#
# @param [String,Pa] path
# @param [Hash] o option
# @option o [Boolean] :all split all parts
# @return [Array<String>]
def split2(path, o={})
dir, base = File.split(get(path))
ret = Util.wrap_array(File.basename(base))
if o[:all]
loop do
dir1, base = File.split(dir)
break if dir1 == dir
ret.unshift base
dir = dir1
end
end
ret.unshift dir
ret
end
# special case
def split(*args)
dir, *names = split2(*args)
[ Pa(dir), *names]
end
# join paths, skip nil and empty string.
#
# @example
#
# Pa.join2("", "foo", nil, "bar") -> "foo/bar"
#
# @param [*Array<String>] *paths
# @return [String]
def join2(*paths)
paths.map!{|v|get(v)}
# skip nil
paths.compact!
# skip empty string
paths.delete("")
File.join(*paths)
end
DELEGATE_CLASS_METHODS.each {|meth|
eval <<-EOF
def #{meth}(*args, &blk)
Pa(#{meth}2(*args, &blk))
end
EOF
}
private
# wrap result to Pa
def _wrap(obj)
case obj
when Array
obj.map{|v| Pa(v)}
when String
Pa(obj)
end
end
end
DELEGATE_ATTR_METHODS2 = [ :dir2, :dir_strict2, :base2, :name2, :ext2, :fext2, :head2]
DELEGATE_ATTR_METHODS = [ :absolute, :dir, :dir_strict, :rel, :rea ]
DELEGATE_METHODS2 = [ :join2 ]
DELEGATE_METHODS = [ :change, :join]
DELEGATE_TO_PATH2 = [ :sub2, :gsub2 ]
DELEGATE_TO_PATH = [:match, :start_with?, :end_with?]
attr_reader :path2
attr_reader :absolute2, :dir2, :dir_strict2, :base2, :name2, :short2, :ext2, :fext2, :head2, :rel2, :rea2
attr_reader :options
# @param [Hash] o option
# @option o [String] rel relative path
# @option o [String] base_dir
# @param [String, #path] path
def initialize(path, o={})
@path2 = Pa.get(path)
# convert ~ to ENV["HOME"]
@path2.sub!(/^~/, ENV["HOME"].to_s) if @path2 # nil
@options = o
@base_dir = o[:base_dir] || "."
initialize_variables
end
chainable = Module.new do
def initialize_variables; end
end
include chainable
DELEGATE_ATTR_METHODS2.each {|meth2|
class_eval <<-EOF
def #{meth2}(*args, &blk)
@#{meth2} ||= Pa.#{meth2}(path, *args, &blk)
end
EOF
}
DELEGATE_ATTR_METHODS.each {|meth|
class_eval <<-EOF
def #{meth}(*args, &blk)
@#{meth} ||= Pa(#{meth}2(*args, &blk))
end
EOF
}
DELEGATE_METHODS2.each { |meth2|
class_eval <<-EOF
def #{meth2}(*args, &blk)
Pa.#{meth2}(path, *args, &blk)
end
EOF
}
DELEGATE_METHODS.each {|meth|
class_eval <<-EOF
def #{meth}(*args, &blk)
Pa(#{meth}2(*args, &blk))
end
EOF
}
DELEGATE_TO_PATH2.each {|meth2|
class_eval <<-EOF
def #{meth2}(*args, &blk)
path.#{meth2[0...-1]}(*args, &blk)
end
EOF
}
DELEGATE_TO_PATH.each {|meth|
class_eval <<-EOF
def #{meth}(*args, &blk)
path.#{meth}(*args, &blk)
end
EOF
}
def base_dir
@base_dir ||= (options[:base_dir] || ".")
end
def rel2
@rel2 ||= (options[:rel] || "" )
end
def rea2
@rea2 ||= options[:base_dir] ? File.join(base_dir, path) : path
end
def absolute2
@absolute2 ||= Pa.absolute2(rea2)
end
# both x, x2 return String
alias path path2
alias base base2
alias name name2
alias ext ext2
alias fext fext2
# abbreviate
alias p2 path2
alias p2 path
alias a2 absolute2
alias d2 dir2
alias d_s2 dir_strict2
alias b2 base2
alias n2 name2
alias e2 ext2
alias fe2 fext2
alias p path
alias b base
alias n name
alias e ext
alias fe fext
alias a absolute
alias d dir
alias d_s dir_strict
# return '#<Pa @path="foo", @absolute="/home/foo">'
#
# @return [String]
def inspect
ret="#<" + self.class.to_s + " "
ret += "@path=\"#{path}\", @absolute2=\"#{absolute2}\""
ret += " >"
ret
end
# return '/home/foo'
#
# @return [String] path
def to_s
path
end
# @param [String,#path]
# @return [Pa] the same Pa object
def replace(path)
@path2 = Pa.get(path)
initialize_variables
end
def ==(other)
case other
when Pa
self.path == other.path
else
false
end
end
def <=>(other)
path <=> Pa.get(other)
end
def =~(regexp)
path =~ regexp
end
# add string to path
#
# @example
# pa = Pa('/home/foo/a.txt')
# pa+'~' #=> new Pa('/home/foo/a.txt~')
#
# @param [String] str
# @return [Pa]
def +(str)
Pa(path+str)
end
def short2
@short2 ||= Pa.shorten2(@path)
end
def short
@short ||= Pa(short2)
end
# @return [Pa]
def sub(*args, &blk)
Pa(sub2(*args, &blk))
end
# @return [Pa]
def gsub(*args, &blk)
Pa(gsub2(*args, &blk))
end
# @return [Pa]
def sub!(*args,&blk)
replace path.sub(*args,&blk)
end
# @return [Pa]
def gsub!(*args,&blk)
replace path.gsub(*args,&blk)
end
# Change some parts of the path.
#
# path
# dir base
# dir name ext
# ...
#
# @return [String] path
def change2(data={}, &blk)
return Pa.new(blk.call(self)) if blk
if data[:path]
return data[:path]
elsif data[:base]
return File.join(data[:dir] || dir2, data[:base])
else
dir, name, ext = data[:dir] || dir2, data[:name] || name2, data[:ext] || ext2
File.join(dir, name)+ext
end
end
end
require "pa/path"
require "pa/cmd"
require "pa/directory"
require "pa/state"
class Pa
include Path
include Directory
include State
include Cmd
end
module Kernel
private
# a very convient function.
#
# @example
#
# Pa('/home').exists?
def Pa(path, o={})
return path if Pa===path
Pa.new path, o
end
end