#
# = pathname.rb
#
# Object-Oriented Pathname Class
#
# Author:: Tanaka Akira <akr@m17n.org>
# Documentation:: Author and Gavin Sinclair
#
# For documentation, see class Pathname.
#
# <tt>pathname.rb</tt> is distributed with Ruby since 1.8.0.
#
require 'pathname_ext'
class Pathname
# :stopdoc:
if RUBY_VERSION < "1.9"
TO_PATH = :to_str
else
# to_path is implemented so Pathname objects are usable with File.open, etc.
TO_PATH = :to_path
end
SAME_PATHS = if File::FNM_SYSCASE.nonzero?
proc {|a, b| a.casecmp(b).zero?}
else
proc {|a, b| a == b}
end
# :startdoc:
if File::ALT_SEPARATOR
SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}"
SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
else
SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}"
SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
end
# chop_basename(path) -> [pre-basename, basename] or nil
def chop_basename(path)
base = File.basename(path)
if /\A#{SEPARATOR_PAT}?\z/o =~ base
return nil
else
return path[0, path.rindex(base)], base
end
end
private :chop_basename
# split_names(path) -> prefix, [name, ...]
def split_names(path)
names = []
while r = chop_basename(path)
path, basename = r
names.unshift basename
end
return path, names
end
private :split_names
def prepend_prefix(prefix, relpath)
if relpath.empty?
File.dirname(prefix)
elsif /#{SEPARATOR_PAT}/o =~ prefix
prefix = File.dirname(prefix)
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
prefix + relpath
else
prefix + relpath
end
end
private :prepend_prefix
# Returns clean pathname of +self+ with consecutive slashes and useless dots
# removed. The filesystem is not accessed.
#
# If +consider_symlink+ is +true+, then a more conservative algorithm is used
# to avoid breaking symbolic linkages. This may retain more <tt>..</tt>
# entries than absolutely necessary, but without accessing the filesystem,
# this can't be avoided. See #realpath.
#
def cleanpath(consider_symlink=false)
if consider_symlink
cleanpath_conservative
else
cleanpath_aggressive
end
end
#
# Clean the path simply by resolving and removing excess "." and ".." entries.
# Nothing more, nothing less.
#
def cleanpath_aggressive
path = @path
names = []
pre = path
while r = chop_basename(pre)
pre, base = r
case base
when '.'
when '..'
names.unshift base
else
if names[0] == '..'
names.shift
else
names.unshift base
end
end
end
if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
names.shift while names[0] == '..'
end
self.class.new(prepend_prefix(pre, File.join(*names)))
end
private :cleanpath_aggressive
# has_trailing_separator?(path) -> bool
def has_trailing_separator?(path)
if r = chop_basename(path)
pre, basename = r
pre.length + basename.length < path.length
else
false
end
end
private :has_trailing_separator?
# add_trailing_separator(path) -> path
def add_trailing_separator(path)
if File.basename(path + 'a') == 'a'
path
else
File.join(path, "") # xxx: Is File.join is appropriate to add separator?
end
end
private :add_trailing_separator
def del_trailing_separator(path)
if r = chop_basename(path)
pre, basename = r
pre + basename
elsif /#{SEPARATOR_PAT}+\z/o =~ path
$` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
else
path
end
end
private :del_trailing_separator
def cleanpath_conservative
path = @path
names = []
pre = path
while r = chop_basename(pre)
pre, base = r
names.unshift base if base != '.'
end
if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
names.shift while names[0] == '..'
end
if names.empty?
self.class.new(File.dirname(pre))
else
if names.last != '..' && File.basename(path) == '.'
names << '.'
end
result = prepend_prefix(pre, File.join(*names))
if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
self.class.new(add_trailing_separator(result))
else
self.class.new(result)
end
end
end
private :cleanpath_conservative
# #parent returns the parent directory.
#
# This is same as <tt>self + '..'</tt>.
def parent
self + '..'
end
# #mountpoint? returns +true+ if <tt>self</tt> points to a mountpoint.
def mountpoint?
begin
stat1 = self.lstat
stat2 = self.parent.lstat
stat1.dev == stat2.dev && stat1.ino == stat2.ino ||
stat1.dev != stat2.dev
rescue Errno::ENOENT
false
end
end
#
# #root? is a predicate for root directories. I.e. it returns +true+ if the
# pathname consists of consecutive slashes.
#
# It doesn't access actual filesystem. So it may return +false+ for some
# pathnames which points to roots such as <tt>/usr/..</tt>.
#
def root?
!!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path)
end
# Predicate method for testing whether a path is absolute.
# It returns +true+ if the pathname begins with a slash.
def absolute?
!relative?
end
# The opposite of #absolute?
def relative?
path = @path
while r = chop_basename(path)
path, = r
end
path == ''
end
#
# Iterates over each component of the path.
#
# Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
# # yields "usr", "bin", and "ruby".
#
def each_filename # :yield: filename
return to_enum(__method__) unless block_given?
_, names = split_names(@path)
names.each {|filename| yield filename }
nil
end
# Iterates over and yields a new Pathname object
# for each element in the given path in descending order.
#
# Pathname.new('/path/to/some/file.rb').descend {|v| p v}
# #<Pathname:/>
# #<Pathname:/path>
# #<Pathname:/path/to>
# #<Pathname:/path/to/some>
# #<Pathname:/path/to/some/file.rb>
#
# Pathname.new('path/to/some/file.rb').descend {|v| p v}
# #<Pathname:path>
# #<Pathname:path/to>
# #<Pathname:path/to/some>
# #<Pathname:path/to/some/file.rb>
#
# It doesn't access actual filesystem.
#
# This method is available since 1.8.5.
#
def descend
vs = []
ascend {|v| vs << v }
vs.reverse_each {|v| yield v }
nil
end
# Iterates over and yields a new Pathname object
# for each element in the given path in ascending order.
#
# Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
# #<Pathname:/path/to/some/file.rb>
# #<Pathname:/path/to/some>
# #<Pathname:/path/to>
# #<Pathname:/path>
# #<Pathname:/>
#
# Pathname.new('path/to/some/file.rb').ascend {|v| p v}
# #<Pathname:path/to/some/file.rb>
# #<Pathname:path/to/some>
# #<Pathname:path/to>
# #<Pathname:path>
#
# It doesn't access actual filesystem.
#
# This method is available since 1.8.5.
#
def ascend
path = @path
yield self
while r = chop_basename(path)
path, = r
break if path.empty?
yield self.class.new(del_trailing_separator(path))
end
end
#
# Pathname#+ appends a pathname fragment to this one to produce a new Pathname
# object.
#
# p1 = Pathname.new("/usr") # Pathname:/usr
# p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby
# p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd
#
# This method doesn't access the file system; it is pure string manipulation.
#
def +(other)
other = Pathname.new(other) unless Pathname === other
Pathname.new(plus(@path, other.to_s))
end
def plus(path1, path2) # -> path
prefix2 = path2
index_list2 = []
basename_list2 = []
while r2 = chop_basename(prefix2)
prefix2, basename2 = r2
index_list2.unshift prefix2.length
basename_list2.unshift basename2
end
return path2 if prefix2 != ''
prefix1 = path1
while true
while !basename_list2.empty? && basename_list2.first == '.'
index_list2.shift
basename_list2.shift
end
break unless r1 = chop_basename(prefix1)
prefix1, basename1 = r1
next if basename1 == '.'
if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
prefix1 = prefix1 + basename1
break
end
index_list2.shift
basename_list2.shift
end
r1 = chop_basename(prefix1)
if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)
while !basename_list2.empty? && basename_list2.first == '..'
index_list2.shift
basename_list2.shift
end
end
if !basename_list2.empty?
Loading ...