diff options
Diffstat (limited to 'lib/fileutils.rb')
| -rw-r--r-- | lib/fileutils.rb | 587 |
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| |
