summaryrefslogtreecommitdiff
path: root/lib/pathname.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pathname.rb')
-rw-r--r--lib/pathname.rb626
1 files changed, 126 insertions, 500 deletions
diff --git a/lib/pathname.rb b/lib/pathname.rb
index d48cef6857..0e51e1fdf6 100644
--- a/lib/pathname.rb
+++ b/lib/pathname.rb
@@ -1,525 +1,151 @@
+# frozen_string_literal: true
+#
+# = pathname.rb
+#
# Object-Oriented Pathname Class
#
# Author:: Tanaka Akira <akr@m17n.org>
-
+# Documentation:: Author and Gavin Sinclair
+#
+# For documentation, see class Pathname.
+#
class Pathname
- def initialize(path)
- @path = path.to_str
- end
-
- def ==(other)
- return false unless Pathname === other
- other.to_s == @path
- end
- alias === ==
- alias eql? ==
-
- def <=>(other)
- return nil unless Pathname === other
- @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
- end
-
- def hash
- @path.hash
- end
-
- def to_s
- @path
- end
- alias to_str to_s
- def inspect
- "#<#{self.class}:#{@path}>"
- end
-
- # cleanpath returns clean pathname of self which is without consecutive
- # slashes and useless dots.
+ # :markup: markdown
#
- # If true is given as the optional argument consider_symlink,
- # symbolic links are considered. It makes more dots are retained.
+ # call-seq:
+ # Pathname.find(ignore_error: true) -> nil
#
- # cleanpath doesn't access actual filesystem.
- def cleanpath(consider_symlink=false)
- if consider_symlink
- cleanpath_conservative
- else
- cleanpath_aggressive
- end
- end
-
- def cleanpath_aggressive # :nodoc:
- # cleanpath_aggressive assumes:
- # * no symlink
- # * all pathname prefix contained in the pathname is existing directory
- return Pathname.new('') if @path == ''
- absolute = absolute?
- names = []
- @path.scan(%r{[^/]+}) {|name|
- next if name == '.'
- if name == '..'
- if names.empty?
- next if absolute
- else
- if names.last != '..'
- names.pop
- next
- end
- end
- end
- names << name
- }
- return Pathname.new(absolute ? '/' : '.') if names.empty?
- path = absolute ? '/' : ''
- path << names.join('/')
- Pathname.new(path)
- end
-
- def cleanpath_conservative # :nodoc:
- return Pathname.new('') if @path == ''
- names = @path.scan(%r{[^/]+})
- last_dot = names.last == '.'
- names.delete('.')
- names.shift while names.first == '..' if absolute?
- return Pathname.new(absolute? ? '/' : '.') if names.empty?
- path = absolute? ? '/' : ''
- path << names.join('/')
- if names.last != '..'
- if last_dot
- path << '/.'
- elsif %r{/\z} =~ @path
- path << '/'
- end
- end
- Pathname.new(path)
- end
-
- # realpath returns real pathname of self in actual filesystem.
+ # With a block given, performs a depth-first traversal of the path in `self`;
+ # calls the block with each found path:
#
- # If false is given for the optional argument force_absolute,
- # it may return relative pathname.
- # Otherwise it returns absolute pathname.
- def realpath(force_absolute=true)
- if relative? && force_absolute
- path = File.join Dir.pwd, @path
- else
- path = @path
- end
- stats = {}
- if %r{\A/} =~ path || realpath_root?('.', stats)
- resolved = '/'
- else
- resolved = '.'
- end
- resolved = realpath_rec(resolved, path, stats)
- Pathname.new(resolved)
- end
-
- def realpath_root?(path, stats) # :nodoc:
- path_stat = stats[path] ||= File.lstat(path)
- parent = path == '.' ? '..' : File.join(path, '..')
- parent_stat = stats[parent] ||= File.lstat(parent)
- path_stat.dev == parent_stat.dev && path_stat.ino == parent_stat.ino
- end
-
- def realpath_rec(resolved, unresolved, stats, rec={}) # :nodoc:
- unresolved.scan(%r{[^/]+}) {|f|
- next if f == '.'
- if f == '..'
- case resolved
- when '/'
- # Since the parent directory of '/' is '/', do nothing.
- when '.'
- resolved = '..'
- resolved = '/' if realpath_root?(resolved, stats)
- when %r{(\A|/)\.\.\z}
- resolved << '/..'
- resolved = '/' if realpath_root?(resolved, stats)
- when %r{/}
- resolved = File.dirname(resolved)
- else
- resolved = '.'
- end
- else
- path = resolved == '.' ? f : File.join(resolved, f)
- if File.lstat(path).symlink?
- raise Errno::ELOOP.new(path) if rec.include? path
- link = File.readlink path
- begin
- rec[path] = true
- resolved = realpath_rec(resolved, link, stats, rec)
- ensure
- rec.delete path
- end
- else
- resolved = path
- end
- end
- }
- resolved
- end
-
- # parent method returns parent directory, i.e. ".." is joined at last.
- def parent
- self.join('..')
- end
-
- # mountpoint? method returns true if self points 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
-
-=begin
- def rootdir?
- stat1 = self.lstat
- stat2 = self.parent.lstat
- stat1.dev == stat2.dev && stat1.ino == stat2.ino
- end
-=end
-
- # root? method is a predicate for root directory.
- # I.e. it returns true if the pathname consists of consecutive slashes.
+ # ```ruby
+ # paths = []
+ # Pathname('lib').find {|path| paths << path }
+ # paths.size # => 909
+ # paths.take(3)
+ # # =>
+ # # [#<Pathname:lib>,
+ # # #<Pathname:lib/English.gemspec>,
+ # # #<Pathname:lib/English.rb>]
+ # ```
#
- # It doesn't access actual filesystem.
- # So it may return false for some pathnames
- # which points root such as "/usr/..".
- def root?
- %r{\A/+\z} =~ @path ? true : false
- end
-
-=begin
- def working_directory?
- %r{\A\./*\z} =~ @path ? true : false
- end
-=end
-
- # absolute? method is a predicate for absolute pathname.
- # It returns true if self is beginning with a slash.
- def absolute?
- %r{\A/} =~ @path ? true : false
- end
-
- # relative? method is a predicate for relative pathname.
- # It returns true unless self is beginning with a slash.
- def relative?
- !absolute?
- end
-
- # each_filename iterates over self for each filename components.
- def each_filename
- @path.scan(%r{[^/]+}) { yield $& }
- end
-
- # Pathname#+ return new pathname which is concatenated with self and
- # an argument.
- # If the argument is absolute pathname, it is just returned.
- def +(other)
- other = Pathname.new(other) unless Pathname === other
- if other.absolute?
- other
- elsif %r{/\z} =~ @path
- Pathname.new(@path + other.to_s)
- else
- Pathname.new(@path + '/' + other.to_s)
- end
- end
-
-end
-
-# IO
-class Pathname
- def foreachline(*args, &block) IO.foreach(@path, *args, &block) end
- def read(*args) IO.read(@path, *args) end
- def readlines(*args) IO.readlines(@path, *args) end
- def sysopen(*args) IO.sysopen(@path, *args) end
-end
-
-# File
-class Pathname
- def atime() File.atime(@path) end
- def ctime() File.ctime(@path) end
- def mtime() File.mtime(@path) end
- def chmod(mode) File.chmod(mode, @path) end
- def lchmod(mode) File.chmod(mode, @path) end
- def chown(owner, group) File.chown(owner, group, @path) end
- def lchown(owner, group) File.lchown(owner, group, @path) end
- def fnmatch(pattern, *args) File.fnmatch(pattern, @path, *args) end
- def fnmatch?(pattern, *args) File.fnmatch?(pattern, @path, *args) end
- def ftype() File.ftype(@path) end
- def link(old) File.link(old, @path) end
- def open(*args, &block) File.open(@path, *args, &block) end
- def readlink() Pathname.new(File.readlink(@path)) end
- def rename(to) File.rename(@path, to) end
- def stat() File.stat(@path) end
- def lstat() File.lstat(@path) end
- def symlink(old) File.symlink(old, @path) end
- def truncate(length) File.truncate(@path, length) end
- def utime(atime, mtime) File.utime(atime, mtime, @path) end
- def basename(*args) Pathname.new(File.basename(@path, *args)) end
- def dirname() Pathname.new(File.dirname(@path)) end
- def extname() File.extname(@path) end
- def expand_path(*args) Pathname.new(File.expand_path(@path, *args)) end
- def join(*args) Pathname.new(File.join(@path, *args)) end
- def split() File.split(@path).map {|f| Pathname.new(f) } end
-end
-
-# FileTest
-class Pathname
- def blockdev?() FileTest.blockdev?(@path) end
- def chardev?() FileTest.chardev?(@path) end
- def executable?() FileTest.executable?(@path) end
- def executable_real?() FileTest.executable_real?(@path) end
- def exist?() FileTest.exist?(@path) end
- def grpowned?() FileTest.grpowned?(@path) end
- def directory?() FileTest.directory?(@path) end
- def file?() FileTest.file?(@path) end
- def pipe?() FileTest.pipe?(@path) end
- def socket?() FileTest.socket?(@path) end
- def owned?() FileTest.owned?(@path) end
- def readable?() FileTest.readable?(@path) end
- def readable_real?() FileTest.readable_real?(@path) end
- def setuid?() FileTest.setuid?(@path) end
- def setgid?() FileTest.setgid?(@path) end
- def size() FileTest.size(@path) end
- def size?() FileTest.size?(@path) end
- def sticky?() FileTest.sticky?(@path) end
- def symlink?() FileTest.symlink?(@path) end
- def writable?() FileTest.writable?(@path) end
- def writable_real?() FileTest.writable_real?(@path) end
- def zero?() FileTest.zero?(@path) end
-end
-
-# Dir
-class Pathname
- def Pathname.glob(*args)
- if block_given?
- Dir.glob(*args) {|f| yield Pathname.new(f) }
+ # When `self` contains `'.'`, the found paths omit the leading `'./'`:
+ #
+ # ```ruby
+ # paths = []
+ # Dir.chdir('lib') do
+ # Pathname('.').find {|path| paths << path }
+ # end
+ # paths.take(3)
+ # # # =>
+ # # [#<Pathname:.>,
+ # # #<Pathname:English.gemspec>,
+ # # #<Pathname:English.rb>]
+ # ```
+ #
+ # This method calls method Find.find;
+ # therefore method Find.prune may be used in the block:
+ #
+ # ```ruby
+ # files = []
+ # Pathname('.').find do |path|
+ # Find.prune if File.basename(path) == 'test'
+ # next unless File.file?(path) && File.extname(path) == '.rb'
+ # files << path
+ # end
+ # files.size # => 6690
+ # files.take(3)
+ # # # =>
+ # # [#<Pathname:KNOWNBUGS.rb>,
+ # # #<Pathname:array.rb>,
+ # # #<Pathname:ast.rb>]
+ # ```
+ #
+ # Raises an exception if the path in `self` cannot be read.
+ #
+ # When keyword argument `ignore_error` is given as `true` (the default),
+ # certain exceptions during traversal are ignored (i.e., silently rescued):
+ # Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL;
+ # when given as `false`, no exceptions are rescued.
+ #
+ # Note that these exceptions may be ignored only in `Pathname#find` traversal code;
+ # an exception raised before traversal begins,
+ # or raised while in the block is not ignored.
+ # Each of the calls below raises an Errno::ENOENT exception that is not ignored:
+ #
+ # ```ruby
+ # Pathname('nosuch').find { }
+ # Pathname('lib').find {|entry| raise Errno::ENOENT }
+ # ```
+ #
+ # With no block given, returns a new Enumerator.
+ def find(ignore_error: true) # :yield: pathname
+ return to_enum(__method__, ignore_error: ignore_error) unless block_given?
+ require 'find'
+ if @path == '.'
+ Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) }
else
- Dir.glob(*args).map {|f| Pathname.new(f) }
+ Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) }
end
end
-
- def Pathname.getwd() Pathname.new(Dir.getwd) end
- class << self; alias pwd getwd end
-
- def chdir(&block) Dir.chdir(@path, &block) end
- def chroot() Dir.chroot(@path) end
- def rmdir() Dir.rmdir(@path) end
- def entries() Dir.entries(@path).map {|f| Pathname.new(f) } end
- def dir_foreach(&block) Dir.foreach(@path) {|f| yield Pathname.new(f) } end
- def mkdir(*args) Dir.mkdir(@path, *args) end
- def opendir(&block) Dir.open(@path, &block) end
-end
-
-# Find
-class Pathname
- def find(&block)
- require 'find'
- Find.find(@path) {|f| yield Pathname.new(f) }
- end
end
-# FileUtils
-class Pathname
- def mkpath
- require 'fileutils'
- FileUtils.mkpath(@path)
- nil
- end
- def rmtree
+class Pathname # * FileUtils *
+ # Recursively deletes a directory, including all directories beneath it.
+ #
+ # Note that you need to require 'pathname' to use this method.
+ #
+ # See FileUtils.rm_rf
+ def rmtree(noop: nil, verbose: nil, secure: nil)
# The name "rmtree" is borrowed from File::Path of Perl.
# File::Path provides "mkpath" and "rmtree".
require 'fileutils'
- FileUtils.rm_r(@path)
- nil
+ FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure)
+ self
end
end
-# mixed
-class Pathname
- def unlink()
- if FileTest.directory? @path
- Dir.unlink @path
- else
- File.unlink @path
- end
- end
- alias delete unlink
-
- def foreach(*args, &block)
- if FileTest.directory? @path
- # For polymorphism between Dir.foreach and IO.foreach,
- # Pathname#foreach doesn't yield Pathname object.
- Dir.foreach(@path, *args, &block)
+class Pathname # * tmpdir *
+ # call-seq:
+ # Pathname.mktmpdir -> new_pathname
+ # Pathname.mktmpdir {|pathname| ... } -> object
+ #
+ # Creates:
+ #
+ # - A temporary directory via Dir.mktmpdir.
+ # - A \Pathname object that contains the path to that directory.
+ #
+ # With no block given, returns the created pathname;
+ # the caller should delete the created directory when it is no longer needed
+ # (FileUtils.rm_r is a convenient method for the deletion):
+ #
+ # pathname = Pathname.mktmpdir
+ # dirpath = pathname.to_s
+ # Dir.exist?(dirpath) # => true
+ # # Do something with the directory.
+ # require 'fileutils'
+ # FileUtils.rm_r(dirpath)
+ #
+ # With a block given, calls the block with the created pathname;
+ # on block exit, automatically deletes the created directory and all its contents;
+ # returns the block's exit value:
+ #
+ # pathname = Pathname.mktmpdir do |p|
+ # # Do something with the directory.
+ # p
+ # end
+ # Dir.exist?(pathname.to_s) # => false
+ def self.mktmpdir
+ require 'tmpdir' unless defined?(Dir.mktmpdir)
+ if block_given?
+ Dir.mktmpdir do |dir|
+ dir = self.new(dir)
+ yield dir
+ end
else
- IO.foreach(@path, *args, &block)
- end
- end
-end
-
-if $0 == __FILE__
- require 'test/unit'
-
- class PathnameTest < Test::Unit::TestCase # :nodoc:
- class AnotherStringLike # :nodoc:
- def initialize(s) @s = s end
- def to_str() @s end
- def ==(other) @s == other end
- end
-
- def test_equality
- obj = Pathname.new("a")
- str = "a"
- sym = :a
- ano = AnotherStringLike.new("a")
- assert_equal(false, obj == str)
- assert_equal(false, str == obj)
- assert_equal(false, obj == ano)
- assert_equal(false, ano == obj)
- assert_equal(false, obj == sym)
- assert_equal(false, sym == obj)
-
- obj2 = Pathname.new("a")
- assert_equal(true, obj == obj2)
- assert_equal(true, obj === obj2)
- assert_equal(true, obj.eql?(obj2))
- end
-
- def test_hashkey
- h = {}
- h[Pathname.new("a")] = 1
- h[Pathname.new("a")] = 2
- assert_equal(1, h.size)
- end
-
- def assert_pathname_cmp(e, s1, s2)
- p1 = Pathname.new(s1)
- p2 = Pathname.new(s2)
- r = p1 <=> p2
- assert(e == r,
- "#{p1.inspect} <=> #{p2.inspect}: <#{e}> expected but was <#{r}>")
- end
- def test_comparison
- assert_pathname_cmp( 0, "a", "a")
- assert_pathname_cmp( 1, "b", "a")
- assert_pathname_cmp(-1, "a", "b")
- ss = %w(
- a
- a/
- a/b
- a.
- a0
- )
- s1 = ss.shift
- ss.each {|s2|
- assert_pathname_cmp(-1, s1, s2)
- s1 = s2
- }
- end
-
- def test_comparison_string
- assert_equal(nil, Pathname.new("a") <=> "a")
- assert_equal(nil, "a" <=> Pathname.new("a"))
- end
-
- def test_syntactical
- assert_equal(true, Pathname.new("/").root?)
- assert_equal(true, Pathname.new("//").root?)
- assert_equal(true, Pathname.new("///").root?)
- assert_equal(false, Pathname.new("").root?)
- assert_equal(false, Pathname.new("a").root?)
- #assert_equal(true, Pathname.new(".").working_directory?)
- #assert_equal(true, Pathname.new("./").working_directory?)
- #assert_equal(true, Pathname.new(".//").working_directory?)
- #assert_equal(false, Pathname.new("").working_directory?)
- #assert_equal(false, Pathname.new("a").working_directory?)
- end
-
- def test_cleanpath
- assert_equal('/', Pathname.new('/').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('//').cleanpath(true).to_s)
- assert_equal('', Pathname.new('').cleanpath(true).to_s)
-
- assert_equal('.', Pathname.new('.').cleanpath(true).to_s)
- assert_equal('..', Pathname.new('..').cleanpath(true).to_s)
- assert_equal('a', Pathname.new('a').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('/.').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('/..').cleanpath(true).to_s)
- assert_equal('/a', Pathname.new('/a').cleanpath(true).to_s)
- assert_equal('.', Pathname.new('./').cleanpath(true).to_s)
- assert_equal('..', Pathname.new('../').cleanpath(true).to_s)
- assert_equal('a/', Pathname.new('a/').cleanpath(true).to_s)
-
- assert_equal('a/b', Pathname.new('a//b').cleanpath(true).to_s)
- assert_equal('a/.', Pathname.new('a/.').cleanpath(true).to_s)
- assert_equal('a/.', Pathname.new('a/./').cleanpath(true).to_s)
- assert_equal('a/..', Pathname.new('a/../').cleanpath(true).to_s)
- assert_equal('/a/.', Pathname.new('/a/.').cleanpath(true).to_s)
- assert_equal('..', Pathname.new('./..').cleanpath(true).to_s)
- assert_equal('..', Pathname.new('../.').cleanpath(true).to_s)
- assert_equal('..', Pathname.new('./../').cleanpath(true).to_s)
- assert_equal('..', Pathname.new('.././').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('/./..').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('/../.').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('/./../').cleanpath(true).to_s)
- assert_equal('/', Pathname.new('/.././').cleanpath(true).to_s)
-
- assert_equal('a/b/c', Pathname.new('a/b/c').cleanpath(true).to_s)
- assert_equal('b/c', Pathname.new('./b/c').cleanpath(true).to_s)
- assert_equal('a/c', Pathname.new('a/./c').cleanpath(true).to_s)
- assert_equal('a/b/.', Pathname.new('a/b/.').cleanpath(true).to_s)
- assert_equal('a/..', Pathname.new('a/../.').cleanpath(true).to_s)
-
- assert_equal('/a', Pathname.new('/../.././../a').cleanpath(true).to_s)
- assert_equal('a/b/../../../../c/../d', Pathname.new('a/b/../../../../c/../d').cleanpath(true).to_s)
+ self.new(Dir.mktmpdir)
end
-
-
- def test_cleanpath_no_symlink
- assert_equal('/', Pathname.new('/').cleanpath.to_s)
- assert_equal('/', Pathname.new('//').cleanpath.to_s)
- assert_equal('', Pathname.new('').cleanpath.to_s)
-
- assert_equal('.', Pathname.new('.').cleanpath.to_s)
- assert_equal('..', Pathname.new('..').cleanpath.to_s)
- assert_equal('a', Pathname.new('a').cleanpath.to_s)
- assert_equal('/', Pathname.new('/.').cleanpath.to_s)
- assert_equal('/', Pathname.new('/..').cleanpath.to_s)
- assert_equal('/a', Pathname.new('/a').cleanpath.to_s)
- assert_equal('.', Pathname.new('./').cleanpath.to_s)
- assert_equal('..', Pathname.new('../').cleanpath.to_s)
- assert_equal('a', Pathname.new('a/').cleanpath.to_s)
-
- assert_equal('a/b', Pathname.new('a//b').cleanpath.to_s)
- assert_equal('a', Pathname.new('a/.').cleanpath.to_s)
- assert_equal('a', Pathname.new('a/./').cleanpath.to_s)
- assert_equal('.', Pathname.new('a/../').cleanpath.to_s)
- assert_equal('/a', Pathname.new('/a/.').cleanpath.to_s)
- assert_equal('..', Pathname.new('./..').cleanpath.to_s)
- assert_equal('..', Pathname.new('../.').cleanpath.to_s)
- assert_equal('..', Pathname.new('./../').cleanpath.to_s)
- assert_equal('..', Pathname.new('.././').cleanpath.to_s)
- assert_equal('/', Pathname.new('/./..').cleanpath.to_s)
- assert_equal('/', Pathname.new('/../.').cleanpath.to_s)
- assert_equal('/', Pathname.new('/./../').cleanpath.to_s)
- assert_equal('/', Pathname.new('/.././').cleanpath.to_s)
-
- assert_equal('a/b/c', Pathname.new('a/b/c').cleanpath.to_s)
- assert_equal('b/c', Pathname.new('./b/c').cleanpath.to_s)
- assert_equal('a/c', Pathname.new('a/./c').cleanpath.to_s)
- assert_equal('a/b', Pathname.new('a/b/.').cleanpath.to_s)
- assert_equal('.', Pathname.new('a/../.').cleanpath.to_s)
-
- assert_equal('/a', Pathname.new('/../.././../a').cleanpath.to_s)
- assert_equal('../../d', Pathname.new('a/b/../../../../c/../d').cleanpath.to_s)
- end
-
end
end