diff options
Diffstat (limited to 'lib/bundler/vendor/fileutils')
-rw-r--r-- | lib/bundler/vendor/fileutils/.document | 1 | ||||
-rw-r--r-- | lib/bundler/vendor/fileutils/lib/fileutils.rb | 1750 |
2 files changed, 1341 insertions, 410 deletions
diff --git a/lib/bundler/vendor/fileutils/.document b/lib/bundler/vendor/fileutils/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/fileutils/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb index 8f8faf30c8..6db19caf6f 100644 --- a/lib/bundler/vendor/fileutils/lib/fileutils.rb +++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb @@ -3,106 +3,184 @@ begin require 'rbconfig' rescue LoadError - # for make mjit-headers + # for make rjit-headers end +# Namespace for file utility methods for copying, moving, removing, etc. # -# = fileutils.rb +# == What's Here # -# Copyright (c) 2000-2007 Minero Aoki +# First, what’s elsewhere. \Module \Bundler::FileUtils: # -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. +# - Inherits from {class Object}[rdoc-ref:Object]. +# - Supplements {class File}[rdoc-ref:File] +# (but is not included or extended there). # -# == module Bundler::FileUtils +# Here, module \Bundler::FileUtils provides methods that are useful for: # -# Namespace for several file utility methods for copying, moving, removing, etc. +# - {Creating}[rdoc-ref:FileUtils@Creating]. +# - {Deleting}[rdoc-ref:FileUtils@Deleting]. +# - {Querying}[rdoc-ref:FileUtils@Querying]. +# - {Setting}[rdoc-ref:FileUtils@Setting]. +# - {Comparing}[rdoc-ref:FileUtils@Comparing]. +# - {Copying}[rdoc-ref:FileUtils@Copying]. +# - {Moving}[rdoc-ref:FileUtils@Moving]. +# - {Options}[rdoc-ref:FileUtils@Options]. # -# === Module Functions +# === Creating # -# require 'bundler/vendor/fileutils/lib/fileutils' +# - ::mkdir: Creates directories. +# - ::mkdir_p, ::makedirs, ::mkpath: Creates directories, +# also creating ancestor directories as needed. +# - ::link_entry: Creates a hard link. +# - ::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 # -# Bundler::FileUtils.cd(dir, **options) -# Bundler::FileUtils.cd(dir, **options) {|dir| block } -# Bundler::FileUtils.pwd() -# Bundler::FileUtils.mkdir(dir, **options) -# Bundler::FileUtils.mkdir(list, **options) -# Bundler::FileUtils.mkdir_p(dir, **options) -# Bundler::FileUtils.mkdir_p(list, **options) -# Bundler::FileUtils.rmdir(dir, **options) -# Bundler::FileUtils.rmdir(list, **options) -# Bundler::FileUtils.ln(target, link, **options) -# Bundler::FileUtils.ln(targets, dir, **options) -# Bundler::FileUtils.ln_s(target, link, **options) -# Bundler::FileUtils.ln_s(targets, dir, **options) -# Bundler::FileUtils.ln_sf(target, link, **options) -# Bundler::FileUtils.cp(src, dest, **options) -# Bundler::FileUtils.cp(list, dir, **options) -# Bundler::FileUtils.cp_r(src, dest, **options) -# Bundler::FileUtils.cp_r(list, dir, **options) -# Bundler::FileUtils.mv(src, dest, **options) -# Bundler::FileUtils.mv(list, dir, **options) -# Bundler::FileUtils.rm(list, **options) -# Bundler::FileUtils.rm_r(list, **options) -# Bundler::FileUtils.rm_rf(list, **options) -# Bundler::FileUtils.install(src, dest, **options) -# Bundler::FileUtils.chmod(mode, list, **options) -# Bundler::FileUtils.chmod_R(mode, list, **options) -# Bundler::FileUtils.chown(user, group, list, **options) -# Bundler::FileUtils.chown_R(user, group, list, **options) -# Bundler::FileUtils.touch(list, **options) +# === Deleting # -# Possible <tt>options</tt> are: +# - ::remove_dir: Removes a directory and its descendants. +# - ::remove_entry: Removes an entry, including its descendants if it is a directory. +# - ::remove_entry_secure: Like ::remove_entry, but removes securely. +# - ::remove_file: Removes a file entry. +# - ::rm, ::remove: Removes entries. +# - ::rm_f, ::safe_unlink: Like ::rm, but removes forcibly. +# - ::rm_r: Removes entries and their descendants. +# - ::rm_rf, ::rmtree: Like ::rm_r, but removes forcibly. +# - ::rmdir: Removes directories. # -# <tt>:force</tt> :: forced operation (rewrite files if exist, remove -# directories if not empty, etc.); -# <tt>:verbose</tt> :: print command to be run, in bash syntax, before -# performing it; -# <tt>:preserve</tt> :: preserve object's group, user and modification -# time on copying; -# <tt>:noop</tt> :: no changes are made (usable in combination with -# <tt>:verbose</tt> which will print the command to run) +# === Querying # -# Each method documents the options that it honours. See also ::commands, -# ::options and ::options_of methods to introspect which command have which -# options. +# - ::pwd, ::getwd: Returns the path to the working directory. +# - ::uptodate?: Returns whether a given entry is newer than given other entries. # -# All methods that have the concept of a "source" file or directory can take -# either one file or a list of files in that argument. See the method -# documentation for examples. +# === Setting # -# There are some `low level' methods, which do not accept keyword arguments: +# - ::cd, ::chdir: Sets the working directory. +# - ::chmod: Sets permissions for an entry. +# - ::chmod_R: Sets permissions for an entry and its descendants. +# - ::chown: Sets the owner and group for entries. +# - ::chown_R: Sets the owner and group for entries and their descendants. +# - ::touch: Sets modification and access times for entries, +# creating if necessary. # -# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) -# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true) -# Bundler::FileUtils.copy_stream(srcstream, deststream) -# Bundler::FileUtils.remove_entry(path, force = false) -# Bundler::FileUtils.remove_entry_secure(path, force = false) -# Bundler::FileUtils.remove_file(path, force = false) -# Bundler::FileUtils.compare_file(path_a, path_b) -# Bundler::FileUtils.compare_stream(stream_a, stream_b) -# Bundler::FileUtils.uptodate?(file, cmp_list) +# === Comparing # -# == module Bundler::FileUtils::Verbose +# - ::compare_file, ::cmp, ::identical?: Returns whether two entries are identical. +# - ::compare_stream: Returns whether two streams are identical. # -# This module has all methods of Bundler::FileUtils module, but it outputs messages -# before acting. This equates to passing the <tt>:verbose</tt> flag to methods -# in Bundler::FileUtils. +# === Copying # -# == module Bundler::FileUtils::NoWrite +# - ::copy_entry: Recursively copies an entry. +# - ::copy_file: Copies an entry. +# - ::copy_stream: Copies a stream. +# - ::cp, ::copy: Copies files. +# - ::cp_lr: Recursively creates hard links. +# - ::cp_r: Recursively copies files, retaining mode, owner, and group. +# - ::install: Recursively copies files, optionally setting mode, +# owner, and group. # -# This module has all methods of Bundler::FileUtils module, but never changes -# files/directories. This equates to passing the <tt>:noop</tt> flag to methods -# in Bundler::FileUtils. +# === Moving # -# == module Bundler::FileUtils::DryRun +# - ::mv, ::move: Moves entries. # -# This module has all methods of Bundler::FileUtils module, but never changes -# files/directories. This equates to passing the <tt>:noop</tt> and -# <tt>:verbose</tt> flags to methods in Bundler::FileUtils. +# === Options +# +# - ::collect_method: Returns the names of methods that accept a given option. +# - ::commands: Returns the names of methods that accept options. +# - ::have_option?: Returns whether a given method accepts a given option. +# - ::options: Returns all option names. +# - ::options_of: Returns the names of the options for a given method. +# +# == Path Arguments +# +# Some methods in \Bundler::FileUtils accept _path_ arguments, +# which are interpreted as paths to filesystem entries: +# +# - If the argument is a string, that value is the path. +# - If the argument has method +:to_path+, it is converted via that method. +# - If the argument has method +:to_str+, it is converted via that method. +# +# == About the Examples +# +# Some examples here involve trees of file entries. +# For these, we sometimes display trees using the +# {tree command-line utility}[https://en.wikipedia.org/wiki/Tree_(command)], +# which is a recursive directory-listing utility that produces +# a depth-indented listing of files and directories. +# +# We use a helper method to launch the command and control the format: +# +# def tree(dirpath = '.') +# command = "tree --noreport --charset=ascii #{dirpath}" +# system(command) +# end +# +# To illustrate: +# +# tree('src0') +# # => src0 +# # |-- sub0 +# # | |-- src0.txt +# # | `-- src1.txt +# # `-- sub1 +# # |-- src2.txt +# # `-- src3.txt +# +# == Avoiding the TOCTTOU Vulnerability +# +# For certain methods that recursively remove entries, +# there is a potential vulnerability called the +# {Time-of-check to time-of-use}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use], +# or TOCTTOU, vulnerability that can exist when: +# +# - An ancestor directory of the entry at the target path is world writable; +# such directories include <tt>/tmp</tt>. +# - The directory tree at the target path includes: +# +# - A world-writable descendant directory. +# - A symbolic link. +# +# To avoid that vulnerability, you can use this method to remove entries: +# +# - Bundler::FileUtils.remove_entry_secure: removes recursively +# if the target path points to a directory. +# +# Also available are these methods, +# each of which calls \Bundler::FileUtils.remove_entry_secure: +# +# - Bundler::FileUtils.rm_r with keyword argument <tt>secure: true</tt>. +# - Bundler::FileUtils.rm_rf with keyword argument <tt>secure: true</tt>. +# +# Finally, this method for moving entries calls \Bundler::FileUtils.remove_entry_secure +# if the source and destination are on different file systems +# (which means that the "move" is really a copy and remove): +# +# - Bundler::FileUtils.mv with keyword argument <tt>secure: true</tt>. +# +# \Method \Bundler::FileUtils.remove_entry_secure removes securely +# by applying a special pre-process: +# +# - 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). +# +# WARNING: You must ensure that *ALL* parent directories cannot be +# moved by other untrusted users. For example, parent directories +# should not be owned by untrusted users, and should not be world +# writable except when the sticky bit is set. +# +# For details of this security vulnerability, see Perl cases: +# +# - {CVE-2005-0448}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448]. +# - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452]. # module Bundler::FileUtils - VERSION = "1.4.1" + VERSION = "1.7.2" def self.private_module_function(name) #:nodoc: module_function name @@ -110,7 +188,11 @@ module Bundler::FileUtils end # - # Returns the name of the current directory. + # Returns a string containing the path to the current directory: + # + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # + # Related: Bundler::FileUtils.cd. # def pwd Dir.pwd @@ -120,19 +202,38 @@ module Bundler::FileUtils alias getwd pwd module_function :getwd + # Changes the working directory to the given +dir+, which + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]: + # + # With no block given, + # changes the current directory to the directory at +dir+; returns zero: + # + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # Bundler::FileUtils.cd('..') + # Bundler::FileUtils.pwd # => "/rdoc" + # Bundler::FileUtils.cd('fileutils') + # + # With a block given, changes the current directory to the directory + # at +dir+, calls the block with argument +dir+, + # and restores the original current directory; returns the block's value: # - # Changes the current directory to the directory +dir+. + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # Bundler::FileUtils.cd('..') { |arg| [arg, Bundler::FileUtils.pwd] } # => ["..", "/rdoc"] + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" # - # If this method is called with block, resumes to the previous - # working directory after the block execution has finished. + # Keyword arguments: # - # Bundler::FileUtils.cd('/') # change directory + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.cd('/', verbose: true) # change directory and report it + # Bundler::FileUtils.cd('..') + # Bundler::FileUtils.cd('fileutils') # - # Bundler::FileUtils.cd('/') do # change directory - # # ... # do something - # end # return to original directory + # Output: + # + # cd .. + # cd fileutils + # + # Related: Bundler::FileUtils.pwd. # def cd(dir, verbose: nil, &block) # :yield: dir fu_output_message "cd #{dir}" if verbose @@ -146,11 +247,19 @@ module Bundler::FileUtils module_function :chdir # - # Returns true if +new+ is newer than all +old_list+. - # Non-existent files are older than any file. + # Returns +true+ if the file at path +new+ + # is newer than all the files at paths in array +old_list+; + # +false+ otherwise. + # + # Argument +new+ and the elements of +old_list+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]: + # + # Bundler::FileUtils.uptodate?('Rakefile', ['Gemfile', 'README.md']) # => true + # Bundler::FileUtils.uptodate?('Gemfile', ['Rakefile', 'README.md']) # => false # - # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ - # system 'make hello.o' + # A non-existent file is considered to be infinitely old. + # + # Related: Bundler::FileUtils.touch. # def uptodate?(new, old_list) return false unless File.exist?(new) @@ -170,12 +279,39 @@ module Bundler::FileUtils private_module_function :remove_trailing_slash # - # Creates one or more directories. + # Creates directories at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, creates a directory at each +path+ in +list+ + # by calling: <tt>Dir.mkdir(path, mode)</tt>; + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: + # + # Bundler::FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"] + # Bundler::FileUtils.mkdir('tmp4') # => ["tmp4"] + # + # Keyword arguments: + # + # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; + # see {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not create directories. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mkdir(%w[tmp0 tmp1], verbose: true) + # Bundler::FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true) + # + # Output: + # + # mkdir tmp0 tmp1 + # mkdir -m 700 tmp2 tmp3 # - # Bundler::FileUtils.mkdir 'test' - # Bundler::FileUtils.mkdir %w(tmp data) - # Bundler::FileUtils.mkdir 'notexist', noop: true # Does not really create. - # Bundler::FileUtils.mkdir 'tmp', mode: 0700 + # Raises an exception if any path points to an existing + # file or directory, or if for any reason a directory cannot be created. + # + # Related: Bundler::FileUtils.mkdir_p. # def mkdir(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -189,40 +325,56 @@ module Bundler::FileUtils module_function :mkdir # - # Creates a directory and all its parent directories. - # For example, + # Creates directories at the paths in the given +list+ + # (a single path or an array of paths), + # also creating ancestor directories as needed; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # 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}[rdoc-ref:Dir.mkdir]: + # + # Bundler::FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] + # Bundler::FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"] + # + # Keyword arguments: + # + # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; + # see {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not create directories. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true) + # Bundler::FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true) + # + # Output: # - # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby' + # mkdir -p tmp0 tmp1 + # mkdir -p -m 700 tmp2 tmp3 # - # causes to make following directories, if they do not exist. + # Raises an exception if for any reason a directory cannot be created. # - # * /usr - # * /usr/local - # * /usr/local/lib - # * /usr/local/lib/ruby + # Bundler::FileUtils.mkpath and Bundler::FileUtils.makedirs are aliases for Bundler::FileUtils.mkdir_p. # - # You can pass several directories at a time in a list. + # Related: Bundler::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 return *list if noop - list.map {|path| remove_trailing_slash(path)}.each do |path| - # optimize for the most common case - begin - fu_mkdir path, mode - next - rescue SystemCallError - next if File.directory?(path) - end + list.each do |item| + path = remove_trailing_slash(item) stack = [] - until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" + until File.directory?(path) || File.dirname(path) == path stack.push path path = File.dirname(path) end - stack.pop # root directory should exist stack.reverse_each do |dir| begin fu_mkdir dir, mode @@ -253,12 +405,39 @@ module Bundler::FileUtils private_module_function :fu_mkdir # - # Removes one or more directories. + # Removes directories at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, removes the directory at each +path+ in +list+, + # by calling: <tt>Dir.rmdir(path)</tt>; + # see {Dir.rmdir}[rdoc-ref:Dir.rmdir]: + # + # Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] + # Bundler::FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"] + # + # Keyword arguments: + # + # - <tt>parents: true</tt> - removes successive ancestor directories + # if empty. + # - <tt>noop: true</tt> - does not remove directories. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.rmdir 'somedir' - # Bundler::FileUtils.rmdir %w(somedir anydir otherdir) - # # Does not really remove directory; outputs message. - # Bundler::FileUtils.rmdir 'somedir', verbose: true, noop: true + # Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3], parents: true, verbose: true) + # Bundler::FileUtils.rmdir('tmp4/tmp5', parents: true, verbose: true) + # + # Output: + # + # rmdir -p tmp0/tmp1 tmp2/tmp3 + # rmdir -p tmp4/tmp5 + # + # 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) @@ -279,26 +458,60 @@ module Bundler::FileUtils end module_function :rmdir + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # When +src+ is the path to an existing file + # and +dest+ is the path to a non-existent file, + # creates a hard link at +dest+ pointing to +src+; returns zero: + # + # Dir.children('tmp0/') # => ["t.txt"] + # Dir.children('tmp1/') # => [] + # Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk') # => 0 + # Dir.children('tmp1/') # => ["t.lnk"] + # + # When +src+ is the path to an existing file + # and +dest+ is the path to an existing directory, + # creates a hard link at <tt>dest/src</tt> pointing to +src+; returns zero: # - # :call-seq: - # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil) + # Dir.children('tmp2') # => ["t.dat"] + # Dir.children('tmp3') # => [] + # Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0 + # Dir.children('tmp3') # => ["t.dat"] # - # In the first form, creates a hard link +link+ which points to +target+. - # If +link+ already exists, raises Errno::EEXIST. - # But if the +force+ option is set, overwrites +link+. + # When +src+ is an array of paths to existing files + # and +dest+ is the path to an existing directory, + # then for each path +target+ in +src+, + # creates a hard link at <tt>dest/target</tt> pointing to +target+; + # returns +src+: # - # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true - # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' + # Dir.children('tmp4/') # => [] + # Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/') # => ["tmp0/t.txt", "tmp2/t.dat"] + # Dir.children('tmp4/') # => ["t.dat", "t.txt"] # - # In the second form, creates a link +dir/target+ pointing to +target+. - # In the third form, creates several hard links in the directory +dir+, - # pointing to each item in +targets+. - # If +dir+ is not a directory, raises Errno::ENOTDIR. + # Keyword arguments: # - # Bundler::FileUtils.cd '/sbin' - # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. + # - <tt>force: true</tt> - overwrites +dest+ if it exists. + # - <tt>noop: true</tt> - does not create links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk', verbose: true) + # Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3', verbose: true) + # Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/', verbose: true) + # + # Output: + # + # ln tmp0/t.txt tmp1/t.lnk + # ln tmp2/t.dat tmp3 + # ln tmp0/t.txt tmp2/t.dat tmp4/ + # + # Raises an exception if +dest+ is the path to an existing file + # and keyword argument +force+ is not +true+. + # + # Related: Bundler::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 @@ -313,28 +526,103 @@ module Bundler::FileUtils alias link ln module_function :link - # - # Hard link +src+ to +dest+. If +src+ is a directory, this method links - # all its contents recursively. If +dest+ is a directory, links - # +src+ to +dest/src+. - # - # +src+ can be a list of files. - # - # If +dereference_root+ is true, this method dereference tree root. - # - # If +remove_destination+ is true, this method removes each destination file before copy. - # - # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true - # Bundler::FileUtils.cp_lr 'lib/', site_ruby + '/mylib' - # - # # Examples of linking several files to target directory. - # Bundler::FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail' - # Bundler::FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true - # - # # If you want to link all contents of a directory instead of the - # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, - # # use the following code. - # Bundler::FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't. + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a directory and +dest+ does not exist, + # creates links +dest+ and descendents pointing to +src+ and its descendents: + # + # tree('src0') + # # => src0 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # File.exist?('dest0') # => false + # Bundler::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: + # + # tree('src1') + # # => src1 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest1') + # Bundler::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: + # + # tree('src2') + # # => src2 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest2') + # Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2') + # tree('dest2') + # # => dest2 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # Keyword arguments: + # + # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link, + # does not dereference it. + # - <tt>noop: true</tt> - does not create links. + # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true) + # Bundler::FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true) + # + # Output: + # + # 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) @@ -346,27 +634,79 @@ module Bundler::FileUtils end module_function :cp_lr + # Creates {symbolic links}[https://en.wikipedia.org/wiki/Symbolic_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to an existing file: + # + # - When +dest+ is the path to a non-existent file, + # creates a symbolic link at +dest+ pointing to +src+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt') + # File.symlink?('dest0.txt') # => true + # + # - When +dest+ is the path to an existing file, + # creates a symbolic link at +dest+ pointing to +src+ + # if and only if keyword argument <tt>force: true</tt> is given + # (raises an exception otherwise): # - # :call-seq: - # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.touch('dest1.txt') + # Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt', force: true) + # FileTest.symlink?('dest1.txt') # => true # - # In the first form, creates a symbolic link +link+ which points to +target+. - # If +link+ already exists, raises Errno::EEXIST. - # But if the <tt>force</tt> option is set, overwrites +link+. + # Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST. # - # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' - # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true + # If +dest+ is the path to a directory, + # creates a symbolic link at <tt>dest/src</tt> pointing to +src+: # - # In the second form, creates a link +dir/target+ pointing to +target+. - # In the third form, creates several symbolic links in the directory +dir+, - # pointing to each item in +targets+. - # If +dir+ is not a directory, raises Errno::ENOTDIR. + # Bundler::FileUtils.touch('src2.txt') + # Bundler::FileUtils.mkdir('destdir2') + # Bundler::FileUtils.ln_s('src2.txt', 'destdir2') + # File.symlink?('destdir2/src2.txt') # => true # - # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin' + # If +src+ is an array of paths to existing files and +dest+ is a directory, + # for each child +child+ in +src+ creates a symbolic link <tt>dest/child</tt> + # pointing to +child+: # - def ln_s(src, dest, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.mkdir('srcdir3') + # Bundler::FileUtils.touch('srcdir3/src0.txt') + # Bundler::FileUtils.touch('srcdir3/src1.txt') + # Bundler::FileUtils.mkdir('destdir3') + # Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3') + # File.symlink?('destdir3/src0.txt') # => true + # File.symlink?('destdir3/src1.txt') # => true + # + # 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: + # + # Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.ln_s('src1.txt', 'destdir1', noop: true, verbose: true) + # Bundler::FileUtils.ln_s('src2.txt', 'dest2.txt', force: true, noop: true, verbose: true) + # Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3', noop: true, verbose: true) + # + # Output: + # + # ln -s src0.txt dest0.txt + # ln -s src1.txt destdir1 + # ln -sf src2.txt dest2.txt + # ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3 + # + # Related: Bundler::FileUtils.ln_sf. + # + 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, noop: noop, verbose: verbose) + end fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose return if noop fu_each_src_dest0(src, dest) do |s,d| @@ -379,29 +719,95 @@ module Bundler::FileUtils alias symlink ln_s module_function :symlink - # - # :call-seq: - # Bundler::FileUtils.ln_sf(*args) - # - # Same as - # - # Bundler::FileUtils.ln_s(*args, force: true) + # Like Bundler::FileUtils.ln_s, but always with keyword argument <tt>force: true</tt> given. # def ln_sf(src, dest, noop: nil, verbose: nil) ln_s src, dest, force: true, noop: noop, verbose: verbose end module_function :ln_sf + # Like Bundler::FileUtils.ln_s, but create links relative to +dest+. # - # Hard links a file system entry +src+ to +dest+. - # If +src+ is a directory, this method links its contents recursively. + def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil) + options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" + dest = File.path(dest) + srcs = Array(src) + link = proc do |s, target_dir_p = true| + s = File.path(s) + if target_dir_p + d = File.join(destdirs = dest, File.basename(s)) + else + destdirs = File.dirname(d = dest) + end + destdirs = fu_split_path(File.realpath(destdirs)) + if fu_starting_path?(s) + srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s))) + base = fu_relative_components_from(srcdirs, destdirs) + s = File.join(*base) + else + srcdirs = fu_clean_components(*fu_split_path(s)) + base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs) + while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last) + srcdirs.shift + base.pop + end + s = File.join(*base, *srcdirs) + end + fu_output_message "ln -s#{options} #{s} #{d}" if verbose + next if noop + remove_file d, true if force + File.symlink s, d + end + case srcs.size + when 0 + when 1 + link[srcs[0], target_directory && File.directory?(dest)] + else + srcs.each(&link) + end + end + module_function :ln_sr + + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file and +dest+ does not exist, + # creates a hard link at +dest+ pointing to +src+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.link_entry('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # Both of +src+ and +dest+ must be a path name. - # +src+ must exist, +dest+ must not exist. + # If +src+ is the path to a directory and +dest+ does not exist, + # recursively creates hard links at +dest+ pointing to paths in +src+: # - # If +dereference_root+ is true, this method dereferences the tree root. + # Bundler::FileUtils.mkdir_p(['src1/dir0', 'src1/dir1']) + # src_file_paths = [ + # 'src1/dir0/t0.txt', + # 'src1/dir0/t1.txt', + # 'src1/dir1/t2.txt', + # 'src1/dir1/t3.txt', + # ] + # Bundler::FileUtils.touch(src_file_paths) + # File.directory?('dest1') # => true + # Bundler::FileUtils.link_entry('src1', 'dest1') + # 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 # - # If +remove_destination+ is true, this method removes each destination file before copy. + # Keyword arguments: + # + # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. + # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # + # 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: Bundler::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| @@ -412,16 +818,57 @@ module Bundler::FileUtils end module_function :link_entry + # Copies files. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file and +dest+ is not the path to a directory, + # copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.cp('src0.txt', 'dest0.txt') + # 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>: + # + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp('src1.txt', 'dest1') + # File.file?('dest1/src1.txt') # => true # - # Copies a file content +src+ to +dest+. If +dest+ is a directory, - # copies +src+ to +dest/src+. + # If +src+ is an array of paths to files and +dest+ is the path to a directory, + # copies from each +src+ to +dest+: # - # If +src+ is a list of files, then +dest+ must be a directory. + # src_file_paths = ['src2.txt', 'src2.dat'] + # Bundler::FileUtils.touch(src_file_paths) + # Bundler::FileUtils.mkdir('dest2') + # Bundler::FileUtils.cp(src_file_paths, 'dest2') + # File.file?('dest2/src2.txt') # => true + # File.file?('dest2/src2.dat') # => true # - # Bundler::FileUtils.cp 'eval.c', 'eval.c.org' - # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' - # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true - # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink + # Keyword arguments: + # + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>noop: true</tt> - does not copy files. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.cp('src1.txt', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp(src_file_paths, 'dest2', noop: true, verbose: true) + # + # Output: + # + # cp src0.txt dest0.txt + # cp src1.txt dest1 + # cp src2.txt src2.dat dest2 + # + # Raises an exception if +src+ is a directory. + # + # 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 @@ -435,30 +882,105 @@ module Bundler::FileUtils alias copy cp module_function :copy - # - # Copies +src+ to +dest+. If +src+ is a directory, this method copies - # all its contents recursively. If +dest+ is a directory, copies - # +src+ to +dest/src+. - # - # +src+ can be a list of files. - # - # If +dereference_root+ is true, this method dereference tree root. - # - # If +remove_destination+ is true, this method removes each destination file before copy. - # - # # Installing Ruby library "mylib" under the site_ruby - # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true - # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib' - # - # # Examples of copying several files to target directory. - # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' - # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true - # - # # If you want to copy all contents of a directory instead of the - # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, - # # use following code. - # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, - # # but this doesn't. + # Recursively copies files. + # + # Arguments +src+ (a single path or an array of paths) + # 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 Bundler::FileUtils.install instead. + # + # If +src+ is the path to a file and +dest+ is not the path to a directory, + # copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt') + # 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>: + # + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp_r('src1.txt', 'dest1') + # 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 + # Bundler::FileUtils.exist?('dest2') # => false + # Bundler::FileUtils.cp_r('src2', 'dest2') + # tree('dest2') + # # => 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 + # Bundler::FileUtils.mkdir('dest3') + # Bundler::FileUtils.cp_r('src3', 'dest3') + # tree('dest3') + # # => 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+; + # the paths in +src+ may point to files and/or directories. + # + # Keyword arguments: + # + # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link, + # does not dereference it. + # - <tt>noop: true</tt> - does not copy files. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src1.txt', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src2', 'dest2', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src3', 'dest3', noop: true, verbose: true) + # + # Output: + # + # cp -r src0.txt dest0.txt + # cp -r src1.txt dest1 + # cp -r src2 dest2 + # cp -r src3 dest3 + # + # Raises an exception of +src+ is the path to a directory + # and +dest+ is the path to a file. + # + # 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) @@ -470,21 +992,50 @@ module Bundler::FileUtils end module_function :cp_r + # Recursively copies files from +src+ to +dest+. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file, copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.copy_entry('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # Copies a file system entry +src+ to +dest+. - # If +src+ is a directory, this method copies its contents recursively. - # This method preserves file types, c.f. symlink, directory... - # (FIFO, device files and etc. are not supported yet) + # If +src+ is a directory, recursively copies +src+ to +dest+: # - # Both of +src+ and +dest+ must be a path name. - # +src+ must exist, +dest+ must not exist. + # tree('src1') + # # => src1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.copy_entry('src1', 'dest1') + # tree('dest1') + # # => dest1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt # - # If +preserve+ is true, this method preserves owner, group, and - # modified time. Permissions are copied regardless +preserve+. + # The recursive copying preserves file types for regular files, + # directories, and symbolic links; + # other file types (FIFO streams, device files, etc.) are not supported. # - # If +dereference_root+ is true, this method dereference tree root. + # Keyword arguments: # - # If +remove_destination+ is true, this method removes each destination file before copy. + # - <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. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) if dereference_root @@ -502,9 +1053,25 @@ module Bundler::FileUtils end module_function :copy_entry + # Copies file from +src+ to +dest+, which should not be directories. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Examples: + # + # Bundler::FileUtils.touch('src0.txt') + # Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true + # + # Keyword arguments: # - # Copies file contents of +src+ to +dest+. - # Both of +src+ and +dest+ must be a path name. + # - <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. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_file(src, dest, preserve = false, dereference = true) ent = Entry_.new(src, nil, dereference) @@ -513,25 +1080,79 @@ module Bundler::FileUtils end module_function :copy_file + # Copies \IO stream +src+ to \IO stream +dest+ via + # {IO.copy_stream}[rdoc-ref:IO.copy_stream]. # - # Copies stream +src+ to +dest+. - # +src+ must respond to #read(n) and - # +dest+ must respond to #write(str). + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_stream(src, dest) IO.copy_stream(src, dest) end module_function :copy_stream - # - # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different - # disk partition, the file is copied then the original file is removed. - # - # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb' - # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error - # - # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/' - # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true + # Moves entries. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ and +dest+ are on different file systems, + # first copies, then removes +src+. + # + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # If +src+ is the path to a single file or directory and +dest+ does not exist, + # moves +src+ to +dest+: + # + # tree('src0') + # # => src0 + # # |-- src0.txt + # # `-- src1.txt + # File.exist?('dest0') # => false + # Bundler::FileUtils.mv('src0', 'dest0') + # File.exist?('src0') # => false + # tree('dest0') + # # => dest0 + # # |-- src0.txt + # # `-- src1.txt + # + # If +src+ is an array of paths to files and directories + # and +dest+ is the path to a directory, + # copies from each path in the array to +dest+: + # + # File.file?('src1.txt') # => true + # tree('src1') + # # => src1 + # # |-- src.dat + # # `-- src.txt + # Dir.empty?('dest1') # => true + # Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1') + # tree('dest1') + # # => dest1 + # # |-- src1 + # # | |-- src.dat + # # | `-- src.txt + # # `-- src1.txt + # + # Keyword arguments: + # + # - <tt>force: true</tt> - if the move includes removing +src+ + # (that is, if +src+ and +dest+ are on different file systems), + # ignores raised exceptions of StandardError and its descendants. + # - <tt>noop: true</tt> - does not move files. + # - <tt>secure: true</tt> - removes +src+ securely; + # see details at Bundler::FileUtils.remove_entry_secure. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mv('src0', 'dest0', noop: true, verbose: true) + # Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1', noop: true, verbose: true) + # + # Output: + # + # mv src0 dest0 + # mv src1.txt src1 dest1 # def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil) fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -565,13 +1186,32 @@ module Bundler::FileUtils alias move mv module_function :move + # Removes entries at the paths in the given +list+ + # (a single path or an array of paths) + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. # - # Remove file(s) specified in +list+. This method cannot remove directories. - # All StandardErrors are ignored when the :force option is set. + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Bundler::FileUtils.rm %w( junk.txt dust.txt ) - # Bundler::FileUtils.rm Dir.glob('*.so') - # Bundler::FileUtils.rm 'NotExistFile', force: true # never raises exception + # With no keyword arguments, removes files at the paths given in +list+: + # + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) + # Bundler::FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"] + # + # Keyword arguments: + # + # - <tt>force: true</tt> - ignores raised exceptions of StandardError + # and its descendants. + # - <tt>noop: true</tt> - does not remove files; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true) + # + # Output: + # + # rm src0.dat src0.txt + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm(list, force: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -587,10 +1227,16 @@ module Bundler::FileUtils alias remove rm module_function :remove + # Equivalent to: + # + # Bundler::FileUtils.rm(list, force: true, **kwargs) # - # Equivalent to + # Argument +list+ (a single path or an array of paths) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Bundler::FileUtils.rm(list, force: true) + # See Bundler::FileUtils.rm for keyword arguments. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_f(list, noop: nil, verbose: nil) rm list, force: true, noop: noop, verbose: verbose @@ -600,24 +1246,55 @@ module Bundler::FileUtils alias safe_unlink rm_f module_function :safe_unlink + # Removes entries at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # For each file path, removes the file at that path: + # + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) + # Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt']) + # File.exist?('src0.txt') # => false + # File.exist?('src0.dat') # => false + # + # For each directory path, recursively removes files and directories: + # + # tree('src1') + # # => src1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.rm_r('src1') + # File.exist?('src1') # => false + # + # Keyword arguments: + # + # - <tt>force: true</tt> - ignores raised exceptions of StandardError + # and its descendants. + # - <tt>noop: true</tt> - does not remove entries; returns +nil+. + # - <tt>secure: true</tt> - removes +src+ securely; + # see details at Bundler::FileUtils.remove_entry_secure. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # remove files +list+[0] +list+[1]... If +list+[n] is a directory, - # removes its all contents recursively. This method ignores - # StandardError when :force option is set. + # Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true) + # Bundler::FileUtils.rm_r('src1', noop: true, verbose: true) # - # Bundler::FileUtils.rm_r Dir.glob('/tmp/*') - # Bundler::FileUtils.rm_r 'some_dir', force: true + # Output: # - # WARNING: This method causes local vulnerability - # if one of parent directories or removing directory tree are world - # writable (including /tmp, whose permission is 1777), and the current - # process has strong privilege such as Unix super user (root), and the - # system has symbolic link. For secure removing, read the documentation - # of remove_entry_secure carefully, and set :secure option to true. - # Default is <tt>secure: false</tt>. + # rm -r src0.dat src0.txt + # rm -r src1 # - # NOTE: This method calls remove_entry_secure if :secure option is set. - # See also remove_entry_secure. + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil) list = fu_list(list) @@ -633,13 +1310,20 @@ module Bundler::FileUtils end module_function :rm_r + # Equivalent to: # - # Equivalent to + # Bundler::FileUtils.rm_r(list, force: true, **kwargs) # - # Bundler::FileUtils.rm_r(list, force: true) + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # WARNING: This method causes local vulnerability. - # Read the documentation of rm_r first. + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # See Bundler::FileUtils.rm_r for keyword arguments. + # + # 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 @@ -649,37 +1333,20 @@ module Bundler::FileUtils alias rmtree rm_rf module_function :rmtree + # Securely removes the entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. # - # This method removes a file system entry +path+. +path+ shall be a - # regular file, a directory, or something. If +path+ is a directory, - # remove it recursively. This method is required to avoid TOCTTOU - # (time-of-check-to-time-of-use) local security vulnerability of rm_r. - # #rm_r causes security hole when: - # - # * Parent directory is world writable (including /tmp). - # * Removing directory tree includes world writable directory. - # * The system has symbolic link. - # - # To avoid this security hole, this method applies special preprocess. - # If +path+ is a directory, this method chown(2) and chmod(2) all - # removing directories. This requires the current process is the - # owner of the removing whole directory tree, or is the super user (root). + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # WARNING: You must ensure that *ALL* parent directories cannot be - # moved by other untrusted users. For example, parent directories - # should not be owned by untrusted users, and should not be world - # writable except when the sticky bit set. + # Avoids a local vulnerability that can exist in certain circumstances; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. # - # WARNING: Only the owner of the removing directory tree, or Unix super - # user (root) should invoke this method. Otherwise this method does not - # work. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. # - # For details of this security vulnerability, see Perl's case: - # - # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 - # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 - # - # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_entry_secure(path, force = false) unless fu_have_symlink? @@ -767,12 +1434,17 @@ module Bundler::FileUtils end private_module_function :fu_stat_identical_entry? + # Removes the entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. # - # This method removes a file system entry +path+. - # +path+ might be a regular file, a directory, or something. - # If +path+ is a directory, remove it recursively. + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # See also remove_entry_secure. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. + # + # Related: Bundler::FileUtils.remove_entry_secure. # def remove_entry(path, force = false) Entry_.new(path).postorder_traverse do |ent| @@ -787,9 +1459,16 @@ module Bundler::FileUtils end module_function :remove_entry + # Removes the file entry given by +path+, + # which should be the entry for a regular file or a symbolic link. + # + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Removes a file +path+. - # This method ignores StandardError if +force+ is true. + # 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 @@ -798,20 +1477,32 @@ module Bundler::FileUtils end module_function :remove_file + # Recursively removes the directory entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. + # + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. # - # Removes a directory +dir+ and its contents recursively. - # This method ignores StandardError if +force+ is true. + # 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 end module_function :remove_dir + # Returns +true+ if the contents of files +a+ and +b+ are identical, + # +false+ otherwise. # - # Returns true if the contents of a file +a+ and a file +b+ are identical. + # Arguments +a+ and +b+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true - # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false + # Bundler::FileUtils.identical? and Bundler::FileUtils.cmp are aliases for Bundler::FileUtils.compare_file. + # + # Related: Bundler::FileUtils.compare_stream. # def compare_file(a, b) return false unless File.size(a) == File.size(b) @@ -828,19 +1519,19 @@ module Bundler::FileUtils module_function :identical? module_function :cmp + # Returns +true+ if the contents of streams +a+ and +b+ are identical, + # +false+ otherwise. + # + # Arguments +a+ and +b+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Returns true if the contents of a stream +a+ and +b+ are identical. + # Related: Bundler::FileUtils.compare_file. # def compare_stream(a, b) bsize = fu_stream_blksize(a, b) - if RUBY_VERSION > "2.4" - sa = String.new(capacity: bsize) - sb = String.new(capacity: bsize) - else - sa = String.new - sb = String.new - end + sa = String.new(capacity: bsize) + sb = String.new(capacity: bsize) begin a.read(bsize, sa) @@ -851,13 +1542,69 @@ module Bundler::FileUtils end module_function :compare_stream + # Copies a file entry. + # See {install(1)}[https://man7.org/linux/man-pages/man1/install.1.html]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]; + # + # If the entry at +dest+ does not exist, copies from +src+ to +dest+: + # + # File.read('src0.txt') # => "aaa\n" + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.install('src0.txt', 'dest0.txt') + # File.read('dest0.txt') # => "aaa\n" + # + # If +dest+ is a file entry, copies from +src+ to +dest+, overwriting: + # + # File.read('src1.txt') # => "aaa\n" + # File.read('dest1.txt') # => "bbb\n" + # Bundler::FileUtils.install('src1.txt', 'dest1.txt') + # File.read('dest1.txt') # => "aaa\n" + # + # If +dest+ is a directory entry, copies from +src+ to <tt>dest/src</tt>, + # overwriting if necessary: + # + # File.read('src2.txt') # => "aaa\n" + # File.read('dest2/src2.txt') # => "bbb\n" + # Bundler::FileUtils.install('src2.txt', 'dest2') + # File.read('dest2/src2.txt') # => "aaa\n" + # + # If +src+ is an array of paths and +dest+ points to a directory, + # copies each path +path+ in +src+ to <tt>dest/path</tt>: + # + # File.file?('src3.txt') # => true + # File.file?('src3.dat') # => true + # Bundler::FileUtils.mkdir('dest3') + # Bundler::FileUtils.install(['src3.txt', 'src3.dat'], 'dest3') + # File.file?('dest3/src3.txt') # => true + # File.file?('dest3/src3.dat') # => true + # + # Keyword arguments: # - # If +src+ is not same as +dest+, copies it and changes the permission - # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. - # This method removes destination before copy. + # - <tt>group: <i>group</i></tt> - changes the group if not +nil+, + # using {File.chown}[rdoc-ref:File.chown]. + # - <tt>mode: <i>permissions</i></tt> - changes the permissions. + # 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}[rdoc-ref:File.chown]. + # - <tt>preserve: true</tt> - preserve timestamps + # using {File.utime}[rdoc-ref:File.utime]. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true - # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true + # Bundler::FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.install('src1.txt', 'dest1.txt', noop: true, verbose: true) + # Bundler::FileUtils.install('src2.txt', 'dest2', noop: true, verbose: true) + # + # Output: + # + # install -c src0.txt dest0.txt + # 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) @@ -877,7 +1624,13 @@ module Bundler::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 @@ -917,11 +1670,8 @@ module Bundler::FileUtils private_module_function :apply_mask def symbolic_modes_to_i(mode_sym, path) #:nodoc: - mode = if File::Stat === path - path.mode - else - File.stat(path).mode - end + path = File.stat(path) unless File::Stat === path + mode = path.mode mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause| target, *actions = clause.split(/([=+-])/) raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty? @@ -938,7 +1688,7 @@ module Bundler::FileUtils when "x" mask | 0111 when "X" - if FileTest.directory? path + if path.directory? mask | 0111 else mask @@ -978,37 +1728,78 @@ module Bundler::FileUtils end private_module_function :mode_to_s + # Changes permissions on the entries at the paths given in +list+ + # (a single path or an array of paths) + # to the permissions given by +mode+; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise: + # + # - Modifies each entry that is a regular file using + # {File.chmod}[rdoc-ref:File.chmod]. + # - Modifies each entry that is a symbolic link using + # {File.lchmod}[rdoc-ref:File.lchmod]. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Argument +mode+ may be either an integer or a string: + # + # - \Integer +mode+: represents the permission bits to be set: + # + # Bundler::FileUtils.chmod(0755, 'src0.txt') + # Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat']) + # + # - \String +mode+: represents the permissions to be set: + # + # The string is of the form <tt>[targets][[operator][perms[,perms]]</tt>, where: + # + # - +targets+ may be any combination of these letters: + # + # - <tt>'u'</tt>: permissions apply to the file's owner. + # - <tt>'g'</tt>: permissions apply to users in the file's group. + # - <tt>'o'</tt>: permissions apply to other users not in the file's group. + # - <tt>'a'</tt> (the default): permissions apply to all users. + # + # - +operator+ may be one of these letters: + # + # - <tt>'+'</tt>: adds permissions. + # - <tt>'-'</tt>: removes permissions. + # - <tt>'='</tt>: sets (replaces) permissions. + # + # - +perms+ (may be repeated, with separating commas) + # may be any combination of these letters: + # + # - <tt>'r'</tt>: Read. + # - <tt>'w'</tt>: Write. + # - <tt>'x'</tt>: Execute (search, for a directory). + # - <tt>'X'</tt>: Search (for a directories only; + # must be used with <tt>'+'</tt>) + # - <tt>'s'</tt>: Uid or gid. + # - <tt>'t'</tt>: Sticky bit. + # + # Examples: + # + # Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt') + # Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby') + # + # Keyword arguments: + # + # - <tt>noop: true</tt> - does not change permissions; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.chmod(0755, 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt', noop: true, verbose: true) + # Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby', noop: true, verbose: true) + # + # Output: + # + # chmod 755 src0.txt + # chmod 644 src0.txt src0.dat + # chmod u=wrx,go=rx src1.txt + # chmod u=wrx,go=rx /usr/bin/ruby + # + # Related: Bundler::FileUtils.chmod_R. # - # Changes permission bits on the named files (in +list+) to the bit pattern - # represented by +mode+. - # - # +mode+ is the symbolic and absolute mode can be used. - # - # Absolute mode is - # Bundler::FileUtils.chmod 0755, 'somecommand' - # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) - # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true - # - # Symbolic mode is - # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand' - # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) - # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true - # - # "a" :: is user, group, other mask. - # "u" :: is user's mask. - # "g" :: is group's mask. - # "o" :: is other's mask. - # "w" :: is write permission. - # "r" :: is read permission. - # "x" :: is execute permission. - # "X" :: - # is execute permission for directories only, must be used in conjunction with "+" - # "s" :: is uid, gid. - # "t" :: is sticky bit. - # "+" :: is added to a class given the specified mode. - # "-" :: Is removed from a given class given mode. - # "=" :: Is the exact nature of the class will be given a specified mode. - 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 @@ -1019,12 +1810,7 @@ module Bundler::FileUtils end module_function :chmod - # - # Changes permission bits on the named files (in +list+) - # to the bit pattern represented by +mode+. - # - # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}" - # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" + # Like Bundler::FileUtils.chmod, but changes permissions recursively. # def chmod_R(mode, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) @@ -1044,15 +1830,68 @@ module Bundler::FileUtils end module_function :chmod_R + # Changes the owner and group on the entries at the paths given in +list+ + # (a single path or an array of paths) + # to the given +user+ and +group+; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise: + # + # - Modifies each entry that is a regular file using + # {File.chown}[rdoc-ref:File.chown]. + # - Modifies each entry that is a symbolic link using + # {File.lchown}[rdoc-ref:File.lchown]. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # User and group: + # + # - Argument +user+ may be a user name or a user id; + # if +nil+ or +-1+, the user is not changed. + # - Argument +group+ may be a group name or a group id; + # if +nil+ or +-1+, the group is not changed. + # - The user must be a member of the group. # - # Changes owner and group on the named files (in +list+) - # to the user +user+ and the group +group+. +user+ and +group+ - # may be an ID (Integer/String) or a name (String). - # If +user+ or +group+ is nil, this method does not change - # the attribute. + # Examples: # - # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' - # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true + # # One path. + # # User and group as string names. + # File.stat('src0.txt').uid # => 1004 + # File.stat('src0.txt').gid # => 1004 + # Bundler::FileUtils.chown('user2', 'group1', 'src0.txt') + # File.stat('src0.txt').uid # => 1006 + # File.stat('src0.txt').gid # => 1005 + # + # # User and group as uid and gid. + # Bundler::FileUtils.chown(1004, 1004, 'src0.txt') + # File.stat('src0.txt').uid # => 1004 + # File.stat('src0.txt').gid # => 1004 + # + # # Array of paths. + # Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat']) + # + # # Directory (not recursive). + # Bundler::FileUtils.chown('user2', 'group1', '.') + # + # Keyword arguments: + # + # - <tt>noop: true</tt> - does not change permissions; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.chown('user2', 'group1', 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chown(1004, 1004, 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.chown('user2', 'group1', path, noop: true, verbose: true) + # Bundler::FileUtils.chown('user2', 'group1', '.', noop: true, verbose: true) + # + # Output: + # + # chown user2:group1 src0.txt + # chown 1004:1004 src0.txt + # chown 1006:1005 src0.txt src0.dat + # chown user2:group1 src0.txt + # chown user2:group1 . + # + # Related: Bundler::FileUtils.chown_R. # def chown(user, group, list, noop: nil, verbose: nil) list = fu_list(list) @@ -1068,15 +1907,7 @@ module Bundler::FileUtils end module_function :chown - # - # Changes owner and group on the named files (in +list+) - # to the user +user+ and the group +group+ recursively. - # +user+ and +group+ may be an ID (Integer/String) or - # a name (String). If +user+ or +group+ is nil, this - # method does not change the attribute. - # - # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs' - # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true + # Like Bundler::FileUtils.chown, but changes owner and group recursively. # def chown_R(user, group, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) @@ -1127,12 +1958,50 @@ module Bundler::FileUtils end private_module_function :fu_get_gid + # Updates modification times (mtime) and access times (atime) + # of the entries given by the paths in +list+ + # (a single path or an array of paths); + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # By default, creates an empty file for any path to a non-existent entry; + # use keyword argument +nocreate+ to raise an exception instead. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Examples: + # + # # Single path. + # f = File.new('src0.txt') # Existing file. + # f.atime # => 2022-06-10 11:11:21.200277 -0700 + # f.mtime # => 2022-06-10 11:11:21.200277 -0700 + # Bundler::FileUtils.touch('src0.txt') + # f = File.new('src0.txt') + # f.atime # => 2022-06-11 08:28:09.8185343 -0700 + # f.mtime # => 2022-06-11 08:28:09.8185343 -0700 + # + # # Array of paths. + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) # - # Updates modification time (mtime) and access time (atime) of file(s) in - # +list+. Files are created if they don't exist. + # Keyword arguments: # - # Bundler::FileUtils.touch 'timestamp' - # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make' + # - <tt>mtime: <i>time</i></tt> - sets the entry's mtime to the given time, + # instead of the current time. + # - <tt>nocreate: true</tt> - raises an exception if the entry does not exist. + # - <tt>noop: true</tt> - does not touch entries; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.touch('src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.touch(path, noop: true, verbose: true) + # + # Output: + # + # touch src0.txt + # touch src0.txt src0.dat + # touch src0.txt + # + # Related: Bundler::FileUtils.uptodate?. # def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil) list = fu_list(list) @@ -1290,14 +2159,9 @@ module Bundler::FileUtils def entries opts = {} - opts[:encoding] = ::Encoding::UTF_8 if fu_windows? + opts[:encoding] = fu_windows? ? ::Encoding::UTF_8 : path.encoding - files = if Dir.respond_to?(:children) - Dir.children(path, **opts) - else - Dir.entries(path(), **opts) - .reject {|n| n == '.' or n == '..' } - end + files = Dir.children(path, **opts) untaint = RUBY_VERSION < '2.7' files.map {|n| Entry_.new(prefix(), join(rel(), untaint ? n.untaint : n)) } @@ -1345,6 +2209,7 @@ module Bundler::FileUtils else File.chmod mode, path() end + rescue Errno::EOPNOTSUPP end def chown(uid, gid) @@ -1439,7 +2304,7 @@ module Bundler::FileUtils if st.symlink? begin File.lchmod mode, path - rescue NotImplementedError + rescue NotImplementedError, Errno::EOPNOTSUPP end else File.chmod mode, path @@ -1498,13 +2363,21 @@ module Bundler::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 @@ -1559,7 +2432,15 @@ module Bundler::FileUtils def join(dir, base) return File.path(dir) if not base or base == '.' return File.path(base) if not dir or dir == '.' - File.join(dir, base) + begin + File.join(dir, base) + rescue EncodingError + if fu_windows? + File.join(dir.encode(::Encoding::UTF_8), base.encode(::Encoding::UTF_8)) + else + raise + end + end end if File::ALT_SEPARATOR @@ -1590,15 +2471,15 @@ module Bundler::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) 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) @@ -1614,7 +2495,7 @@ module Bundler::FileUtils def fu_output_message(msg) #:nodoc: output = @fileutils_output if defined?(@fileutils_output) - output ||= $stderr + output ||= $stdout if defined?(@fileutils_label) msg = @fileutils_label + msg end @@ -1622,6 +2503,56 @@ module Bundler::FileUtils end private_module_function :fu_output_message + def fu_split_path(path) + path = File.path(path) + list = [] + until (parent, base = File.split(path); parent == path or parent == ".") + list << base + path = parent + end + list << path + list.reverse! + end + private_module_function :fu_split_path + + def fu_relative_components_from(target, base) #:nodoc: + i = 0 + while target[i]&.== base[i] + i += 1 + end + Array.new(base.size-i, '..').concat(target[i..-1]) + end + private_module_function :fu_relative_components_from + + def fu_clean_components(*comp) + 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.chomp!(%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) + path&.start_with?(%r(\w:|/)) + end + else + def fu_starting_path?(path) + 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| @@ -1631,50 +2562,49 @@ module Bundler::FileUtils public + # Returns an array of the string names of \Bundler::FileUtils methods + # that accept one or more keyword arguments: # - # Returns an Array of names of high-level methods that accept any keyword - # arguments. - # - # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # Bundler::FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"] # def self.commands OPT_TABLE.keys end + # Returns an array of the string keyword names: # - # Returns an Array of option names. - # - # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # Bundler::FileUtils.options.take(3) # => ["noop", "verbose", "force"] # def self.options OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } end + # Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise; + # the arguments may be strings or symbols: # - # Returns true if the method +mid+ have an option +opt+. - # - # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true - # p Bundler::FileUtils.have_option?(:rm, :force) #=> true - # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false + # Bundler::FileUtils.have_option?(:chmod, :noop) # => true + # Bundler::FileUtils.have_option?('chmod', 'secure') # => false # def self.have_option?(mid, opt) li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" li.include?(opt) end + # Returns an array of the string keyword name for method +mid+; + # the argument may be a string or a symbol: # - # Returns an Array of option names of the method +mid+. - # - # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"] + # Bundler::FileUtils.options_of(:rm) # => ["force", "noop", "verbose"] + # Bundler::FileUtils.options_of('mv') # => ["force", "noop", "verbose", "secure"] # def self.options_of(mid) OPT_TABLE[mid.to_s].map {|sym| sym.to_s } end + # Returns an array of the string method names of the methods + # that accept the given keyword option +opt+; + # the argument must be a symbol: # - # Returns an Array of methods names which have the option +opt+. - # - # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] + # Bundler::FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"] # def self.collect_method(opt) OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } |