summaryrefslogtreecommitdiff
path: root/lib/fileutils.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/fileutils.rb')
-rw-r--r--lib/fileutils.rb587
1 files changed, 378 insertions, 209 deletions
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index 23aef6e146..0706e007ca 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -3,7 +3,7 @@
begin
require 'rbconfig'
rescue LoadError
- # for make mjit-headers
+ # for make rjit-headers
end
# Namespace for file utility methods for copying, moving, removing, etc.
@@ -12,8 +12,8 @@ end
#
# First, what’s elsewhere. \Module \FileUtils:
#
-# - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html].
-# - Supplements {class File}[https://docs.ruby-lang.org/en/master/File.html]
+# - Inherits from {class Object}[rdoc-ref:Object].
+# - Supplements {class File}[rdoc-ref:File]
# (but is not included or extended there).
#
# Here, module \FileUtils provides methods that are useful for:
@@ -24,7 +24,7 @@ end
# - {Setting}[rdoc-ref:FileUtils@Setting].
# - {Comparing}[rdoc-ref:FileUtils@Comparing].
# - {Copying}[rdoc-ref:FileUtils@Copying].
-# - {Moving}[rdoc-ref:FileUtils@Copying].
+# - {Moving}[rdoc-ref:FileUtils@Moving].
# - {Options}[rdoc-ref:FileUtils@Options].
#
# === Creating
@@ -36,6 +36,7 @@ end
# - ::ln, ::link: Creates hard links.
# - ::ln_s, ::symlink: Creates symbolic links.
# - ::ln_sf: Creates symbolic links, overwriting if necessary.
+# - ::ln_sr: Creates symbolic links relative to targets
#
# === Deleting
#
@@ -76,8 +77,9 @@ end
# - ::copy_stream: Copies a stream.
# - ::cp, ::copy: Copies files.
# - ::cp_lr: Recursively creates hard links.
-# - ::cp_r: Recursively copies files.
-# - ::install: Recursively copies files (with options different from ::cp_r).
+# - ::cp_r: Recursively copies files, retaining mode, owner, and group.
+# - ::install: Recursively copies files, optionally setting mode,
+# owner, and group.
#
# === Moving
#
@@ -115,22 +117,16 @@ end
# system(command)
# end
#
-# To illustrate, here's the tree for the test directory for \FileUtils:
-# tree('test')
-# test
-# |-- fileutils
-# | |-- clobber.rb
-# | |-- fileasserts.rb
-# | |-- test_dryrun.rb
-# | |-- test_fileutils.rb
-# | |-- test_nowrite.rb
-# | |-- test_verbose.rb
-# | `-- visibility_tests.rb
-# `-- lib
-# |-- core_assertions.rb
-# |-- envutil.rb
-# |-- find_executable.rb
-# `-- helper.rb
+# To illustrate:
+#
+# tree('src0')
+# # => src0
+# # |-- sub0
+# # | |-- src0.txt
+# # | `-- src1.txt
+# # `-- sub1
+# # |-- src2.txt
+# # `-- src3.txt
#
# == Avoiding the TOCTTOU Vulnerability
#
@@ -166,9 +162,9 @@ end
# \Method \FileUtils.remove_entry_secure removes securely
# by applying a special pre-process:
#
-# - If the target path points to a directory, this method uses
-# {chown(2)}[https://man7.org/linux/man-pages/man2/chown.2.html]
-# and {chmod(2)}[https://man7.org/linux/man-pages/man2/chmod.2.html]
+# - If the target path points to a directory, this method uses methods
+# {File#chown}[rdoc-ref:File#chown]
+# and {File#chmod}[rdoc-ref:File#chmod]
# in removing directories.
# - The owner of the target directory should be either the current process
# or the super user (root).
@@ -184,7 +180,8 @@ end
# - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452].
#
module FileUtils
- VERSION = "1.6.0"
+ # The version number.
+ VERSION = "1.8.0"
def self.private_module_function(name) #:nodoc:
module_function name
@@ -196,7 +193,7 @@ module FileUtils
#
# FileUtils.pwd # => "/rdoc/fileutils"
#
- # FileUtils.getwd is an alias for FileUtils.pwd.
+ # Related: FileUtils.cd.
#
def pwd
Dir.pwd
@@ -237,7 +234,7 @@ module FileUtils
# cd ..
# cd fileutils
#
- # FileUtils.chdir is an alias for FileUtils.cd.
+ # Related: FileUtils.pwd.
#
def cd(dir, verbose: nil, &block) # :yield: dir
fu_output_message "cd #{dir}" if verbose
@@ -263,6 +260,8 @@ module FileUtils
#
# A non-existent file is considered to be infinitely old.
#
+ # Related: FileUtils.touch.
+ #
def uptodate?(new, old_list)
return false unless File.exist?(new)
new_time = File.mtime(new)
@@ -290,7 +289,7 @@ module FileUtils
#
# With no keyword arguments, creates a directory at each +path+ in +list+
# by calling: <tt>Dir.mkdir(path, mode)</tt>;
- # see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]:
+ # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]:
#
# FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"]
# FileUtils.mkdir('tmp4') # => ["tmp4"]
@@ -298,7 +297,7 @@ module FileUtils
# Keyword arguments:
#
# - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>;
- # see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
+ # see {File.chmod}[rdoc-ref:File.chmod].
# - <tt>noop: true</tt> - does not create directories.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
@@ -313,6 +312,8 @@ module FileUtils
# Raises an exception if any path points to an existing
# file or directory, or if for any reason a directory cannot be created.
#
+ # Related: FileUtils.mkdir_p.
+ #
def mkdir(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
@@ -336,7 +337,7 @@ module FileUtils
# With no keyword arguments, creates a directory at each +path+ in +list+,
# along with any needed ancestor directories,
# by calling: <tt>Dir.mkdir(path, mode)</tt>;
- # see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]:
+ # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]:
#
# FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]
# FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"]
@@ -344,7 +345,7 @@ module FileUtils
# Keyword arguments:
#
# - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>;
- # see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
+ # see {File.chmod}[rdoc-ref:File.chmod].
# - <tt>noop: true</tt> - does not create directories.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
@@ -360,6 +361,8 @@ module FileUtils
#
# FileUtils.mkpath and FileUtils.makedirs are aliases for FileUtils.mkdir_p.
#
+ # Related: FileUtils.mkdir.
+ #
def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
@@ -369,7 +372,7 @@ module FileUtils
path = remove_trailing_slash(item)
stack = []
- until File.directory?(path)
+ until File.directory?(path) || File.dirname(path) == path
stack.push path
path = File.dirname(path)
end
@@ -412,7 +415,7 @@ module FileUtils
#
# With no keyword arguments, removes the directory at each +path+ in +list+,
# by calling: <tt>Dir.rmdir(path)</tt>;
- # see {Dir.rmdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-rmdir]:
+ # see {Dir.rmdir}[rdoc-ref:Dir.rmdir]:
#
# FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]
# FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"]
@@ -435,6 +438,8 @@ module FileUtils
# Raises an exception if a directory does not exist
# or if for any reason a directory cannot be removed.
#
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
+ #
def rmdir(list, parents: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
@@ -507,7 +512,7 @@ module FileUtils
# Raises an exception if +dest+ is the path to an existing file
# and keyword argument +force+ is not +true+.
#
- # FileUtils#link is an alias for FileUtils#ln.
+ # Related: FileUtils.link_entry (has different options).
#
def ln(src, dest, force: nil, noop: nil, verbose: nil)
fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -531,52 +536,71 @@ module FileUtils
# If +src+ is the path to a directory and +dest+ does not exist,
# creates links +dest+ and descendents pointing to +src+ and its descendents:
#
- # Dir.glob('**/*.txt')
- # # => ["tmp0/tmp2/t0.txt",
- # "tmp0/tmp2/t1.txt",
- # "tmp0/tmp3/t2.txt",
- # "tmp0/tmp3/t3.txt"]
- # FileUtils.cp_lr('tmp0', 'tmp1')
- # Dir.glob('**/*.txt')
- # # => ["tmp0/tmp2/t0.txt",
- # "tmp0/tmp2/t1.txt",
- # "tmp0/tmp3/t2.txt",
- # "tmp0/tmp3/t3.txt",
- # "tmp1/tmp2/t0.txt",
- # "tmp1/tmp2/t1.txt",
- # "tmp1/tmp3/t2.txt",
- # "tmp1/tmp3/t3.txt"]
+ # tree('src0')
+ # # => src0
+ # # |-- sub0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- sub1
+ # # |-- src2.txt
+ # # `-- src3.txt
+ # File.exist?('dest0') # => false
+ # FileUtils.cp_lr('src0', 'dest0')
+ # tree('dest0')
+ # # => dest0
+ # # |-- sub0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- sub1
+ # # |-- src2.txt
+ # # `-- src3.txt
#
# If +src+ and +dest+ are both paths to directories,
# creates links <tt>dest/src</tt> and descendents
# pointing to +src+ and its descendents:
#
- # FileUtils.rm_r('tmp1')
- # Dir.mkdir('tmp1')
- # FileUtils.cp_lr('tmp0', 'tmp1')
- # # => ["tmp0/tmp2/t0.txt",
- # "tmp0/tmp2/t1.txt",
- # "tmp0/tmp3/t2.txt",
- # "tmp0/tmp3/t3.txt",
- # "tmp1/tmp0/tmp2/t0.txt",
- # "tmp1/tmp0/tmp2/t1.txt",
- # "tmp1/tmp0/tmp3/t2.txt",
- # "tmp1/tmp0/tmp3/t3.txt"]
- #
- # If +src+ is an array of paths to files and +dest+ is the path to a directory,
+ # tree('src1')
+ # # => src1
+ # # |-- sub0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- sub1
+ # # |-- src2.txt
+ # # `-- src3.txt
+ # FileUtils.mkdir('dest1')
+ # FileUtils.cp_lr('src1', 'dest1')
+ # tree('dest1')
+ # # => dest1
+ # # `-- src1
+ # # |-- sub0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- sub1
+ # # |-- src2.txt
+ # # `-- src3.txt
+ #
+ # If +src+ is an array of paths to entries and +dest+ is the path to a directory,
# for each path +filepath+ in +src+, creates a link at <tt>dest/filepath</tt>
# pointing to that path:
#
- # FileUtils.rm_r('tmp1')
- # Dir.mkdir('tmp1')
- # FileUtils.cp_lr(['tmp0/tmp3/t2.txt', 'tmp0/tmp3/t3.txt'], 'tmp1')
- # Dir.glob('**/*.txt')
- # # => ["tmp0/tmp2/t0.txt",
- # "tmp0/tmp2/t1.txt",
- # "tmp0/tmp3/t2.txt",
- # "tmp0/tmp3/t3.txt",
- # "tmp1/t2.txt",
- # "tmp1/t3.txt"]
+ # tree('src2')
+ # # => src2
+ # # |-- sub0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- sub1
+ # # |-- src2.txt
+ # # `-- src3.txt
+ # FileUtils.mkdir('dest2')
+ # FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2')
+ # tree('dest2')
+ # # => dest2
+ # # |-- sub0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- sub1
+ # # |-- src2.txt
+ # # `-- src3.txt
#
# Keyword arguments:
#
@@ -586,17 +610,21 @@ module FileUtils
# - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
- # FileUtils.cp_lr('tmp0', 'tmp1', verbose: true, noop: true)
- # FileUtils.cp_lr(['tmp0/tmp3/t2.txt', 'tmp0/tmp3/t3.txt'], 'tmp1', verbose: true, noop: true)
+ # FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true)
+ # FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true)
+ # FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true)
#
# Output:
#
- # cp -lr tmp0 tmp1
- # cp -lr tmp0/tmp3/t2.txt tmp0/tmp3/t3.txt tmp1
+ # cp -lr src0 dest0
+ # cp -lr src1 dest1
+ # cp -lr src2/sub0 src2/sub1 dest2
#
# Raises an exception if +dest+ is the path to an existing file or directory
# and keyword argument <tt>remove_destination: true</tt> is not given.
#
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
+ #
def cp_lr(src, dest, noop: nil, verbose: nil,
dereference_root: true, remove_destination: false)
fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -658,6 +686,7 @@ module FileUtils
# Keyword arguments:
#
# - <tt>force: true</tt> - overwrites +dest+ if it exists.
+ # - <tt>relative: false</tt> - create links relative to +dest+.
# - <tt>noop: true</tt> - does not create links.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
@@ -673,12 +702,16 @@ module FileUtils
# ln -sf src2.txt dest2.txt
# ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3
#
- # FileUtils.symlink is an alias for FileUtils.ln_s.
+ # Related: FileUtils.ln_sf.
#
- def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
- fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil)
+ if relative
+ return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose)
+ end
+ fu_output_message "ln -s#{force ? 'f' : ''}#{
+ target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
return if noop
- fu_each_src_dest0(src, dest) do |s,d|
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
remove_file d, true if force
File.symlink s, d
end
@@ -695,6 +728,43 @@ module FileUtils
end
module_function :ln_sf
+ # Like FileUtils.ln_s, but create links relative to +dest+.
+ #
+ def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
+ cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
+ if target_directory
+ parent = File.dirname(d)
+ destdirs = fu_split_path(parent)
+ real_ddirs = fu_split_path(File.realpath(parent))
+ else
+ destdirs ||= fu_split_path(dest)
+ real_ddirs ||= fu_split_path(File.realdirpath(dest))
+ end
+ srcdirs = fu_split_path(s)
+ i = fu_common_components(srcdirs, destdirs)
+ n = destdirs.size - i
+ n -= 1 unless target_directory
+ link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1])
+ begin
+ real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil
+ rescue
+ else
+ i = fu_common_components(real_sdirs, real_ddirs)
+ n = real_ddirs.size - i
+ n -= 1 unless target_directory
+ link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1])
+ link1 = link2 if link1.size > link2.size
+ end
+ s = File.join(link1)
+ fu_output_message [cmd, s, d].flatten.join(' ') if verbose
+ next if noop
+ remove_file d, true if force
+ File.symlink s, d
+ end
+ end
+ module_function :ln_sr
+
# Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+.
#
# Arguments +src+ and +dest+
@@ -704,9 +774,9 @@ module FileUtils
# creates a hard link at +dest+ pointing to +src+:
#
# FileUtils.touch('src0.txt')
- # File.exist?('dest0.txt') # => false
+ # File.exist?('dest0.txt') # => false
# FileUtils.link_entry('src0.txt', 'dest0.txt')
- # File.exist?('dest0.txt') # => true
+ # File.file?('dest0.txt') # => true
#
# If +src+ is the path to a directory and +dest+ does not exist,
# recursively creates hard links at +dest+ pointing to paths in +src+:
@@ -719,20 +789,22 @@ module FileUtils
# 'src1/dir1/t3.txt',
# ]
# FileUtils.touch(src_file_paths)
- # File.exist?('dest1') # => true
+ # File.directory?('dest1') # => true
# FileUtils.link_entry('src1', 'dest1')
- # File.exist?('dest1/dir0/t0.txt') # => true
- # File.exist?('dest1/dir0/t1.txt') # => true
- # File.exist?('dest1/dir1/t2.txt') # => true
- # File.exist?('dest1/dir1/t3.txt') # => true
+ # File.file?('dest1/dir0/t0.txt') # => true
+ # File.file?('dest1/dir0/t1.txt') # => true
+ # File.file?('dest1/dir1/t2.txt') # => true
+ # File.file?('dest1/dir1/t3.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link.
- # - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
+ # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before creating links (+false+ by default).
#
# Raises an exception if +dest+ is the path to an existing file or directory
- # and keyword argument <tt>remove_destination: true</tt> is not given.
+ # and optional argument +remove_destination+ is not given.
+ #
+ # Related: FileUtils.ln (has different options).
#
def link_entry(src, dest, dereference_root = false, remove_destination = false)
Entry_.new(src, nil, dereference_root).traverse do |ent|
@@ -755,7 +827,7 @@ module FileUtils
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.cp('src0.txt', 'dest0.txt')
- # File.exist?('dest0.txt') # => true
+ # File.file?('dest0.txt') # => true
#
# If +src+ is the path to a file and +dest+ is the path to a directory,
# copies +src+ to <tt>dest/src</tt>:
@@ -763,7 +835,7 @@ module FileUtils
# FileUtils.touch('src1.txt')
# FileUtils.mkdir('dest1')
# FileUtils.cp('src1.txt', 'dest1')
- # File.exist?('dest1/src1.txt') # => true
+ # File.file?('dest1/src1.txt') # => true
#
# If +src+ is an array of paths to files and +dest+ is the path to a directory,
# copies from each +src+ to +dest+:
@@ -772,8 +844,8 @@ module FileUtils
# FileUtils.touch(src_file_paths)
# FileUtils.mkdir('dest2')
# FileUtils.cp(src_file_paths, 'dest2')
- # File.exist?('dest2/src2.txt') # => true
- # File.exist?('dest2/src2.dat') # => true
+ # File.file?('dest2/src2.txt') # => true
+ # File.file?('dest2/src2.dat') # => true
#
# Keyword arguments:
#
@@ -793,9 +865,7 @@ module FileUtils
#
# Raises an exception if +src+ is a directory.
#
- # Related: FileUtils.cp_r (recursive).
- #
- # FileUtils.copy is an alias for FileUtils.cp.
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -815,13 +885,16 @@ module FileUtils
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
+ # The mode, owner, and group are retained in the copy;
+ # to change those, use FileUtils.install instead.
+ #
# If +src+ is the path to a file and +dest+ is not the path to a directory,
# copies +src+ to +dest+:
#
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.cp_r('src0.txt', 'dest0.txt')
- # File.exist?('dest0.txt') # => true
+ # File.file?('dest0.txt') # => true
#
# If +src+ is the path to a file and +dest+ is the path to a directory,
# copies +src+ to <tt>dest/src</tt>:
@@ -829,54 +902,52 @@ module FileUtils
# FileUtils.touch('src1.txt')
# FileUtils.mkdir('dest1')
# FileUtils.cp_r('src1.txt', 'dest1')
- # File.exist?('dest1/src1.txt') # => true
+ # File.file?('dest1/src1.txt') # => true
#
# If +src+ is the path to a directory and +dest+ does not exist,
# recursively copies +src+ to +dest+:
#
# tree('src2')
- # src2
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => src2
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
# FileUtils.exist?('dest2') # => false
- #
# FileUtils.cp_r('src2', 'dest2')
# tree('dest2')
- # dest2
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => dest2
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
#
# If +src+ and +dest+ are paths to directories,
# recursively copies +src+ to <tt>dest/src</tt>:
#
# tree('src3')
- # src3
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => src3
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
# FileUtils.mkdir('dest3')
- #
# FileUtils.cp_r('src3', 'dest3')
# tree('dest3')
- # dest3
- # `-- src3
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => dest3
+ # # `-- src3
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
#
# If +src+ is an array of paths and +dest+ is a directory,
# recursively copies from each path in +src+ to +dest+;
@@ -906,7 +977,7 @@ module FileUtils
# Raises an exception of +src+ is the path to a directory
# and +dest+ is the path to a file.
#
- # Related: FileUtils.cp (not recursive).
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
dereference_root: true, remove_destination: nil)
@@ -933,33 +1004,35 @@ module FileUtils
# If +src+ is a directory, recursively copies +src+ to +dest+:
#
# tree('src1')
- # src1
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => src1
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
# FileUtils.copy_entry('src1', 'dest1')
# tree('dest1')
- # dest1
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => dest1
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
#
# The recursive copying preserves file types for regular files,
# directories, and symbolic links;
# other file types (FIFO streams, device files, etc.) are not supported.
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link,
- # follows the link.
- # - <tt>preserve: true</tt> - preserves file times.
- # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
+ # - +dereference_root+ - if +src+ is a symbolic link,
+ # follows the link (+false+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
+ #
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
if dereference_root
@@ -988,12 +1061,14 @@ module FileUtils
# FileUtils.copy_file('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference: false</tt> - if +src+ is a symbolic link,
- # does not follow the link.
- # - <tt>preserve: true</tt> - preserves file times.
- # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
+ # - +dereference+ - if +src+ is a symbolic link,
+ # follows the link (+true+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
+ #
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def copy_file(src, dest, preserve = false, dereference = true)
ent = Entry_.new(src, nil, dereference)
@@ -1003,7 +1078,9 @@ module FileUtils
module_function :copy_file
# Copies \IO stream +src+ to \IO stream +dest+ via
- # {IO.copy_stream}[https://docs.ruby-lang.org/en/master/IO.html#method-c-copy_stream].
+ # {IO.copy_stream}[rdoc-ref:IO.copy_stream].
+ #
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def copy_stream(src, dest)
IO.copy_stream(src, dest)
@@ -1027,16 +1104,16 @@ module FileUtils
# moves +src+ to +dest+:
#
# tree('src0')
- # src0
- # |-- src0.txt
- # `-- src1.txt
+ # # => src0
+ # # |-- src0.txt
+ # # `-- src1.txt
# File.exist?('dest0') # => false
# FileUtils.mv('src0', 'dest0')
# File.exist?('src0') # => false
# tree('dest0')
- # dest0
- # |-- src0.txt
- # `-- src1.txt
+ # # => dest0
+ # # |-- src0.txt
+ # # `-- src1.txt
#
# If +src+ is an array of paths to files and directories
# and +dest+ is the path to a directory,
@@ -1044,17 +1121,17 @@ module FileUtils
#
# File.file?('src1.txt') # => true
# tree('src1')
- # src1
- # |-- src.dat
- # `-- src.txt
- # Dir.empty?('dest1') # => true
+ # # => src1
+ # # |-- src.dat
+ # # `-- src.txt
+ # Dir.empty?('dest1') # => true
# FileUtils.mv(['src1.txt', 'src1'], 'dest1')
# tree('dest1')
- # dest1
- # |-- src1
- # | |-- src.dat
- # | `-- src.txt
- # `-- src1.txt
+ # # => dest1
+ # # |-- src1
+ # # | |-- src.dat
+ # # | `-- src.txt
+ # # `-- src1.txt
#
# Keyword arguments:
#
@@ -1074,8 +1151,6 @@ module FileUtils
# mv src0 dest0
# mv src1.txt src1 dest1
#
- # FileUtils.move is an alias for FileUtils.mv.
- #
def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
@@ -1133,7 +1208,7 @@ module FileUtils
#
# rm src0.dat src0.txt
#
- # FileUtils.remove is an alias for FileUtils.rm.
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm(list, force: nil, noop: nil, verbose: nil)
list = fu_list(list)
@@ -1158,7 +1233,7 @@ module FileUtils
#
# See FileUtils.rm for keyword arguments.
#
- # FileUtils.safe_unlink is an alias for FileUtils.rm_f.
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm_f(list, noop: nil, verbose: nil)
rm list, force: true, noop: noop, verbose: verbose
@@ -1189,13 +1264,13 @@ module FileUtils
# For each directory path, recursively removes files and directories:
#
# tree('src1')
- # src1
- # |-- dir0
- # | |-- src0.txt
- # | `-- src1.txt
- # `-- dir1
- # |-- src2.txt
- # `-- src3.txt
+ # # => src1
+ # # |-- dir0
+ # # | |-- src0.txt
+ # # | `-- src1.txt
+ # # `-- dir1
+ # # |-- src2.txt
+ # # `-- src3.txt
# FileUtils.rm_r('src1')
# File.exist?('src1') # => false
#
@@ -1216,6 +1291,8 @@ module FileUtils
# rm -r src0.dat src0.txt
# rm -r src1
#
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
+ #
def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
list = fu_list(list)
fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
@@ -1243,7 +1320,7 @@ module FileUtils
#
# See FileUtils.rm_r for keyword arguments.
#
- # FileUtils.rmtree is an alias for FileUtils.rm_rf.
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm_rf(list, noop: nil, verbose: nil, secure: nil)
rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
@@ -1266,6 +1343,8 @@ module FileUtils
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
+ #
def remove_entry_secure(path, force = false)
unless fu_have_symlink?
remove_entry path, force
@@ -1386,6 +1465,8 @@ module FileUtils
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
+ #
def remove_file(path, force = false)
Entry_.new(path).remove_file
rescue
@@ -1403,8 +1484,11 @@ module FileUtils
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
+ #
def remove_dir(path, force = false)
- remove_entry path, force # FIXME?? check if it is a directory
+ raise Errno::ENOTDIR, path unless force or File.directory?(path)
+ remove_entry path, force
end
module_function :remove_dir
@@ -1416,6 +1500,8 @@ module FileUtils
#
# FileUtils.identical? and FileUtils.cmp are aliases for FileUtils.compare_file.
#
+ # Related: FileUtils.compare_stream.
+ #
def compare_file(a, b)
return false unless File.size(a) == File.size(b)
File.open(a, 'rb') {|fa|
@@ -1437,6 +1523,8 @@ module FileUtils
# Arguments +a+ and +b+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
+ # Related: FileUtils.compare_file.
+ #
def compare_stream(a, b)
bsize = fu_stream_blksize(a, b)
@@ -1494,14 +1582,14 @@ module FileUtils
# Keyword arguments:
#
# - <tt>group: <i>group</i></tt> - changes the group if not +nil+,
- # using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
+ # using {File.chown}[rdoc-ref:File.chown].
# - <tt>mode: <i>permissions</i></tt> - changes the permissions.
- # using {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
- # - <tt>noop: true</tt> - does not remove entries; returns +nil+.
+ # using {File.chmod}[rdoc-ref:File.chmod].
+ # - <tt>noop: true</tt> - does not copy entries; returns +nil+.
# - <tt>owner: <i>owner</i></tt> - changes the owner if not +nil+,
- # using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
+ # using {File.chown}[rdoc-ref:File.chown].
# - <tt>preserve: true</tt> - preserve timestamps
- # using {File.utime}[https://docs.ruby-lang.org/en/master/File.html#method-c-utime].
+ # using {File.utime}[rdoc-ref:File.utime].
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true)
@@ -1514,6 +1602,8 @@ module FileUtils
# install -c src1.txt dest1.txt
# install -c src2.txt dest2
#
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
+ #
def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
noop: nil, verbose: nil)
if verbose
@@ -1532,7 +1622,13 @@ module FileUtils
st = File.stat(s)
unless File.exist?(d) and compare_file(s, d)
remove_file d, true
- copy_file s, d
+ if d.end_with?('/')
+ mkdir_p d
+ copy_file s, d + File.basename(s)
+ else
+ mkdir_p File.expand_path('..', d)
+ copy_file s, d
+ end
File.utime st.atime, st.mtime, d if preserve
File.chmod fu_mode(mode, st), d if mode
File.chown uid, gid, d if uid or gid
@@ -1553,7 +1649,7 @@ module FileUtils
when "a"
mask | 07777
else
- raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
+ raise ArgumentError, "invalid 'who' symbol in file mode: #{chr}"
end
end
end
@@ -1607,7 +1703,7 @@ module FileUtils
copy_mask = user_mask(chr)
(current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
else
- raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
+ raise ArgumentError, "invalid 'perm' symbol in file mode: #{chr}"
end
end
@@ -1636,9 +1732,9 @@ module FileUtils
# returns +list+ if it is an array, <tt>[list]</tt> otherwise:
#
# - Modifies each entry that is a regular file using
- # {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
+ # {File.chmod}[rdoc-ref:File.chmod].
# - Modifies each entry that is a symbolic link using
- # {File.lchmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchmod].
+ # {File.lchmod}[rdoc-ref:File.lchmod].
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
@@ -1700,6 +1796,8 @@ module FileUtils
# chmod u=wrx,go=rx src1.txt
# chmod u=wrx,go=rx /usr/bin/ruby
#
+ # Related: FileUtils.chmod_R.
+ #
def chmod(mode, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
@@ -1736,9 +1834,9 @@ module FileUtils
# returns +list+ if it is an array, <tt>[list]</tt> otherwise:
#
# - Modifies each entry that is a regular file using
- # {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
+ # {File.chown}[rdoc-ref:File.chown].
# - Modifies each entry that is a symbolic link using
- # {File.lchown}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchown].
+ # {File.lchown}[rdoc-ref:File.lchown].
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
@@ -1791,6 +1889,8 @@ module FileUtils
# chown user2:group1 src0.txt
# chown user2:group1 .
#
+ # Related: FileUtils.chown_R.
+ #
def chown(user, group, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chown %s %s',
@@ -1899,6 +1999,8 @@ module FileUtils
# touch src0.txt src0.dat
# touch src0.txt
#
+ # Related: FileUtils.uptodate?.
+ #
def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
list = fu_list(list)
t = mtime
@@ -1924,21 +2026,22 @@ module FileUtils
private
- module StreamUtils_
+ module StreamUtils_ # :nodoc:
+
private
case (defined?(::RbConfig) ? ::RbConfig::CONFIG['host_os'] : ::RUBY_PLATFORM)
when /mswin|mingw/
- def fu_windows?; true end
+ def fu_windows?; true end #:nodoc:
else
- def fu_windows?; false end
+ def fu_windows?; false end #:nodoc:
end
def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
IO.copy_stream(src, dest)
end
- def fu_stream_blksize(*streams)
+ def fu_stream_blksize(*streams) #:nodoc:
streams.each do |s|
next unless s.respond_to?(:stat)
size = fu_blksize(s.stat)
@@ -1947,14 +2050,14 @@ module FileUtils
fu_default_blksize()
end
- def fu_blksize(st)
+ def fu_blksize(st) #:nodoc:
s = st.blksize
return nil unless s
return nil if s == 0
s
end
- def fu_default_blksize
+ def fu_default_blksize #:nodoc:
1024
end
end
@@ -2259,13 +2362,21 @@ module FileUtils
def postorder_traverse
if directory?
- entries().each do |ent|
+ begin
+ children = entries()
+ rescue Errno::EACCES
+ # Failed to get the list of children.
+ # Assuming there is no children, try to process the parent directory.
+ yield self
+ return
+ end
+
+ children.each do |ent|
ent.postorder_traverse do |e|
yield e
end
end
end
- ensure
yield self
end
@@ -2359,15 +2470,19 @@ module FileUtils
end
private_module_function :fu_each_src_dest
- def fu_each_src_dest0(src, dest) #:nodoc:
+ def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
if tmp = Array.try_convert(src)
+ unless target_directory or tmp.size <= 1
+ tmp = tmp.map {|f| File.path(f)} # A workaround for RBS
+ raise ArgumentError, "extra target #{tmp}"
+ end
tmp.each do |s|
s = File.path(s)
- yield s, File.join(dest, File.basename(s))
+ yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
end
else
src = File.path(src)
- if File.directory?(dest)
+ if target_directory and File.directory?(dest)
yield src, File.join(dest, File.basename(src))
else
yield src, File.path(dest)
@@ -2391,6 +2506,60 @@ module FileUtils
end
private_module_function :fu_output_message
+ def fu_split_path(path) #:nodoc:
+ path = File.path(path)
+ list = []
+ until (parent, base = File.split(path); parent == path or parent == ".")
+ if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path))
+ list.pop
+ else
+ list << base
+ end
+ path = parent
+ end
+ list << path
+ list.reverse!
+ end
+ private_module_function :fu_split_path
+
+ def fu_common_components(target, base) #:nodoc:
+ i = 0
+ while target[i]&.== base[i]
+ i += 1
+ end
+ i
+ end
+ private_module_function :fu_common_components
+
+ def fu_clean_components(*comp) #:nodoc:
+ comp.shift while comp.first == "."
+ return comp if comp.empty?
+ clean = [comp.shift]
+ path = File.join(*clean, "") # ending with File::SEPARATOR
+ while c = comp.shift
+ if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path))
+ clean.pop
+ path.sub!(%r((?<=\A|/)[^/]+/\z), "")
+ else
+ clean << c
+ path << c << "/"
+ end
+ end
+ clean
+ end
+ private_module_function :fu_clean_components
+
+ if fu_windows?
+ def fu_starting_path?(path) #:nodoc:
+ path&.start_with?(%r(\w:|/))
+ end
+ else
+ def fu_starting_path?(path) #:nodoc:
+ path&.start_with?("/")
+ end
+ end
+ private_module_function :fu_starting_path?
+
# This hash table holds command options.
OPT_TABLE = {} #:nodoc: internal use only
(private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|