summaryrefslogtreecommitdiff
path: root/pathname_builtin.rb
diff options
context:
space:
mode:
Diffstat (limited to 'pathname_builtin.rb')
-rw-r--r--pathname_builtin.rb1895
1 files changed, 1895 insertions, 0 deletions
diff --git a/pathname_builtin.rb b/pathname_builtin.rb
new file mode 100644
index 0000000000..11ade220f0
--- /dev/null
+++ b/pathname_builtin.rb
@@ -0,0 +1,1895 @@
+# frozen_string_literal: true
+#
+# A \Pathname object contains a string directory path or filepath;
+# it does not represent a corresponding actual file or directory
+# -- which in fact may or may not exist.
+#
+# A \Pathname object is immutable (except for method #freeze).
+#
+# A pathname may be relative or absolute:
+#
+# Pathname.new('lib') # => #<Pathname:lib>
+# Pathname.new('/usr/local/bin') # => #<Pathname:/usr/local/bin>
+#
+# == About the Examples
+#
+# Many examples here use these variables:
+#
+# :include: doc/examples/files.rdoc
+#
+# == Convenience Methods
+#
+# The class provides *all* functionality from class File and module FileTest,
+# along with some functionality from class Dir and module FileUtils.
+#
+# Here's an example string path and corresponding \Pathname object:
+#
+# path = 'lib/fileutils.rb'
+# pn = Pathname.new(path) # => #<Pathname:lib/fileutils.rb>
+#
+# Each of these method pairs (\Pathname vs. \File) gives exactly the same result:
+#
+# pn.size # => 83777
+# File.size(path) # => 83777
+#
+# pn.directory? # => false
+# File.directory?(path) # => false
+#
+# pn.read.size # => 81074
+# File.read(path).size# # => 81074
+#
+# Each of these method pairs gives similar results,
+# but each \Pathname method returns a more versatile \Pathname object,
+# instead of a string:
+#
+# pn.dirname # => #<Pathname:lib>
+# File.dirname(path) # => "lib"
+#
+# pn.basename # => #<Pathname:fileutils.rb>
+# File.basename(path) # => "fileutils.rb"
+#
+# pn.split # => [#<Pathname:lib>, #<Pathname:fileutils.rb>]
+# File.split(path) # => ["lib", "fileutils.rb"]
+#
+# Each of these methods takes a block:
+#
+# pn.open do |file|
+# p file
+# end
+# File.open(path) do |file|
+# p file
+# end
+#
+# The outputs for each:
+#
+# #<File:lib/fileutils.rb (closed)>
+# #<File:lib/fileutils.rb (closed)>
+#
+# Each of these methods takes a block:
+#
+# pn.each_line do |line|
+# p line
+# break
+# end
+# File.foreach(path) do |line|
+# p line
+# break
+# end
+#
+# The outputs for each:
+#
+# "# frozen_string_literal: true\n"
+# "# frozen_string_literal: true\n"
+#
+# == More Methods
+#
+# Here is a sampling of other available methods:
+#
+# p1 = Pathname.new('/usr/lib') # => #<Pathname:/usr/lib>
+# p1.absolute? # => true
+# p2 = p1 + 'ruby/4.0' # => #<Pathname:/usr/lib/ruby/4.0>
+# p3 = p1.parent # => #<Pathname:/usr>
+# p4 = p2.relative_path_from(p3) # => #<Pathname:lib/ruby/4.0>
+# p4.absolute? # => false
+# p5 = Pathname.new('.') # => #<Pathname:.>
+# p6 = p5 + 'usr/../var' # => #<Pathname:usr/../var>
+# p6.cleanpath # => #<Pathname:var>
+# p6.realpath # => #<Pathname:/var>
+# p6.children.take(2)
+# # => [#<Pathname:usr/../var/local>, #<Pathname:usr/../var/spool>]
+#
+# == Breakdown of functionality
+#
+# === Core methods
+#
+# These methods are effectively manipulating a String, because that's
+# all a path is. None of these access the file system except for
+# #mountpoint?, #children, #each_child, #realdirpath and #realpath.
+#
+# - +
+# - #join
+# - #parent
+# - #root?
+# - #absolute?
+# - #relative?
+# - #relative_path_from
+# - #each_filename
+# - #cleanpath
+# - #realpath
+# - #realdirpath
+# - #children
+# - #each_child
+# - #mountpoint?
+#
+# === File status predicate methods
+#
+# These methods are a facade for FileTest:
+# - #blockdev?
+# - #chardev?
+# - #directory?
+# - #executable?
+# - #executable_real?
+# - #exist?
+# - #file?
+# - #grpowned?
+# - #owned?
+# - #pipe?
+# - #readable?
+# - #world_readable?
+# - #readable_real?
+# - #setgid?
+# - #setuid?
+# - #size
+# - #size?
+# - #socket?
+# - #sticky?
+# - #symlink?
+# - #writable?
+# - #world_writable?
+# - #writable_real?
+# - #zero?
+#
+# === File property and manipulation methods
+#
+# These methods are a facade for File:
+# - #each_line(*args, &block)
+# - #read(*args)
+# - #binread(*args)
+# - #readlines(*args)
+# - #sysopen(*args)
+# - #write(*args)
+# - #binwrite(*args)
+# - #atime
+# - #birthtime
+# - #ctime
+# - #mtime
+# - #chmod(mode)
+# - #lchmod(mode)
+# - #chown(owner, group)
+# - #lchown(owner, group)
+# - #fnmatch(pattern, *args)
+# - #fnmatch?(pattern, *args)
+# - #ftype
+# - #make_link(old)
+# - #open(*args, &block)
+# - #readlink
+# - #rename(to)
+# - #stat
+# - #lstat
+# - #make_symlink(old)
+# - #truncate(length)
+# - #utime(atime, mtime)
+# - #lutime(atime, mtime)
+# - #basename(*args)
+# - #dirname
+# - #extname
+# - #expand_path(*args)
+# - #split
+#
+# === Directory methods
+#
+# These methods are a facade for Dir:
+# - Pathname.glob(*args)
+# - Pathname.getwd / Pathname.pwd
+# - #rmdir
+# - #entries
+# - #each_entry(&block)
+# - #mkdir(*args)
+# - #opendir(*args)
+#
+# === Utilities
+#
+# These methods are a mixture of Find, FileUtils, and others:
+# - #find(&block)
+# - #mkpath
+# - #rmtree
+# - #unlink / #delete
+#
+#
+# == Method documentation
+#
+# As the above section shows, most of the methods in Pathname are facades. The
+# documentation for these methods generally just says, for instance, "See
+# FileTest.writable?", as you should be familiar with the original method
+# anyway, and its documentation (e.g. through +ri+) will contain more
+# information. In some cases, a brief description will follow.
+#
+class Pathname
+
+ # The version string.
+ VERSION = "0.4.0"
+
+ # :stopdoc:
+
+ attr_reader :path
+ protected :path
+
+ # :startdoc:
+
+ # call-seq:
+ # Pathname.new(path) -> new_pathname
+ #
+ # Returns a new \Pathname object based on the given +path+,
+ # via <tt>File.path(path).dup</tt>.
+ # the +path+ may be a String, a File, a Dir, or another \Pathname;
+ # see File.path:
+ #
+ # Pathname.new('.') # => #<Pathname:.>
+ # Pathname.new('/usr/bin') # => #<Pathname:/usr/bin>
+ # Pathname.new(File.new('LEGAL')) # => #<Pathname:LEGAL>
+ # Pathname.new(Dir.new('.')) # => #<Pathname:.>
+ # Pathname.new(Pathname.new('.')) # => #<Pathname:.>
+ #
+ def initialize(path)
+ @path = File.path(path).dup
+ rescue TypeError => e
+ raise e.class, "Pathname.new requires a String, #to_path or #to_str", cause: nil
+ end
+
+ #
+ # Freze self.
+ #
+ def freeze
+ super
+ @path.freeze
+ self
+ end
+
+ # call-seq:
+ # self == other -> true or false
+ #
+ # Returns whether the stored paths in +self+ and +other+ are equal:
+ #
+ # pn = Pathname('lib')
+ # pn == Pathname('lib') # => true
+ # pn == Pathname('./lib') # => false
+ #
+ # Returns +false+ if +other+ is not a pathname:
+ #
+ # pn == 'lib' # => false
+ #
+ def ==(other)
+ return false unless Pathname === other
+ other.path == @path
+ end
+ alias === ==
+ alias eql? ==
+
+ def hash # :nodoc:
+ @path.hash
+ end
+
+ # Return the path as a String.
+ def to_s
+ @path.dup
+ end
+
+ # to_path is implemented so Pathname objects are usable with File.open, etc.
+ alias to_path to_s
+
+ def inspect # :nodoc:
+ "#<#{self.class}:#{@path}>"
+ end
+
+ # Creates a full path, including any intermediate directories that don't yet
+ # exist.
+ #
+ # See FileUtils.mkpath and FileUtils.mkdir_p
+ def mkpath(mode: nil)
+ path = @path == '/' ? @path : @path.chomp('/')
+
+ stack = []
+ until File.directory?(path) || (parent = File.dirname(path)) == path
+ stack.push path
+ path = parent
+ end
+
+ stack.reverse_each do |dir|
+ dir = dir == '/' ? dir : dir.chomp('/')
+ if mode
+ Dir.mkdir dir, mode
+ File.chmod mode, dir
+ else
+ Dir.mkdir dir
+ end
+ rescue SystemCallError
+ raise unless File.directory?(dir)
+ end
+
+ self
+ end
+
+ def prepend_prefix(prefix, relpath) # :nodoc:
+ if relpath.empty?
+ File.dirname(prefix)
+ elsif has_separator?(prefix)
+ add_trailing_separator(File.dirname(prefix)) + relpath
+ else
+ prefix + relpath
+ end
+ end
+ private :prepend_prefix
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # cleanpath(symlinks = false) -> new_pathname
+ #
+ # Returns a new \Pathname object, "cleaned" of unnecessary separators,
+ # single-dot entries, and double-dot entries.
+ #
+ # When `self` is empty, returns a pathname with a single-dot entry:
+ #
+ # ```
+ # Pathname('').cleanpath # => #<Pathname:.>
+ # ```
+ #
+ # <b>Separators</b>
+ #
+ # A lone separator is preserved:
+ #
+ # ```
+ # Pathname('/').cleanpath # => #<Pathname:/>
+ # ```
+ #
+ # Multiple trailing separators are removed:
+ #
+ # ```
+ # Pathname('foo/////').cleanpath # => #<Pathname:foo>
+ # Pathname('foo/').cleanpath # => #<Pathname:foo>
+ # ```
+ #
+ # Multiple embedded separators are reduced to a single separator:
+ #
+ # ```
+ # Pathname('foo///bar').cleanpath # => #<Pathname:foo/bar>
+ # ```
+ #
+ # Multiple leading separators are reduced:
+ #
+ # ```
+ # # On Windows, where File.dirname('//') == '//'.
+ # Pathname('/////foo').cleanpath # => #<Pathname://foo>
+ # Pathname('/////').cleanpath # => #<Pathname://>
+ # # Otherwise, where File.dirname('//') == '/'.
+ # Pathname('/////foo').cleanpath # => #<Pathname:/foo>
+ # Pathname('/////').cleanpath # => #<Pathname:/>
+ # ```
+ #
+ # <b>Single-Dot Entries</b>
+ #
+ # A lone single-dot entry is preserved:
+ #
+ # ```
+ # Pathname('.').cleanpath # => #<Pathname:.>
+ # ```
+ #
+ # A non-lone single-dot entry, regardless of its location, is removed:
+ #
+ # ```
+ # Pathname('foo/././././bar').cleanpath # => #<Pathname:foo/bar>
+ # Pathname('./foo/./././bar').cleanpath # => #<Pathname:foo/bar>
+ # Pathname('foo/./././bar/./').cleanpath # => #<Pathname:foo/bar>
+ # ```
+ #
+ # <b>Double-Dot Entries</b>
+ #
+ # A lone double-dot entry is preserved:
+ #
+ # ```
+ # Pathname('..').cleanpath # => #<Pathname:..>
+ # ```
+ #
+ # When a non-lone double-dot entry is preceded by a named entry, both are removed:
+ #
+ # ```
+ # Pathname('foo/..').cleanpath # => #<Pathname:.>
+ # Pathname('foo/../bar').cleanpath # => #<Pathname:bar>
+ # Pathname('foo/../bar/..').cleanpath # => #<Pathname:.>
+ # Pathname('foo/bar/./../..').cleanpath # => #<Pathname:.>
+ # ```
+ #
+ # When a non-lone double-dot entry is _not_ preceded by a named entry,
+ # it is preserved:
+ #
+ # ```
+ # Pathname('../..').cleanpath # => #<Pathname:../..>
+ # ```
+ #
+ # A non-lone meaningless double-dot entry is removed:
+ #
+ # ```
+ # Pathname('/..').cleanpath # => #<Pathname:/>
+ # Pathname('/../..').cleanpath # => #<Pathname:/>
+ # ```
+ #
+ # <b> Symbolic Links</b>
+ #
+ # If the path may contain [symbolic links][symbolic link],
+ # consider give optional argument `symlinks` as `true`;
+ # the method then uses a more conservative algorithm
+ # that avoids breaking symbolic links.
+ # This may preserve more double-dot entries than are absolutely necessary,
+ # but without accessing the filesystem, this can't be avoided.
+ #
+ # Examples:
+ #
+ # ```
+ # Pathname('a/').cleanpath # => #<Pathname:a>
+ # Pathname('a/').cleanpath(true) # => #<Pathname:a/>
+ #
+ # Pathname('a/.').cleanpath # => #<Pathname:a>
+ # Pathname('a/.').cleanpath(true) # => #<Pathname:a/.>
+ #
+ # Pathname('a/./').cleanpath # => #<Pathname:a>
+ # Pathname('a/./').cleanpath(true) # => #<Pathname:a/.>
+ #
+ # Pathname('a/b/.').cleanpath # => #<Pathname:a/b>
+ # Pathname('a/b/.').cleanpath(true) # => #<Pathname:a/b/.>
+ #
+ # Pathname('a/../.').cleanpath # => #<Pathname:.>
+ # Pathname('a/../.').cleanpath(true) # => #<Pathname:a/..>
+ #
+ # Pathname('a/b/../../../../c/../d').cleanpath
+ # # => #<Pathname:../../d>
+ # Pathname('a/b/../../../../c/../d').cleanpath(true)
+ # # => #<Pathname:a/b/../../../../c/../d>
+ # ```
+ #
+ # [symbolic link]: https://en.wikipedia.org/wiki/Symbolic_link
+ #
+ 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 # :nodoc:
+ 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
+ pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
+ if has_separator?(File.basename(pre))
+ names.shift while names[0] == '..'
+ end
+ self.class.new(prepend_prefix(pre, File.join(*names)))
+ end
+ private :cleanpath_aggressive
+
+ def cleanpath_conservative # :nodoc:
+ path = @path
+ names = []
+ pre = path
+ while r = chop_basename(pre)
+ pre, base = r
+ names.unshift base if base != '.'
+ end
+ pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
+ if has_separator?(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
+
+ # Returns the parent directory.
+ #
+ # This is same as <code>self + '..'</code>.
+ def parent
+ self + '..'
+ end
+
+ # Returns +true+ if +self+ points to a mountpoint.
+ def mountpoint?
+ begin
+ stat1 = self.lstat
+ stat2 = self.parent.lstat
+ stat1.dev != stat2.dev || stat1.ino == stat2.ino
+ rescue Errno::ENOENT
+ false
+ end
+ end
+
+ # The opposite of Pathname#absolute?
+ #
+ # It returns +false+ if the pathname begins with a slash.
+ #
+ # p = Pathname.new('/im/sure')
+ # p.relative?
+ # #=> false
+ #
+ # p = Pathname.new('not/so/sure')
+ # p.relative?
+ # #=> true
+ def relative?
+ !absolute?
+ end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # each_filename {|component| ... } -> nil
+ # each_filename -> new_enumerator
+ #
+ # With a block given, yields each component of the string path:
+ #
+ # ```ruby
+ # Pathname('/foo/bar/baz').each_filename {|filename| p filename }
+ # => nil
+ # ```
+ #
+ # Output:
+ #
+ # ```text
+ # "foo"
+ # "bar"
+ # "baz"
+ # ```
+ #
+ # With no block given, returns a new Enumerator.
+ def each_filename # :yield: filename
+ return to_enum(__method__) unless block_given?
+ _, names = split_names(@path)
+ names.each {|filename| yield filename }
+ nil
+ end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # descend {|entry| ... } -> nil
+ # descend -> new_enumerator
+ #
+ # With a block given, yields a new pathname for each successive dirname
+ # in the stored path; see File.dirname:
+ #
+ # ```ruby
+ # # Absolute path.
+ # Pathname('/path/to/some/file.rb').descend {|pn| p pn }
+ # # #<Pathname:/>
+ # # #<Pathname:/path>
+ # # #<Pathname:/path/to>
+ # # #<Pathname:/path/to/some>
+ # # #<Pathname:/path/to/some/file.rb>
+ # # Relative path.
+ # Pathname('path/to/some/file.rb').descend {|pn| p pn }
+ # # #<Pathname:path>
+ # # #<Pathname:path/to>
+ # # #<Pathname:path/to/some>
+ # # #<Pathname:path/to/some/file.rb>
+ # ```
+ #
+ # With no block given, returns a new Enumerator.
+ def descend
+ return to_enum(__method__) unless block_given?
+ vs = []
+ ascend {|v| vs << v }
+ vs.reverse_each {|v| yield v }
+ nil
+ end
+
+ # call-seq:
+ # ascend {|entry| ... } -> nil
+ # ascend -> new_enumerator
+ #
+ # With a block given,
+ # yields +self+, then a new pathname for each successive dirname in the stored path;
+ # see File.dirname:
+ #
+ # Pathname('/path/to/some/file.rb').ascend {|dirname| p dirname}
+ # #<Pathname:/path/to/some/file.rb>
+ # #<Pathname:/path/to/some>
+ # #<Pathname:/path/to>
+ # #<Pathname:/path>
+ # #<Pathname:/>
+ #
+ # With no block given, returns a new Enumerator.
+ def ascend
+ return to_enum(__method__) unless block_given?
+ 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
+
+ # call-seq:
+ # self + other -> new_pathname
+ #
+ # Returns a new \Pathname object based on the content of +self+ and +other+;
+ # argument +other+ may be a String, a File, a Dir, or another \Pathname:
+ #
+ # pn = Pathname('foo') # => #<Pathname:foo>
+ # pn + 'bar' # => #<Pathname:foo/bar>
+ # pn + File.new('LEGAL') # => #<Pathname:foo/LEGAL>
+ # pn + Dir.new('lib') # => #<Pathname:foo/lib>
+ # pn + Pathname('bar') # => #<Pathname:foo/bar>
+ #
+ # When +other+ specifies a relative path (see #relative?),
+ # it is combined with +self+ to form a new pathname:
+ #
+ # Pathname('/a/b') + 'c' # => #<Pathname:/a/b/c>
+ #
+ # Extra component separators (<tt>'/'</tt>) are removed:
+ #
+ # Pathname('/a/b/') + 'c' # => #<Pathname:/a/b/c>
+ #
+ # Extra current-directory components (<tt>'.'</tt>) are removed:
+ #
+ # Pathname('a') + '.' # => #<Pathname:a>
+ # Pathname('.') + 'a' # => #<Pathname:a>
+ # Pathname('.') + '.' # => #<Pathname:.>
+ #
+ # Parent-directory components (<tt>'..'</tt>) are:
+ #
+ # - Resolved, when possible:
+ #
+ # Pathname('a') + '..' # => #<Pathname:.>
+ # Pathname('a/b') + '..' # => #<Pathname:a>
+ # Pathname('/') + '../a' # => #<Pathname:/a>
+ # Pathname('a') + '../b' # => #<Pathname:b>
+ # Pathname('a/b') + '../c' # => #<Pathname:a/c>
+ # Pathname('a//b/c') + '../d//e' # => #<Pathname:a//b/d//e>
+ #
+ # - Removed, when not needed:
+ #
+ # Pathname('/') + '..' # => #<Pathname:/>
+ #
+ # - Retained, when needed:
+ #
+ # Pathname('..') + '..' # => #<Pathname:../..>
+ # Pathname('..') + '../a' # => #<Pathname:../../a>
+ #
+ # When +other+ specifies an absolute path (see #absolute?),
+ # equivalent to <tt>Pathname(other.to_s)</tt>:
+ #
+ # Pathname('/a') + '/b/c' # => #<Pathname:/b/c>
+ #
+ # Occurrences of <tt>'/'</tt>, <tt>'.'</tt>, and <tt>'..'</tt> are preserved:
+ #
+ # Pathname('/a') + '//b//c/./../d' # => #<Pathname://b//c/./../d>
+ #
+ # This method does not access the file system, so +other+ need not represent
+ # an existing (or even a valid) file or directory path:
+ #
+ # Pathname('/var') + 'nosuch:ever' # => #<Pathname:/var/nosuch:ever>
+ #
+ def +(other)
+ other = Pathname.new(other) unless Pathname === other
+ Pathname.new(plus(@path, other.path))
+ end
+ alias / +
+
+ # (path1, path2) -> path
+ def plus(path1, path2) # :nodoc:
+ 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 && (r1 = has_separator?(File.basename(prefix1)))
+ while !basename_list2.empty? && basename_list2.first == '..'
+ index_list2.shift
+ basename_list2.shift
+ end
+ end
+ if !basename_list2.empty?
+ suffix2 = path2[index_list2.first..-1]
+ r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
+ else
+ r1 ? prefix1 : File.dirname(prefix1)
+ end
+ end
+ private :plus
+
+ #
+ # Joins the given pathnames onto +self+ to create a new Pathname object.
+ # This is effectively the same as using Pathname#+ to append +self+ and
+ # all arguments sequentially.
+ #
+ # path0 = Pathname.new("/usr") # Pathname:/usr
+ # path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby
+ # # is the same as
+ # path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby
+ # path0 == path1
+ # #=> true
+ #
+ def join(*args)
+ return self if args.empty?
+ result = args.pop
+ result = Pathname.new(result) unless Pathname === result
+ return result if result.absolute?
+ args.reverse_each {|arg|
+ arg = Pathname.new(arg) unless Pathname === arg
+ result = arg + result
+ return result if result.absolute?
+ }
+ self + result
+ end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # children(with_dirnames = true) -> array_of_pathnames
+ #
+ # Returns an array of pathnames;
+ # each represents a child of the entry represented by `self`,
+ # which must be an existing directory in the underlying file system.
+ #
+ # With `with_dirnames` given as `true` (the default),
+ # each pathname contains the full entry:
+ #
+ # ```ruby
+ # Pathname('lib').children.size # => 72
+ # Pathname('lib').children.take(3)
+ # # => [#<Pathname:lib/bundled_gems.rb>, #<Pathname:lib/bundler>, #<Pathname:lib/bundler.rb>]
+ # ```
+ # With `with_dirnames` given as `false`,
+ # each pathname contains only the basename of the entry:
+ #
+ # ```ruby
+ # Pathname('lib').children(false).take(3)
+ # # => [#<Pathname:bundled_gems.rb>, #<Pathname:bundler>, #<Pathname:bundler.rb>]
+ # ```
+ #
+ # Note that entries `.` and `..` in directory are not actually children,
+ # and so are never included in the result.
+ def children(with_directory=true)
+ with_directory = false if @path == '.'
+ result = Dir.children(@path)
+ if with_directory
+ result.map! {|e| self.class.new(File.join(@path, e))}
+ else
+ result.map! {|e| self.class.new(e)}
+ end
+ result
+ end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # each_child(with_dirnames = true) {|entry| ... } -> array_of_pathnames
+ # each_child(with_dirnames = true) -> new_enumerator
+ #
+ # With a block given and `with_dirnames` given as `true` (the default),
+ # yields a new pathname for each child
+ # of the entry represented by `self`;
+ # returns an array of those pathnames:
+ #
+ # ```ruby
+ # Pathname('include').each_child {|child| p child }
+ # # #<Pathname:include/ruby>
+ # # #<Pathname:include/ruby.h>
+ # # => [#<Pathname:include/ruby>, #<Pathname:include/ruby.h>]
+ # ```
+ #
+ # With a block given and `with_dirnames` given as `false`,
+ # yields a new pathname for each child
+ # of the entry represented by `self` with its dirname omitted;
+ # returns an array of those pathnames:
+ #
+ # ```ruby
+ # Pathname('include').each_child(false) {|child| p child }
+ # # #<Pathname:ruby>
+ # # #<Pathname:ruby.h>
+ # # => [#<Pathname:ruby>, #<Pathname:ruby.h>]
+ # ```
+ #
+ # Note that entries `'.'` and `'..'` are not children.
+ #
+ # With no block given, returns a new Enumerator.
+ def each_child(with_directory=true, &b)
+ children(with_directory).each(&b)
+ end
+
+ #
+ # Returns a relative path from the given +base_directory+ to the receiver.
+ #
+ # If +self+ is absolute, then +base_directory+ must be absolute too.
+ #
+ # If +self+ is relative, then +base_directory+ must be relative too.
+ #
+ # This method doesn't access the filesystem. It assumes no symlinks.
+ #
+ # ArgumentError is raised when it cannot find a relative path.
+ #
+ # Note that this method does not handle situations where the case sensitivity
+ # of the filesystem in use differs from the operating system default.
+ #
+ def relative_path_from(base_directory)
+ base_directory = Pathname.new(base_directory) unless base_directory.is_a? Pathname
+ dest_directory = self.cleanpath.path
+ base_directory = base_directory.cleanpath.path
+ dest_prefix = dest_directory
+ dest_names = []
+ while r = chop_basename(dest_prefix)
+ dest_prefix, basename = r
+ dest_names.unshift basename if basename != '.'
+ end
+ base_prefix = base_directory
+ base_names = []
+ while r = chop_basename(base_prefix)
+ base_prefix, basename = r
+ base_names.unshift basename if basename != '.'
+ end
+ unless same_paths?(dest_prefix, base_prefix)
+ raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
+ end
+ while !dest_names.empty? &&
+ !base_names.empty? &&
+ same_paths?(dest_names.first, base_names.first)
+ dest_names.shift
+ base_names.shift
+ end
+ if base_names.include? '..'
+ raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
+ end
+ base_names.fill('..')
+ relpath_names = base_names + dest_names
+ if relpath_names.empty?
+ Pathname.new('.')
+ else
+ Pathname.new(File.join(*relpath_names))
+ end
+ end
+end
+
+class Pathname # * File *
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # each_line(sep = $/, **opts) {|line| ... } → nil
+ # each_line(limit, **opts) {|line| ... } → nil
+ # each_line(sep, limit, **opts) {|line| ... } → nil
+ # each_line(...) → new_enumerator
+ #
+ # With a block given, calls the block with each line
+ # from the file represented by `self`;
+ # returns `nil`:
+ #
+ # ```ruby
+ # lines = []
+ # Pathname('COPYING').each_line {|line| lines << line }
+ # lines.take(3)
+ # # =>
+ # # ["{日本語}[rdoc-ref:COPYING.ja]\n",
+ # # "\n",
+ # # "Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.\n"]
+ # ```
+ #
+ # The lines are read using IO.foreach,
+ # all arguments and options are passed to that method;
+ # see details at IO.foreach.
+ #
+ # With no block given, returns a new Enumerator.
+ def each_line(...) # :yield: line
+ File.foreach(@path, ...)
+ end
+
+ # call-seq:
+ # read(length = nil, offset = 0, **opts) -> string or nil
+ #
+ # Reads and returns some or all of the content of the file
+ # whose path is <tt>self.to_s</tt>.
+ #
+ # With no arguments given,
+ # reads in text mode and returns the entire content of the file:
+ #
+ # Pathname.new('t.txt').read
+ # # => "First line\nSecond line\n\nFourth line\nFifth line\n"
+ # Pathname.new('t.ja').read
+ # # => "こんにちは"
+ # Pathname.new('t.dat').read
+ # # => "\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"
+ #
+ # On Windows, text mode can terminate reading and leave bytes in the file unread
+ # when encountering certain special bytes.
+ # Consider using #binread if all bytes in the file should be read.
+ #
+ # With argument +length+ given, returns +length+ bytes if available:
+ #
+ # Pathname.new('t.txt').read(7)
+ # # => "First l"
+ # Pathname.new('t.ja').read(7)
+ # # => "\xE3\x81\x93\xE3\x82\x93\xE3"
+ # Pathname.new('t.dat').read(7)
+ # # => "\xFE\xFF\x99\x90\x99\x91\x99"
+ #
+ # Returns all bytes if +length+ is larger than the files size:
+ #
+ # Pathname.new('t.txt').read(700)
+ # # => "First line\r\nSecond line\r\n\r\nFourth line\r\nFifth line\r\n"
+ # Pathname.new('t.ja').read(700)
+ # # => "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF"
+ # Pathname.new('t.dat').read(700)
+ # # => "\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"
+ #
+ # With arguments +length+ and +offset+ given,
+ # returns +length+ bytes if available, beginning at the given +offset+:
+ #
+ # Pathname.new('t.txt').read(10, 2)
+ # # => "rst line\r\n"
+ # Pathname.new('t.ja').read(10, 2)
+ # # => "\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1"
+ # Pathname.new('t.dat').read(10, 2)
+ # # => "\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"
+ #
+ # Returns +nil+ if +offset+ is past the end of the file:
+ #
+ # Pathname.new('t.txt').read(10, 200) # => nil
+ #
+ # Optional keyword arguments +opts+ specify:
+ #
+ # - {Open Options}[rdoc-ref:IO@Open+Options].
+ # - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
+ #
+ def read(...) File.read(@path, ...) end
+
+ # call-seq:
+ # binread(length = nil, offset = 0) -> string or nil
+ #
+ # Behaves like #read, except that the file is opened in binary mode
+ # with ASCII-8BIT encoding.
+ #
+ def binread(...) File.binread(@path, ...) end
+
+ # See <tt>File.readlines</tt>. Returns all the lines from the file.
+ def readlines(...) File.readlines(@path, ...) end
+
+ # See <tt>File.sysopen</tt>.
+ def sysopen(...) File.sysopen(@path, ...) end
+
+ # call-seq:
+ # write(data, offset = 0, **opts) -> nonnegative_integer
+ #
+ # Opens the file at +self.to_s+, writes the given +data+ to it,
+ # and closes the file; returns the number of bytes written.
+ #
+ # With only argument +data+ given, writes the given data to the file:
+ #
+ # path = 't.tmp'
+ # pn = Pathname.new(path)
+ # pn.write('foo') # => 3
+ # File.read(path) # => "foo"
+ #
+ # If +offset+ is zero (the default), the file is overwritten:
+ #
+ # pn.write('bar')
+ # File.read(path) # => "bar"
+ #
+ # If +offset+ in within the file content, the file is partly overwritten:
+ #
+ # pn.write('foobarbaz')
+ # pn.write('BAR', 3)
+ # File.read(path) # => "fooBARbaz"
+ #
+ # If +offset+ is outside the file content,
+ # the file is padded with null characters <tt>"\u0000"</tt>:
+ #
+ # pn.write('bat', 12)
+ # File.read(path) # => "fooBARbaz\u0000\u0000\u0000bat"
+ #
+ # Optional keyword arguments +opts+ specify:
+ #
+ # - {Open Options}[rdoc-ref:IO@Open+Options].
+ # - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
+ #
+ def write(...) File.write(@path, ...) end
+
+ # call-seq:
+ # binwrite(string, offset = 0, **opts) -> nonnegative_integer
+ #
+ # Behaves like #write, except that the file is opened in binary mode
+ # with ASCII-8BIT encoding.
+ def binwrite(...) File.binwrite(@path, ...) end
+
+ # call-seq:
+ # atime -> new_time
+ #
+ # Returns a new Time object containing the time of the most recent
+ # access (read or write) to the entry represented by `self`;
+ # see {File System Timestamps}[rdoc-ref:file/timestamps.md]:
+ #
+ # # Work in a temporary directory.
+ # require 'tmpdir'
+ # Dir.mktmpdir do |tmpdirpath|
+ # # A subdirectory therein, and its Pathname.
+ # dirpath = File.join(tmpdirpath, 'subdir')
+ # Dir.mkdir(dirpath)
+ # dir_pn = Pathname(dirpath)
+ # puts "Create directory; establishes atime for directory."
+ # puts " Directory atime: #{dir_pn.atime}"
+ # sleep(1)
+ #
+ # # A file in the subdirectory, and its Pathname.
+ # filepath = File.join(dirpath, 't.txt')
+ # puts "Create file; establishes atime for file, updates atime for directory."
+ # File.write(filepath, 'foo')
+ # file_pn = Pathname(filepath)
+ # puts " File atime: #{file_pn.atime}"
+ # puts " Directory atime: #{dir_pn.atime}"
+ # sleep(1)
+ # puts "Write file; updates atimes for file and directory."
+ # File.write(filepath, 'bar')
+ # puts " File atime: #{file_pn.atime}"
+ # puts " Directory atime: #{dir_pn.atime}"
+ # end
+ #
+ # Output:
+ #
+ # Create directory; establishes atime for directory.
+ # Directory atime: 2026-05-14 14:36:43 +0100
+ # Create file; establishes atime for file, updates atime for directory.
+ # File atime: 2026-05-14 14:36:44 +0100
+ # Directory atime: 2026-05-14 14:36:44 +0100
+ # Write file; updates atimes for file and directory.
+ # File atime: 2026-05-14 14:36:45 +0100
+ # Directory atime: 2026-05-14 14:36:45 +0100
+ #
+ def atime() File.atime(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # birthtime -> new_time
+ #
+ # Returns a new Time object containing the create time of the entry
+ # represented by `self`;
+ # see [File System Timestamps](rdoc-ref:file/timestamps.md):
+ #
+ # ```ruby
+ # # Work in a temporary directory.
+ # Pathname.mktmpdir do |tmpdirpath|
+ # # A subdirectory therein, and its Pathname.
+ # dirpath = File.join(tmpdirpath, 'subdir')
+ # dir_pn = Pathname(dirpath)
+ # puts "Create directory; directory birthtime established."
+ # dir_pn.mkdir
+ # puts " Directory birthtime: #{dir_pn.birthtime}"
+ # sleep(1)
+ #
+ # # A file in the subdirectory, and its Pathname.
+ # filepath = File.join(dirpath, 't.txt')
+ # file_pn = Pathname(filepath)
+ # puts "Create file; file birthtime established; directory birthtime not updated."
+ # file_pn.write('foo')
+ # puts " File birthtime: #{file_pn.birthtime}"
+ # puts " Directory birthtime: #{dir_pn.birthtime}"
+ # sleep(1)
+ # puts "Write file; neither birthtime updated."
+ # file_pn.write('bar')
+ # puts " File birthtime: #{file_pn.birthtime}"
+ # puts " Directory birthtime: #{dir_pn.birthtime}"
+ # end
+ # ```
+ #
+ # Output:
+ #
+ # ```text
+ # Create directory; directory birthtime established.
+ # Directory birthtime: 2026-05-14 23:41:12 +0100
+ # Create file; file birthtime established; directory birthtime not updated.
+ # File birthtime: 2026-05-14 23:41:13 +0100
+ # Directory birthtime: 2026-05-14 23:41:12 +0100
+ # Write file; neither birthtime updated.
+ # File birthtime: 2026-05-14 23:41:13 +0100
+ # Directory birthtime: 2026-05-14 23:41:12 +0100
+ # ```
+ #
+ def birthtime() File.birthtime(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # ctime -> new_time
+ #
+ # On Windows, returns the #birthtime.
+ #
+ # On other systems,
+ # returns a new Time object containing the time of the most recent
+ # metadata change to the entry represented by `self`;
+ # see {File System Timestamps}[rdoc-ref:file/timestamps.md]:
+ #
+ # ```ruby
+ # # Work in a temporary directory.
+ # Pathname.mktmpdir do |tmpdirpath|
+ # # A subdirectory therein, and its Pathname.
+ # dirpath = File.join(tmpdirpath, 'subdir')
+ # dir_pn = Pathname(dirpath)
+ # puts "Create directory; directory ctime established."
+ # dir_pn.mkdir
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # sleep(1)
+ #
+ # # A file in the subdirectory, and its Pathname.
+ # filepath = File.join(dirpath, 't.txt')
+ # file_pn = Pathname(filepath)
+ # puts "Create file; file ctime established; directory ctime updated."
+ # file_pn.write('foo')
+ # puts " File ctime: #{file_pn.ctime}"
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # sleep(1)
+ # puts "Write file; file ctime updated; directory ctime not updated."
+ # file_pn.write('bar')
+ # puts " File ctime: #{file_pn.ctime}"
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # sleep(1)
+ # puts "Read file; neither ctime not updated."
+ # file_pn.read
+ # puts " File ctime: #{file_pn.ctime}"
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # end
+ # ```
+ #
+ # Output:
+ #
+ # ```text
+ # Create directory; directory ctime established.
+ # Directory ctime: 2026-05-20 14:05:05 -0500
+ # Create file; file ctime established; directory ctime updated.
+ # File ctime: 2026-05-20 14:05:06 -0500
+ # Directory ctime: 2026-05-20 14:05:06 -0500
+ # Write file; file ctime updated; directory ctime not updated.
+ # File ctime: 2026-05-20 14:05:07 -0500
+ # Directory ctime: 2026-05-20 14:05:06 -0500
+ # Read file; neither ctime not updated.
+ # File ctime: 2026-05-20 14:05:07 -0500
+ # Directory ctime: 2026-05-20 14:05:06 -0500
+ # ```
+ #
+ def ctime() File.ctime(@path) end
+
+ # See <tt>File.mtime</tt>. Returns last modification time.
+ def mtime() File.mtime(@path) end
+
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # chmod(mode) -> 1
+ #
+ # Changes the mode (i.e., permissions) of the entry represented by `self`;
+ # see {File Permissions}[rdoc-ref:File@File+Permissions];
+ # returns `1`:
+ #
+ # ```ruby
+ # # A helper method to make an integer mode display as octal.
+ # def pretty(mode); '0' + (mode & 0777).to_s(8); end
+ #
+ # # Work in a temporary directory.
+ # Pathname.mktmpdir do |tmpdirpath|
+ # # A subdirectory therein, and its Pathname.
+ # dirpath = File.join(tmpdirpath, 'subdir')
+ # dir_pn = Pathname(dirpath)
+ # dir_pn.mkdir
+ # # The directory mode.
+ # puts "Original directory mode: #{pretty(dir_pn.stat.mode)}"
+ # # Change the directory mode.
+ # dir_pn.chmod(0777)
+ # puts "New directory mode: #{pretty(dir_pn.stat.mode)}"
+ #
+ # # A file in the subdirectory, and its Pathname.
+ # filepath = File.join(dirpath, 't.txt')
+ # file_pn = Pathname(filepath)
+ # # Create the file.
+ # file_pn.write('foo')
+ # # The file mode.
+ # puts "Original file mode: #{pretty(file_pn.stat.mode)}"
+ # # Change the file modes.
+ # file_pn.chmod(0777)
+ # puts "New file mode: #{pretty(file_pn.stat.mode)}"
+ # end
+ # ```
+ #
+ # Output:
+ #
+ # ```text
+ # Original directory mode: 0775
+ # New directory mode: 0777
+ # Original file mode: 0664
+ # New file mode: 0777
+ # ```
+ #
+ def chmod(mode) File.chmod(mode, @path) end
+
+ # See <tt>File.lchmod</tt>.
+ def lchmod(mode) File.lchmod(mode, @path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # chown(owner_id, group_id) -> 0
+ #
+ # Changes the owner and group of an entry (directory or file):
+ #
+ # ```ruby
+ # # Work in a temporary directory.
+ # Pathname.mktmpdir do |tmpdirpath|
+ # # A subdirectory therein, and its Pathname.
+ # dirpath = File.join(tmpdirpath, 'subdir')
+ # dir_pn = Pathname(dirpath)
+ # dir_pn.mkdir
+ # dir_stat = File.stat(dirpath)
+ # puts "Original directory owner: #{dir_stat.uid}"
+ # puts "Original directory group: #{dir_stat.gid}"
+ # dir_pn.chown(1000, 1000)
+ # dir_stat = File.stat(dirpath)
+ # puts "New directory owner: #{dir_stat.uid}"
+ # puts "New directory group: #{dir_stat.gid}"
+ #
+ # # A file in the subdirectory, and its Pathname.
+ # filepath = File.join(dirpath, 't.txt')
+ # file_pn = Pathname(filepath)
+ # # Create the file.
+ # file_pn.write('foo')
+ # file_stat = File.stat(filepath)
+ # puts "Original file owner: #{file_stat.uid}"
+ # puts "Original file group: #{file_stat.gid}"
+ # file_pn = Pathname(dirpath)
+ # file_pn.chown(1000, 1000)
+ # file_stat = File.stat(dirpath)
+ # puts "New file owner: #{file_stat.uid}"
+ # puts "New file group: #{file_stat.gid}"
+ # end
+ # ```
+ #
+ # Output:
+ #
+ # ```text
+ # Original directory owner: 0
+ # Original directory group: 0
+ # New directory owner: 1000
+ # New directory group: 1000
+ # Original file owner: 0
+ # Original file group: 0
+ # New file owner: 1000
+ # New file group: 1000
+ # ```
+ #
+ # Notes:
+ #
+ # - On Windows, the owner and group are not changed.
+ # - Only a process with superuser privileges can change the owner of an entry.
+ # - The owner of an entry can change its group to any group
+ # to which the owner belongs.
+ # - A +nil+ or +-1+ owner or group id is ignored.
+ # - The method follows symbolic links to the target entry.
+ #
+ def chown(owner, group) File.chown(owner, group, @path) end
+
+ # See <tt>File.lchown</tt>.
+ def lchown(owner, group) File.lchown(owner, group, @path) end
+
+ # See <tt>File.fnmatch</tt>. Return +true+ if the receiver matches the given
+ # pattern.
+ def fnmatch(pattern, ...) File.fnmatch(pattern, @path, ...) end
+
+ # See <tt>File.fnmatch?</tt> (same as #fnmatch).
+ def fnmatch?(pattern, ...) File.fnmatch?(pattern, @path, ...) end
+
+ # See <tt>File.ftype</tt>. Returns "type" of file ("file", "directory",
+ # etc).
+ def ftype() File.ftype(@path) end
+
+ # See <tt>File.link</tt>. Creates a hard link.
+ def make_link(old) File.link(old, @path) end
+
+ # See <tt>File.open</tt>. Opens the file for reading or writing.
+ def open(...) # :yield: file
+ File.open(@path, ...)
+ end
+
+ # See <tt>File.readlink</tt>. Read symbolic link.
+ def readlink() self.class.new(File.readlink(@path)) end
+
+ # See <tt>File.rename</tt>. Rename the file.
+ def rename(to) File.rename(@path, to) end
+
+ # See <tt>File.stat</tt>. Returns a <tt>File::Stat</tt> object.
+ def stat() File.stat(@path) end
+
+ # See <tt>File.lstat</tt>.
+ def lstat() File.lstat(@path) end
+
+ # See <tt>File.symlink</tt>. Creates a symbolic link.
+ def make_symlink(old) File.symlink(old, @path) end
+
+ # See <tt>File.truncate</tt>. Truncate the file to +length+ bytes.
+ def truncate(length) File.truncate(@path, length) end
+
+ # See <tt>File.utime</tt>. Update the access and modification times.
+ def utime(atime, mtime) File.utime(atime, mtime, @path) end
+
+ # Update the access and modification times of the file.
+ #
+ # Same as Pathname#utime, but does not follow symbolic links.
+ #
+ # See File.lutime.
+ def lutime(atime, mtime) File.lutime(atime, mtime, @path) end
+
+ # call-seq:
+ # basename(path, suffix = '') -> new_pathname
+ #
+ # Returns a new \Pathname object containing all or part of the last entry
+ # of the path represented by +self+.
+ # Entries are delimited by the value of constant File::SEPARATOR
+ # and, if non-nil, the value of constant File::ALT_SEPARATOR.
+ #
+ # When +suffix+ is the empty string <tt>''</tt>, returns all of the last entry:
+ #
+ # Pathname.new('foo/bar/baz/bat.txt').basename # => #<Pathname:bat.txt>
+ # Pathname.new('foo/bar/baz').basename # => #<Pathname:baz>
+ #
+ # File::SEPARATOR # => "/"
+ # Pathname.new('foo/bar.txt////').basename # => #<Pathname:bar.txt>
+ # File::ALT_SEPARATOR # => "\\" # On Windows.
+ # Pathname.new('foo/bar.txt//\\\\//').basename # => #<Pathname:bar.txt>
+ #
+ # When +suffix+ is <tt>'.*'</tt>,
+ # the last {filename extension}[https://en.wikipedia.org/wiki/Filename_extension],
+ # if any, is removed:
+ #
+ # Pathname.new('foo/bar.txt').basename('.*') # => #<Pathname:bar>
+ # Pathname.new('foo/bar.txt.old').basename('.*') # => #<Pathname:bar.txt>
+ # Pathname.new('foo/bar').basename('.*') # => #<Pathname:bar>
+ #
+ # When +suffix+ is any string other than <tt>''</tt> or <tt>'.*'</tt>,
+ # the matching trailing substring, if any, is removed:
+ #
+ # Pathname.new('foo/bar.txt').basename('.txt') # => #<Pathname:bar>
+ # Pathname.new('foo/bar.txt').basename('txt') # => #<Pathname:bar.>
+ # Pathname.new('foo/bar.txt').basename('*') # => #<Pathname:bar.txt>
+ # Pathname.new('foo/bar.txt').basename('.') # => #<Pathname:bar.txt>
+ #
+ def basename(...) self.class.new(File.basename(@path, ...)) end
+
+ # See <tt>File.dirname</tt>. Returns all but the last component of the path.
+ def dirname() self.class.new(File.dirname(@path)) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # extname -> extension
+ #
+ # Returns the filename extension of `self` --
+ # usually the portion of the string path beginning from the last period:
+ #
+ # ```ruby
+ # Pathname('t.rb').extname # => ".rb"
+ # Pathname('foo.bar.t.rb').extname # => ".rb"
+ # Pathname('foo/bar/t.rb').extname # => ".rb"
+ # Pathname('nosuch.txt').extname # => ".txt" # Path need not exist.
+ # ```
+ #
+ # Returns the entire string when there is no period:
+ #
+ # ```ruby
+ # Pathname('foo').extname # => ""
+ # ```
+ #
+ # Returns an empty string when the only period is the first character:
+ #
+ # ```ruby
+ # Pathname('.irbrc').extname # => ""
+ # ```
+ #
+ # Returns an empty string or `'.'` when `path` ends with a period:
+ #
+ # ```ruby
+ # Pathname('foo.').extname # => "" # On Windows.
+ # Pathname('foo.').extname # => "." # Elsewhere.
+ # Pathname('foo....').extname # => "" # On Windows.
+ # Pathname('foo....').extname # => "." # Elsewhere.
+ # ```
+ #
+ def extname() File.extname(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # expand_path(dirpath = '.') -> new_pathname
+ #
+ # Returns a new pathname containing the absolute path for `self`.
+ #
+ # Evaluates a relative path with respect to the directory given by `dirpath`:
+ #
+ # ```ruby
+ # Dir.chdir('/snap')
+ # # Default dirpath.
+ # Pathname('README').expand_path # => #<Pathname:/snap/README>
+ # Pathname('bin').expand_path # => #<Pathname:/snap/bin>
+ # Pathname('bin/../var').expand_path # => #<Pathname:/snap/var> # Cleaned.
+ # # Other dirpath.
+ # Pathname('../zip').expand_path('/usr/bin/ruby') # => #<Pathname:/usr/bin/zip>
+ # Dir.chdir('/usr/bin')
+ # Pathname('../../snap').expand_path(__FILE__) # => #<Pathname:/usr/snap>
+ # ```
+ #
+ # Evaluates an absolute path without respect to `dirpath`:
+ #
+ # ```ruby
+ # Pathname('/snap').expand_path # => #<Pathname:/snap>
+ # Pathname('/snap').expand_path.expand_path('nosuch') # => #<Pathname:/snap>
+ # Pathname('/snap/../snap').expand_path # => #<Pathname:/snap> # Cleaned.
+ # ```
+ #
+ # More examples:
+ #
+ # ```
+ # Dir.chdir('/usr/bin')
+ # Pathname('../../snap').expand_path(__FILE__) # => #<Pathname:/usr/snap>
+ # Pathname('../../snap').expand_path # => #<Pathname:/snap>
+ # ```
+ #
+ def expand_path(...) self.class.new(File.expand_path(@path, ...)) end
+
+ # See <tt>File.split</tt>. Returns the #dirname and the #basename in an
+ # Array.
+ def split()
+ array = File.split(@path)
+ raise TypeError, 'wrong argument type nil (expected Array)' unless Array === array
+ array.map {|f| self.class.new(f) }
+ end
+
+ # Returns the real (absolute) pathname for +self+ in the actual filesystem.
+ #
+ # Does not contain symlinks or useless dots, +..+ and +.+.
+ #
+ # All components of the pathname must exist when this method is called.
+ def realpath(...) self.class.new(File.realpath(@path, ...)) end
+
+ # Returns the real (absolute) pathname of +self+ in the actual filesystem.
+ #
+ # Does not contain symlinks or useless dots, +..+ and +.+.
+ #
+ # The last component of the real pathname can be nonexistent.
+ def realdirpath(...) self.class.new(File.realdirpath(@path, ...)) end
+end
+
+
+class Pathname # * FileTest *
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # blockdev? => true or false
+ #
+ # Returns whether `self` represents a path to a block device
+ # (i.e., a direct-access device):
+ #
+ # ```ruby
+ # Pathname('/dev/nvme0n1').blockdev? # => true
+ # Pathname('/dev/loop0').blockdev? # => true
+ # Pathname('/dev/tty').blockdev? # => false
+ # Pathname('/dev/null').blockdev? # => false
+ # Pathname('nosuch').blockdev? # => false
+ # Pathname($stdin).blockdev? # => false
+ # ```
+ #
+ # The returned value is OS-dependent; on Windows, almost always `false`.
+ def blockdev?() FileTest.blockdev?(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # chardev? => true or false
+ #
+ # Returns whether `self` represents a path to a character device
+ # (i.e., a sequential-access device):
+ #
+ # ```ruby
+ # Pathname('/dev/tty').chardev? # => true
+ # Pathname('/dev/null').chardev? # => true
+ # Pathname('/dev/nvme0n1').chardev? # => false
+ # Pathname('/dev/loop0').chardev? # => false
+ # Pathname($stdin).chardev? # => false
+ # Pathname('nosuch').chardev? # => false
+ # ```
+ #
+ # The returned value is OS-dependent; on Windows, almost always `false`.
+ def chardev?() FileTest.chardev?(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # empty? -> true or false
+ #
+ # Returns whether the entry represented by `self` exists and is empty:
+ #
+ # ```ruby
+ # dir_pn = Pathname('example_dir')
+ # dir_pn.empty? # => false # Dir does not exist.
+ # dir_pn.mkdir
+ # dir_pn.empty? # => true # Dir exists and is empty.
+ #
+ # file_pn = Pathname('example_dir/example.txt')
+ # file_pn.empty? # => false # File does not exist.
+ # file_pn.write('')
+ # file_pn.empty? # => true # File exists and is empty.
+ # dir_pn.empty? # => false # Dir exists and is not empty.
+ # file_pn.write('foo')
+ # file_pn.empty? # => false # File exists and is not empty.
+ #
+ # file_pn.delete
+ # dir_pn.delete
+ # ```
+ #
+ def empty?
+ if FileTest.directory?(@path)
+ Dir.empty?(@path)
+ else
+ File.empty?(@path)
+ end
+ end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # executable? -> true or false
+ #
+ # Returns whether the entry represented by `self` is executable;
+ # calls FileTest.executable? with argument `self.to_s`:
+ #
+ # ```ruby
+ # Pathname('bin/gem').executable? # => true
+ # Pathname('README.md').executable? # => false
+ # ```
+ #
+ def executable?() FileTest.executable?(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # executable_real? -> true or false
+ #
+ # Returns whether the entry represented by `self` is executable
+ # by the real user and group id of the current process;
+ # calls FileTest.executable_real? with argument `self.to_s`:
+ #
+ # ```ruby
+ # pn = Pathname('example')
+ # pn.write('')
+ # pn.executable_real? # => false
+ # pn.chmod(0100)
+ # pn.executable_real? # => true
+ # ```
+ #
+ def executable_real?() FileTest.executable_real?(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # exist? -> true or false
+ #
+ # Returns whether the entry represented by `self` exists:
+ #
+ # ```ruby
+ # Pathname('.').exist? # => true
+ # Pathname('README.md').exist? # => true
+ # Pathname('nosuch').exist? # => false
+ # ```
+ #
+ def exist?() FileTest.exist?(@path) end
+
+ # See <tt>FileTest.grpowned?</tt>.
+ def grpowned?() FileTest.grpowned?(@path) end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # directory? -> true or false
+ #
+ # Returns whether the entry represented by `self` is a directory:
+ #
+ # ```ruby
+ # Pathname('/etc').directory? # => true
+ # Pathname('lib').directory? # => true
+ # Pathname('README.md').directory? # => false
+ # Pathname('nosuch').directory? # => false
+ # ```
+ #
+ def directory?() FileTest.directory?(@path) end
+
+ # See <tt>FileTest.file?</tt>.
+ def file?() FileTest.file?(@path) end
+
+ # See <tt>FileTest.pipe?</tt>.
+ def pipe?() FileTest.pipe?(@path) end
+
+ # See <tt>FileTest.socket?</tt>.
+ def socket?() FileTest.socket?(@path) end
+
+ # See <tt>FileTest.owned?</tt>.
+ def owned?() FileTest.owned?(@path) end
+
+ # See <tt>FileTest.readable?</tt>.
+ def readable?() FileTest.readable?(@path) end
+
+ # See <tt>FileTest.world_readable?</tt>.
+ def world_readable?() File.world_readable?(@path) end
+
+ # See <tt>FileTest.readable_real?</tt>.
+ def readable_real?() FileTest.readable_real?(@path) end
+
+ # See <tt>FileTest.setuid?</tt>.
+ def setuid?() FileTest.setuid?(@path) end
+
+ # See <tt>FileTest.setgid?</tt>.
+ def setgid?() FileTest.setgid?(@path) end
+
+ # See <tt>FileTest.size</tt>.
+ def size() FileTest.size(@path) end
+
+ # See <tt>FileTest.size?</tt>.
+ def size?() FileTest.size?(@path) end
+
+ # See <tt>FileTest.sticky?</tt>.
+ def sticky?() FileTest.sticky?(@path) end
+
+ # See <tt>FileTest.symlink?</tt>.
+ def symlink?() FileTest.symlink?(@path) end
+
+ # See <tt>FileTest.writable?</tt>.
+ def writable?() FileTest.writable?(@path) end
+
+ # See <tt>FileTest.world_writable?</tt>.
+ def world_writable?() File.world_writable?(@path) end
+
+ # See <tt>FileTest.writable_real?</tt>.
+ def writable_real?() FileTest.writable_real?(@path) end
+
+ # See <tt>FileTest.zero?</tt>.
+ def zero?() FileTest.zero?(@path) end
+end
+
+
+class Pathname # * Dir *
+ # call-seq:
+ # glob(patterns, **kwargs) → array_of_pathnames
+ # glob(patterns, **kwargs) {|pathname| ... } → nil
+ #
+ # Calls <tt>Dir.glob(patterns, **kwargs)</tt>, which yields or returns entry names;
+ # see Dir.glob.
+ #
+ # Required argument +patterns+ is a string pattern or an array of string patterns;
+ # note that these patterns are not regexps.
+ #
+ # Keyword arguments <tt>**kwargs</tt> are passed through to Dir.glob;
+ # see the documentation there.
+ #
+ # With no block given, returns an array of \Pathname objects;
+ # each is <tt>Pathname.new(entry_name)</tt> for an entry name returned by Dir.glob.
+ #
+ # Pathname.glob('*').take(3)
+ # # => [#<Pathname:BSDL>, #<Pathname:CONTRIBUTING.md>, #<Pathname:COPYING>]
+ # Pathname.glob(['o*', 'a*']).take(3)
+ # # => [#<Pathname:object.c>, #<Pathname:aclocal.m4>, #<Pathname:addr2line.c>]
+ #
+ # With a block given, calls the block with each pathname
+ # <tt>Pathname.new(entry_name)</tt>,
+ # where each +entry_name+ is a \Pathname object created by the value yielded by Dir.glob.
+ #
+ # a = []
+ # Pathname.glob(['o*', 'a*']) {|pathname| a << pathname }
+ # a.take(3)
+ # # => [#<Pathname:object.c>, #<Pathname:aclocal.m4>, #<Pathname:addr2line.c>]
+ #
+ # Optional keyword argument +base+ is of particular interest.
+ # When it is given, its value specifies the base directory for the pathnames;
+ # each pattern string specifies entries relative to the base directory:
+ #
+ # Pathname.glob('*', base: 'lib').take(2)
+ # # => [#<Pathname:English.gemspec>, #<Pathname:English.rb>]
+ # Pathname.glob('*', base: 'lib/bundler').take(2)
+ # # => [#<Pathname:build_metadata.rb>, #<Pathname:bundler.gemspec>]
+ #
+ # Note that the base directory is not prepended to the entry names in the result.
+ def Pathname.glob(*args, **kwargs) # :yield: pathname
+ if block_given?
+ Dir.glob(*args, **kwargs) {|f| yield self.new(f) }
+ else
+ Dir.glob(*args, **kwargs).map {|f| self.new(f) }
+ end
+ end
+
+ # Returns or yields Pathname objects.
+ #
+ # Pathname("ruby-2.4.2").glob("R*.md")
+ # #=> [#<Pathname:ruby-2.4.2/README.md>, #<Pathname:ruby-2.4.2/README.ja.md>]
+ #
+ # See Dir.glob.
+ # This method uses the +base+ keyword argument of Dir.glob.
+ def glob(*args, **kwargs) # :yield: pathname
+ if block_given?
+ Dir.glob(*args, **kwargs, base: @path) {|f| yield self + f }
+ else
+ Dir.glob(*args, **kwargs, base: @path).map {|f| self + f }
+ end
+ end
+
+ # call-seq:
+ # Pathname.getwd -> new_pathname
+ #
+ # Returns a new \Pathname object containing the path to the current working directory
+ # (equivalent to <tt>Pathname.new(Dir.getwd)</tt>):
+ #
+ # Pathname.getwd # => #<Pathname:/home>
+ #
+ def Pathname.getwd() self.new(Dir.getwd) end
+ class << self
+ alias pwd getwd
+ end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # entries -> array_of_pathnames
+ #
+ # Returns an array of pathnames,
+ # one for each entry in the directory represented by `self`:
+ #
+ # ```ruby
+ # Pathname('.').entries.take(5)
+ # # =>
+ # # [#<Pathname:.>,
+ # # #<Pathname:..>,
+ # # #<Pathname:gc.rb>,
+ # # #<Pathname:yjit.rb>,
+ # # #<Pathname:iseq.h>]
+ # ```
+ #
+ def entries() Dir.entries(@path).map {|f| self.class.new(f) } end
+
+ # :markup: markdown
+ #
+ # call-seq:
+ # each_entry {|entry| ... } -> nil
+ # each_entry -> new_enumerator
+ #
+ # With a block given,
+ # yields a new pathname for each entry
+ # in the entry represented by `self`;
+ # returns `nil`:
+ #
+ # ```ruby
+ # Pathname('include').each_entry {|entry| p entry }
+ # # #<Pathname:ruby>
+ # # #<Pathname:..>
+ # # #<Pathname:ruby.h>
+ # # #<Pathname:.>
+ # # => nil
+ # ```
+ #
+ # With no block given, returns a new Enumerator.
+ def each_entry(&block) # :yield: pathname
+ return to_enum(__method__) unless block_given?
+ Dir.foreach(@path) {|f| yield self.class.new(f) }
+ end
+
+ # See <tt>Dir.mkdir</tt>. Create the referenced directory.
+ def mkdir(...) Dir.mkdir(@path, ...) end
+
+ # See <tt>Dir.rmdir</tt>. Remove the referenced directory.
+ def rmdir() Dir.rmdir(@path) end
+
+ # See <tt>Dir.open</tt>.
+ def opendir(&block) # :yield: dir
+ Dir.open(@path, &block)
+ end
+end
+
+class Pathname # * mixed *
+ #
+ # :markup: markdown
+ #
+ # call-seq:
+ # unlink -> 1 or 0
+ #
+ # Removes the file or directory represented by `self`, using:
+ #
+ # - File.unlink, if `self` represents a file; returns `1`.
+ # - Dir.unlink, if `self` represents a directory; returns `0`.
+ #
+ # Examples:
+ #
+ # ```ruby
+ # Pathname(Tempfile.create).unlink # => 1
+ # Pathname(Pathname.mktmpdir).unlink # => 0
+ # ```
+ #
+ def unlink()
+ Dir.unlink @path
+ rescue Errno::ENOTDIR
+ File.unlink @path
+ end
+ alias delete unlink
+end
+
+class Pathname
+ undef =~ if Kernel.method_defined?(:=~)
+end
+
+module Kernel
+ # Creates a Pathname object.
+ def Pathname(path) # :doc:
+ return path if Pathname === path
+ Pathname.new(path)
+ end
+ module_function :Pathname
+end