module Bundler::FileUtils

def self.collect_method(opt)


Bundler::FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"]

the argument must be a symbol:
that accept the given keyword option +opt+;
Returns an array of the string method names of the methods
def self.collect_method(opt)
  OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
end

def self.commands


Bundler::FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"]

that accept one or more keyword arguments:
Returns an array of the string names of \Bundler::FileUtils methods
def self.commands
  OPT_TABLE.keys
end

def self.have_option?(mid, opt)


Bundler::FileUtils.have_option?('chmod', 'secure') # => false
Bundler::FileUtils.have_option?(:chmod, :noop) # => true

the arguments may be strings or symbols:
Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise;
def self.have_option?(mid, opt)
  li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
  li.include?(opt)
end

def self.options


Bundler::FileUtils.options.take(3) # => ["noop", "verbose", "force"]

Returns an array of the string keyword names:
def self.options
  OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
end

def self.options_of(mid)


Bundler::FileUtils.options_of('mv') # => ["force", "noop", "verbose", "secure"]
Bundler::FileUtils.options_of(:rm) # => ["force", "noop", "verbose"]

the argument may be a string or a symbol:
Returns an array of the string keyword name for method +mid+;
def self.options_of(mid)
  OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
end

def self.private_module_function(name) #:nodoc:

:nodoc:
def self.private_module_function(name)   #:nodoc:
  module_function name
  private_class_method name
end

def apply_mask(mode, user_mask, op, mode_mask) #:nodoc:

:nodoc:
def apply_mask(mode, user_mask, op, mode_mask)   #:nodoc:
  case op
  when '='
    (mode & ~user_mask) | (user_mask & mode_mask)
  when '+'
    mode | (user_mask & mode_mask)
  when '-'
    mode & ~(user_mask & mode_mask)
  end
end

def cd(dir, verbose: nil, &block) # :yield: dir

:yield: dir

Related: Bundler::FileUtils.pwd.

cd fileutils
cd ..

Output:

Bundler::FileUtils.cd('fileutils')
Bundler::FileUtils.cd('..')

- verbose: true - prints an equivalent command:

Keyword arguments:

Bundler::FileUtils.pwd # => "/rdoc/fileutils"
Bundler::FileUtils.cd('..') { |arg| [arg, Bundler::FileUtils.pwd] } # => ["..", "/rdoc"]
Bundler::FileUtils.pwd # => "/rdoc/fileutils"

and restores the original current directory; returns the block's value:
at +dir+, calls the block with argument +dir+,
With a block given, changes the current directory to the directory

Bundler::FileUtils.cd('fileutils')
Bundler::FileUtils.pwd # => "/rdoc"
Bundler::FileUtils.cd('..')
Bundler::FileUtils.pwd # => "/rdoc/fileutils"

changes the current directory to the directory at +dir+; returns zero:
With no block given,

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]:
Changes the working directory to the given +dir+, which
def cd(dir, verbose: nil, &block) # :yield: dir
  fu_output_message "cd #{dir}" if verbose
  result = Dir.chdir(dir, &block)
  fu_output_message 'cd -' if verbose and block
  result
end

def chmod(mode, list, noop: nil, verbose: nil)


Related: Bundler::FileUtils.chmod_R.

chmod u=wrx,go=rx /usr/bin/ruby
chmod u=wrx,go=rx src1.txt
chmod 644 src0.txt src0.dat
chmod 755 src0.txt

Output:

Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby', noop: true, verbose: true)
Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt', noop: true, verbose: true)
Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat'], noop: true, verbose: true)
Bundler::FileUtils.chmod(0755, 'src0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not change permissions; returns +nil+.

Keyword arguments:

Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby')
Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt')

Examples:

- 't': Sticky bit.
- 's': Uid or gid.
must be used with '+')
- 'X': Search (for a directories only;
- 'x': Execute (search, for a directory).
- 'w': Write.
- 'r': Read.

may be any combination of these letters:
- +perms+ (may be repeated, with separating commas)

- '=': sets (replaces) permissions.
- '-': removes permissions.
- '+': adds permissions.

- +operator+ may be one of these letters:

- 'a' (the default): permissions apply to all users.
- 'o': permissions apply to other users not in the file's group.
- 'g': permissions apply to users in the file's group.
- 'u': permissions apply to the file's owner.

- +targets+ may be any combination of these letters:

The string is of the form [targets][[operator][perms[,perms]], where:

- \String +mode+: represents the permissions to be set:

Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat'])
Bundler::FileUtils.chmod(0755, 'src0.txt')

- \Integer +mode+: represents the permission bits to be set:

Argument +mode+ may be either an integer or a string:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

{File.lchmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchmod].
- Modifies each entry that is a symbolic link using
{File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
- Modifies each entry that is a regular file using

returns +list+ if it is an array, [list] otherwise:
to the permissions given by +mode+;
(a single path or an array of paths)
Changes permissions on the entries at the paths given in +list+
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
  return if noop
  list.each do |path|
    Entry_.new(path).chmod(fu_mode(mode, path))
  end
end

def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)


Like Bundler::FileUtils.chmod, but changes permissions recursively.
def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
  list = fu_list(list)
  fu_output_message sprintf('chmod -R%s %s %s',
                            (force ? 'f' : ''),
                            mode_to_s(mode), list.join(' ')) if verbose
  return if noop
  list.each do |root|
    Entry_.new(root).traverse do |ent|
      begin
        ent.chmod(fu_mode(mode, ent.path))
      rescue
        raise unless force
      end
    end
  end
end

def chown(user, group, list, noop: nil, verbose: nil)


Related: Bundler::FileUtils.chown_R.

chown user2:group1 .
chown user2:group1 src0.txt
chown 1006:1005 src0.txt src0.dat
chown 1004:1004 src0.txt
chown user2:group1 src0.txt

Output:

Bundler::FileUtils.chown('user2', 'group1', '.', noop: true, verbose: true)
Bundler::FileUtils.chown('user2', 'group1', path, noop: true, verbose: true)
Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'], noop: true, verbose: true)
Bundler::FileUtils.chown(1004, 1004, 'src0.txt', noop: true, verbose: true)
Bundler::FileUtils.chown('user2', 'group1', 'src0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not change permissions; returns +nil+.

Keyword arguments:

Bundler::FileUtils.chown('user2', 'group1', '.')
# Directory (not recursive).

Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'])
# Array of paths.

File.stat('src0.txt').gid # => 1004
File.stat('src0.txt').uid # => 1004
Bundler::FileUtils.chown(1004, 1004, 'src0.txt')
# User and group as uid and gid.

File.stat('src0.txt').gid # => 1005
File.stat('src0.txt').uid # => 1006
Bundler::FileUtils.chown('user2', 'group1', 'src0.txt')
File.stat('src0.txt').gid # => 1004
File.stat('src0.txt').uid # => 1004
# User and group as string names.
# One path.

Examples:

- The user must be a member of the group.
if +nil+ or +-1+, the group is not changed.
- Argument +group+ may be a group name or a group id;
if +nil+ or +-1+, the user is not changed.
- Argument +user+ may be a user name or a user id;

User and group:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

{File.lchown}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchown].
- Modifies each entry that is a symbolic link using
{File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
- Modifies each entry that is a regular file using

returns +list+ if it is an array, [list] otherwise:
to the given +user+ and +group+;
(a single path or an array of paths)
Changes the owner and group on the entries at the paths given in +list+
def chown(user, group, list, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message sprintf('chown %s %s',
                            (group ? "#{user}:#{group}" : user || ':'),
                            list.join(' ')) if verbose
  return if noop
  uid = fu_get_uid(user)
  gid = fu_get_gid(group)
  list.each do |path|
    Entry_.new(path).chown uid, gid
  end
end

def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)


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)
  fu_output_message sprintf('chown -R%s %s %s',
                            (force ? 'f' : ''),
                            (group ? "#{user}:#{group}" : user || ':'),
                            list.join(' ')) if verbose
  return if noop
  uid = fu_get_uid(user)
  gid = fu_get_gid(group)
  list.each do |root|
    Entry_.new(root).traverse do |ent|
      begin
        ent.chown uid, gid
      rescue
        raise unless force
      end
    end
  end
end

def compare_file(a, b)


Related: Bundler::FileUtils.compare_stream.

Bundler::FileUtils.identical? and Bundler::FileUtils.cmp are aliases for Bundler::FileUtils.compare_file.

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
Arguments +a+ and +b+

+false+ otherwise.
Returns +true+ if the contents of files +a+ and +b+ are identical,
def compare_file(a, b)
  return false unless File.size(a) == File.size(b)
  File.open(a, 'rb') {|fa|
    File.open(b, 'rb') {|fb|
      return compare_stream(fa, fb)
    }
  }
end

def compare_stream(a, b)


Related: Bundler::FileUtils.compare_file.

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
Arguments +a+ and +b+

+false+ otherwise.
Returns +true+ if the contents of streams +a+ and +b+ are identical,
def compare_stream(a, b)
  bsize = fu_stream_blksize(a, b)
  sa = String.new(capacity: bsize)
  sb = String.new(capacity: bsize)
  begin
    a.read(bsize, sa)
    b.read(bsize, sb)
    return true if sa.empty? && sb.empty?
  end while sa == sb
  false
end

def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

- remove_destination: true - removes +dest+ before copying files.
- preserve: true - preserves file times.
follows the link.
- dereference_root: true - if +src+ is a symbolic link,

Keyword arguments:

other file types (FIFO streams, device files, etc.) are not supported.
directories, and symbolic links;
The recursive copying preserves file types for regular files,

# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# => dest1
tree('dest1')
Bundler::FileUtils.copy_entry('src1', 'dest1')
# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# => src1
tree('src1')

If +src+ is a directory, recursively copies +src+ to +dest+:

File.file?('dest0.txt') # => true
Bundler::FileUtils.copy_entry('src0.txt', 'dest0.txt')
File.exist?('dest0.txt') # => false
Bundler::FileUtils.touch('src0.txt')

If +src+ is the path to a file, copies +src+ to +dest+:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Arguments +src+ and +dest+

Recursively copies files from +src+ to +dest+.
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
  if dereference_root
    src = File.realpath(src)
  end
  Entry_.new(src, nil, false).wrap_traverse(proc do |ent|
    destent = Entry_.new(dest, ent.rel, false)
    File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path))
    ent.copy destent.path
  end, proc do |ent|
    destent = Entry_.new(dest, ent.rel, false)
    ent.copy_metadata destent.path if preserve
  end)
end

def copy_file(src, dest, preserve = false, dereference = true)


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

- remove_destination: true - removes +dest+ before copying files.
- preserve: true - preserves file times.
does not follow the link.
- dereference: false - if +src+ is a symbolic link,

Keyword arguments:

File.file?('dest0.txt') # => true
Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt')
Bundler::FileUtils.touch('src0.txt')

Examples:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Arguments +src+ and +dest+

Copies file from +src+ to +dest+, which should not be directories.
def copy_file(src, dest, preserve = false, dereference = true)
  ent = Entry_.new(src, nil, dereference)
  ent.copy_file dest
  ent.copy_metadata dest if preserve
end

def copy_stream(src, dest)


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

{IO.copy_stream}[https://docs.ruby-lang.org/en/master/IO.html#method-c-copy_stream].
Copies \IO stream +src+ to \IO stream +dest+ via
def copy_stream(src, dest)
  IO.copy_stream(src, dest)
end

def cp(src, dest, preserve: nil, noop: nil, verbose: nil)


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

Raises an exception if +src+ is a directory.

cp src2.txt src2.dat dest2
cp src1.txt dest1
cp src0.txt dest0.txt

Output:

Bundler::FileUtils.cp(src_file_paths, 'dest2', noop: true, verbose: true)
Bundler::FileUtils.cp('src1.txt', 'dest1', noop: true, verbose: true)
Bundler::FileUtils.cp('src0.txt', 'dest0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not copy files.
- preserve: true - preserves file times.

Keyword arguments:

File.file?('dest2/src2.dat') # => true
File.file?('dest2/src2.txt') # => true
Bundler::FileUtils.cp(src_file_paths, 'dest2')
Bundler::FileUtils.mkdir('dest2')
Bundler::FileUtils.touch(src_file_paths)
src_file_paths = ['src2.txt', 'src2.dat']

copies from each +src+ to +dest+:
If +src+ is an array of paths to files and +dest+ is the path to a directory,

File.file?('dest1/src1.txt') # => true
Bundler::FileUtils.cp('src1.txt', 'dest1')
Bundler::FileUtils.mkdir('dest1')
Bundler::FileUtils.touch('src1.txt')

copies +src+ to dest/src:
If +src+ is the path to a file and +dest+ is the path to a directory,

File.file?('dest0.txt') # => true
Bundler::FileUtils.cp('src0.txt', 'dest0.txt')
File.exist?('dest0.txt') # => false
Bundler::FileUtils.touch('src0.txt')

copies +src+ to +dest+:
If +src+ is the path to a file and +dest+ is not the path to a directory,

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

Copies files.
def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
  fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    copy_file s, d, preserve
  end
end

def cp_lr(src, dest, noop: nil, verbose: nil,


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

and keyword argument remove_destination: true is not given.
Raises an exception if +dest+ is the path to an existing file or directory

cp -lr src2/sub0 src2/sub1 dest2
cp -lr src1 dest1
cp -lr src0 dest0

Output:

Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true)
Bundler::FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true)
Bundler::FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- remove_destination: true - removes +dest+ before creating links.
- noop: true - does not create links.
does not dereference it.
- dereference_root: false - if +src+ is a symbolic link,

Keyword arguments:

# `-- src3.txt
# |-- src2.txt
# `-- sub1
# | `-- src1.txt
# | |-- src0.txt
# |-- sub0
# => dest2
tree('dest2')
Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2')
Bundler::FileUtils.mkdir('dest2')
# `-- src3.txt
# |-- src2.txt
# `-- sub1
# | `-- src1.txt
# | |-- src0.txt
# |-- sub0
# => src2
tree('src2')

pointing to that path:
for each path +filepath+ in +src+, creates a link at dest/filepath
If +src+ is an array of paths to entries and +dest+ is the path to a directory,

# `-- src3.txt
# |-- src2.txt
# `-- sub1
# | `-- src1.txt
# | |-- src0.txt
# |-- sub0
# `-- src1
# => dest1
tree('dest1')
Bundler::FileUtils.cp_lr('src1', 'dest1')
Bundler::FileUtils.mkdir('dest1')
# `-- src3.txt
# |-- src2.txt
# `-- sub1
# | `-- src1.txt
# | |-- src0.txt
# |-- sub0
# => src1
tree('src1')

pointing to +src+ and its descendents:
creates links dest/src and descendents
If +src+ and +dest+ are both paths to directories,

# `-- src3.txt
# |-- src2.txt
# `-- sub1
# | `-- src1.txt
# | |-- src0.txt
# |-- sub0
# => dest0
tree('dest0')
Bundler::FileUtils.cp_lr('src0', 'dest0')
File.exist?('dest0') # => false
# `-- src3.txt
# |-- src2.txt
# `-- sub1
# | `-- src1.txt
# | |-- src0.txt
# |-- sub0
# => src0
tree('src0')

creates links +dest+ and descendents pointing to +src+ and its descendents:
If +src+ is the path to a directory and +dest+ does not exist,

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link].
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
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    link_entry s, d, dereference_root, remove_destination
  end
end

def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

and +dest+ is the path to a file.
Raises an exception of +src+ is the path to a directory

cp -r src3 dest3
cp -r src2 dest2
cp -r src1.txt dest1
cp -r src0.txt dest0.txt

Output:

Bundler::FileUtils.cp_r('src3', 'dest3', noop: true, verbose: true)
Bundler::FileUtils.cp_r('src2', 'dest2', noop: true, verbose: true)
Bundler::FileUtils.cp_r('src1.txt', 'dest1', noop: true, verbose: true)
Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- remove_destination: true - removes +dest+ before copying files.
- preserve: true - preserves file times.
- noop: true - does not copy files.
does not dereference it.
- dereference_root: false - if +src+ is a symbolic link,

Keyword arguments:

the paths in +src+ may point to files and/or directories.
recursively copies from each path in +src+ to +dest+;
If +src+ is an array of paths and +dest+ is a directory,

# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# `-- src3
# => dest3
tree('dest3')
Bundler::FileUtils.cp_r('src3', 'dest3')
Bundler::FileUtils.mkdir('dest3')
# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# => src3
tree('src3')

recursively copies +src+ to dest/src:
If +src+ and +dest+ are paths to directories,

# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# => dest2
tree('dest2')
Bundler::FileUtils.cp_r('src2', 'dest2')
Bundler::FileUtils.exist?('dest2') # => false
# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# => src2
tree('src2')

recursively copies +src+ to +dest+:
If +src+ is the path to a directory and +dest+ does not exist,

File.file?('dest1/src1.txt') # => true
Bundler::FileUtils.cp_r('src1.txt', 'dest1')
Bundler::FileUtils.mkdir('dest1')
Bundler::FileUtils.touch('src1.txt')

copies +src+ to dest/src:
If +src+ is the path to a file and +dest+ is the path to a directory,

File.file?('dest0.txt') # => true
Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt')
File.exist?('dest0.txt') # => false
Bundler::FileUtils.touch('src0.txt')

copies +src+ to +dest+:
If +src+ is the path to a file and +dest+ is not the path to a directory,

to change those, use Bundler::FileUtils.install instead.
The mode, owner, and group are retained in the copy;

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

Recursively copies files.
def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
         dereference_root: true, remove_destination: nil)
  fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    copy_entry s, d, preserve, dereference_root, remove_destination
  end
end

def fu_clean_components(*comp) #:nodoc:

:nodoc:
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.chomp!(%r((?<=\A|/)[^/]+/\z), "")
    else
      clean << c
      path << c << "/"
    end
  end
  clean
end

def fu_each_src_dest(src, dest) #:nodoc:

:nodoc:
def fu_each_src_dest(src, dest)   #:nodoc:
  fu_each_src_dest0(src, dest) do |s, d|
    raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
    yield s, d
  end
end

def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:

: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, (target_directory ? File.join(dest, File.basename(s)) : dest)
    end
  else
    src = File.path(src)
    if target_directory and File.directory?(dest)
      yield src, File.join(dest, File.basename(src))
    else
      yield src, File.path(dest)
    end
  end
end

def fu_get_gid(group) #:nodoc:

:nodoc:
def fu_get_gid(group)   #:nodoc:
  return nil unless group
  case group
  when Integer
    group
  when /\A\d+\z/
    group.to_i
  else
    require 'etc'
    Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
  end
end

def fu_get_uid(user) #:nodoc:

:nodoc:
def fu_get_uid(user)   #:nodoc:
  return nil unless user
  case user
  when Integer
    user
  when /\A\d+\z/
    user.to_i
  else
    require 'etc'
    Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
  end
end

def fu_have_symlink? #:nodoc:

:nodoc:
def fu_have_symlink?   #:nodoc:
  File.symlink nil, nil
rescue NotImplementedError
  return false
rescue TypeError
  return true
end

def fu_list(arg) #:nodoc:

:nodoc:
def fu_list(arg)   #:nodoc:
  [arg].flatten.map {|path| File.path(path) }
end

def fu_mkdir(path, mode) #:nodoc:

:nodoc:
def fu_mkdir(path, mode)   #:nodoc:
  path = remove_trailing_slash(path)
  if mode
    Dir.mkdir path, mode
    File.chmod mode, path
  else
    Dir.mkdir path
  end
end

def fu_mode(mode, path) #:nodoc:

:nodoc:
def fu_mode(mode, path)  #:nodoc:
  mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
end

def fu_output_message(msg) #:nodoc:

:nodoc:
def fu_output_message(msg)   #:nodoc:
  output = @fileutils_output if defined?(@fileutils_output)
  output ||= $stdout
  if defined?(@fileutils_label)
    msg = @fileutils_label + msg
  end
  output.puts msg
end

def fu_relative_components_from(target, base) #:nodoc:

:nodoc:
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

def fu_same?(a, b) #:nodoc:

:nodoc:
def fu_same?(a, b)   #:nodoc:
  File.identical?(a, b)
end

def fu_split_path(path) #:nodoc:

:nodoc:
def fu_split_path(path) #:nodoc:
  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

def fu_starting_path?(path) #:nodoc:

:nodoc:
def fu_starting_path?(path) #:nodoc:
  path&.start_with?(%r(\w:|/))
end

def fu_starting_path?(path) #:nodoc:

:nodoc:
def fu_starting_path?(path) #:nodoc:
  path&.start_with?("/")
end

def fu_stat_identical_entry?(a, b) #:nodoc:

:nodoc:
def fu_stat_identical_entry?(a, b)   #:nodoc:
  a.dev == b.dev and a.ino == b.ino
end

def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,


Related: {methods for copying}[rdoc-ref:FileUtils@Copying].

install -c src2.txt dest2
install -c src1.txt dest1.txt
install -c src0.txt dest0.txt

Output:

Bundler::FileUtils.install('src2.txt', 'dest2', noop: true, verbose: true)
Bundler::FileUtils.install('src1.txt', 'dest1.txt', noop: true, verbose: true)
Bundler::FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
using {File.utime}[https://docs.ruby-lang.org/en/master/File.html#method-c-utime].
- preserve: true - preserve timestamps
using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
- owner: owner - changes the owner if not +nil+,
- noop: true - does not copy entries; returns +nil+.
using {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
- mode: permissions - changes the permissions.
using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
- group: group - changes the group if not +nil+,

Keyword arguments:

File.file?('dest3/src3.dat') # => true
File.file?('dest3/src3.txt') # => true
Bundler::FileUtils.install(['src3.txt', 'src3.dat'], 'dest3')
Bundler::FileUtils.mkdir('dest3')
File.file?('src3.dat') # => true
File.file?('src3.txt') # => true

copies each path +path+ in +src+ to dest/path:
If +src+ is an array of paths and +dest+ points to a directory,

File.read('dest2/src2.txt') # => "aaa\n"
Bundler::FileUtils.install('src2.txt', 'dest2')
File.read('dest2/src2.txt') # => "bbb\n"
File.read('src2.txt') # => "aaa\n"

overwriting if necessary:
If +dest+ is a directory entry, copies from +src+ to dest/src,

File.read('dest1.txt') # => "aaa\n"
Bundler::FileUtils.install('src1.txt', 'dest1.txt')
File.read('dest1.txt') # => "bbb\n"
File.read('src1.txt') # => "aaa\n"

If +dest+ is a file entry, copies from +src+ to +dest+, overwriting:

File.read('dest0.txt') # => "aaa\n"
Bundler::FileUtils.install('src0.txt', 'dest0.txt')
File.exist?('dest0.txt') # => false
File.read('src0.txt') # => "aaa\n"

If the entry at +dest+ does not exist, copies from +src+ to +dest+:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments];
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

See {install(1)}[https://man7.org/linux/man-pages/man1/install.1.html].
Copies a file entry.
def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
            noop: nil, verbose: nil)
  if verbose
    msg = +"install -c"
    msg << ' -p' if preserve
    msg << ' -m ' << mode_to_s(mode) if mode
    msg << " -o #{owner}" if owner
    msg << " -g #{group}" if group
    msg << ' ' << [src,dest].flatten.join(' ')
    fu_output_message msg
  end
  return if noop
  uid = fu_get_uid(owner)
  gid = fu_get_gid(group)
  fu_each_src_dest(src, dest) do |s, d|
    st = File.stat(s)
    unless File.exist?(d) and compare_file(s, d)
      remove_file d, true
      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
    end
  end
end

def link_entry(src, dest, dereference_root = false, remove_destination = false)


Related: Bundler::FileUtils.ln (has different options).

and keyword argument remove_destination: true is not given.
Raises an exception if +dest+ is the path to an existing file or directory

- remove_destination: true - removes +dest+ before creating links.
- dereference_root: true - dereferences +src+ if it is a symbolic link.

Keyword arguments:

File.file?('dest1/dir1/t3.txt') # => true
File.file?('dest1/dir1/t2.txt') # => true
File.file?('dest1/dir0/t1.txt') # => true
File.file?('dest1/dir0/t0.txt') # => true
Bundler::FileUtils.link_entry('src1', 'dest1')
File.directory?('dest1') # => true
Bundler::FileUtils.touch(src_file_paths)
]
'src1/dir1/t3.txt',
'src1/dir1/t2.txt',
'src1/dir0/t1.txt',
'src1/dir0/t0.txt',
src_file_paths = [
Bundler::FileUtils.mkdir_p(['src1/dir0', 'src1/dir1'])

recursively creates hard links at +dest+ pointing to paths in +src+:
If +src+ is the path to a directory and +dest+ does not exist,

File.file?('dest0.txt') # => true
Bundler::FileUtils.link_entry('src0.txt', 'dest0.txt')
File.exist?('dest0.txt') # => false
Bundler::FileUtils.touch('src0.txt')

creates a hard link at +dest+ pointing to +src+:
If +src+ is the path to a file and +dest+ does not exist,

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Arguments +src+ and +dest+

Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+.
def link_entry(src, dest, dereference_root = false, remove_destination = false)
  Entry_.new(src, nil, dereference_root).traverse do |ent|
    destent = Entry_.new(dest, ent.rel, false)
    File.unlink destent.path if remove_destination && File.file?(destent.path)
    ent.link destent.path
  end
end

def ln(src, dest, force: nil, noop: nil, verbose: nil)


Related: Bundler::FileUtils.link_entry (has different options).

and keyword argument +force+ is not +true+.
Raises an exception if +dest+ is the path to an existing file

ln tmp0/t.txt tmp2/t.dat tmp4/
ln tmp2/t.dat tmp3
ln tmp0/t.txt tmp1/t.lnk

Output:

Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/', verbose: true)
Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3', verbose: true)
Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk', verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not create links.
- force: true - overwrites +dest+ if it exists.

Keyword arguments:

Dir.children('tmp4/') # => ["t.dat", "t.txt"]
Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/') # => ["tmp0/t.txt", "tmp2/t.dat"]
Dir.children('tmp4/') # => []

returns +src+:
creates a hard link at dest/target pointing to +target+;
then for each path +target+ in +src+,
and +dest+ is the path to an existing directory,
When +src+ is an array of paths to existing files

Dir.children('tmp3') # => ["t.dat"]
Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0
Dir.children('tmp3') # => []
Dir.children('tmp2') # => ["t.dat"]

creates a hard link at dest/src pointing to +src+; returns zero:
and +dest+ is the path to an existing directory,
When +src+ is the path to an existing file

Dir.children('tmp1/') # => ["t.lnk"]
Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk') # => 0
Dir.children('tmp1/') # => []
Dir.children('tmp0/') # => ["t.txt"]

creates a hard link at +dest+ pointing to +src+; returns zero:
and +dest+ is the path to a non-existent file,
When +src+ is the path to an existing file

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link].
def ln(src, dest, force: nil, noop: nil, verbose: nil)
  fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest0(src, dest) do |s,d|
    remove_file d, true if force
    File.link s, d
  end
end

def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil)


Related: Bundler::FileUtils.ln_sf.

ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3
ln -sf src2.txt dest2.txt
ln -s src1.txt destdir1
ln -s src0.txt dest0.txt

Output:

Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3', noop: true, verbose: true)
Bundler::FileUtils.ln_s('src2.txt', 'dest2.txt', force: true, noop: true, verbose: true)
Bundler::FileUtils.ln_s('src1.txt', 'destdir1', noop: true, verbose: true)
Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not create links.
- relative: false - create links relative to +dest+.
- force: true - overwrites +dest+ if it exists.

Keyword arguments:

File.symlink?('destdir3/src1.txt') # => true
File.symlink?('destdir3/src0.txt') # => true
Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3')
Bundler::FileUtils.mkdir('destdir3')
Bundler::FileUtils.touch('srcdir3/src1.txt')
Bundler::FileUtils.touch('srcdir3/src0.txt')
Bundler::FileUtils.mkdir('srcdir3')

pointing to +child+:
for each child +child+ in +src+ creates a symbolic link dest/child
If +src+ is an array of paths to existing files and +dest+ is a directory,

File.symlink?('destdir2/src2.txt') # => true
Bundler::FileUtils.ln_s('src2.txt', 'destdir2')
Bundler::FileUtils.mkdir('destdir2')
Bundler::FileUtils.touch('src2.txt')

creates a symbolic link at dest/src pointing to +src+:
If +dest+ is the path to a directory,

Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST.

FileTest.symlink?('dest1.txt') # => true
Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt', force: true)
Bundler::FileUtils.touch('dest1.txt')
Bundler::FileUtils.touch('src1.txt')

(raises an exception otherwise):
if and only if keyword argument force: true is given
creates a symbolic link at +dest+ pointing to +src+
- When +dest+ is the path to an existing file,

File.symlink?('dest0.txt') # => true
Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt')
File.exist?('dest0.txt') # => false
Bundler::FileUtils.touch('src0.txt')

creates a symbolic link at +dest+ pointing to +src+:
- When +dest+ is the path to a non-existent file,

If +src+ is the path to an existing file:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

Creates {symbolic links}[https://en.wikipedia.org/wiki/Symbolic_link].
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|
    remove_file d, true if force
    File.symlink s, d
  end
end

def ln_sf(src, dest, noop: nil, verbose: nil)


Like Bundler::FileUtils.ln_s, but always with keyword argument force: true given.
def ln_sf(src, dest, noop: nil, verbose: nil)
  ln_s src, dest, force: true, noop: noop, verbose: verbose
end

def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)


Like Bundler::FileUtils.ln_s, but create links relative to +dest+.
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

def mkdir(list, mode: nil, noop: nil, verbose: nil)


Related: Bundler::FileUtils.mkdir_p.

file or directory, or if for any reason a directory cannot be created.
Raises an exception if any path points to an existing

mkdir -m 700 tmp2 tmp3
mkdir tmp0 tmp1

Output:

Bundler::FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true)
Bundler::FileUtils.mkdir(%w[tmp0 tmp1], verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not create directories.
see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
- mode: mode - also calls File.chmod(mode, path);

Keyword arguments:

Bundler::FileUtils.mkdir('tmp4') # => ["tmp4"]
Bundler::FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"]

see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]:
by calling: Dir.mkdir(path, mode);
With no keyword arguments, creates a directory at each +path+ in +list+

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

returns +list+ if it is an array, [list] otherwise.
(a single path or an array of paths);
Creates directories at the paths in the given +list+
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
  return if noop
  list.each do |dir|
    fu_mkdir dir, mode
  end
end

def mkdir_p(list, mode: nil, noop: nil, verbose: nil)


Related: Bundler::FileUtils.mkdir.

Bundler::FileUtils.mkpath and Bundler::FileUtils.makedirs are aliases for Bundler::FileUtils.mkdir_p.

Raises an exception if for any reason a directory cannot be created.

mkdir -p -m 700 tmp2 tmp3
mkdir -p tmp0 tmp1

Output:

Bundler::FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true)
Bundler::FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not create directories.
see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
- mode: mode - also calls File.chmod(mode, path);

Keyword arguments:

Bundler::FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"]
Bundler::FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]

see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]:
by calling: Dir.mkdir(path, mode);
along with any needed ancestor directories,
With no keyword arguments, creates a directory at each +path+ in +list+,

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

returns +list+ if it is an array, [list] otherwise.
also creating ancestor directories as needed;
(a single path or an array of paths),
Creates directories at the paths in the given +list+
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.each do |item|
    path = remove_trailing_slash(item)
    stack = []
    until File.directory?(path) || File.dirname(path) == path
      stack.push path
      path = File.dirname(path)
    end
    stack.reverse_each do |dir|
      begin
        fu_mkdir dir, mode
      rescue SystemCallError
        raise unless File.directory?(dir)
      end
    end
  end
  return *list
end

def mode_to_s(mode) #:nodoc:

:nodoc:
def mode_to_s(mode)  #:nodoc:
  mode.is_a?(String) ? mode : "%o" % mode
end

def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)


mv src1.txt src1 dest1
mv src0 dest0

Output:

Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1', noop: true, verbose: true)
Bundler::FileUtils.mv('src0', 'dest0', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
see details at Bundler::FileUtils.remove_entry_secure.
- secure: true - removes +src+ securely;
- noop: true - does not move files.
ignores raised exceptions of StandardError and its descendants.
(that is, if +src+ and +dest+ are on different file systems),
- force: true - if the move includes removing +src+

Keyword arguments:

# `-- src1.txt
# | `-- src.txt
# | |-- src.dat
# |-- src1
# => dest1
tree('dest1')
Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1')
Dir.empty?('dest1') # => true
# `-- src.txt
# |-- src.dat
# => src1
tree('src1')
File.file?('src1.txt') # => true

copies from each path in the array to +dest+:
and +dest+ is the path to a directory,
If +src+ is an array of paths to files and directories

# `-- src1.txt
# |-- src0.txt
# => dest0
tree('dest0')
File.exist?('src0') # => false
Bundler::FileUtils.mv('src0', 'dest0')
File.exist?('dest0') # => false
# `-- src1.txt
# |-- src0.txt
# => src0
tree('src0')

moves +src+ to +dest+:
If +src+ is the path to a single file or directory and +dest+ does not exist,

see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
secure: true;
May cause a local vulnerability if not called with keyword argument

first copies, then removes +src+.
If +src+ and +dest+ are on different file systems,

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
and +dest+ (a single path)
Arguments +src+ (a single path or an array of paths)

Moves entries.
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
  fu_each_src_dest(src, dest) do |s, d|
    destent = Entry_.new(d, nil, true)
    begin
      if destent.exist?
        if destent.directory?
          raise Errno::EEXIST, d
        end
      end
      begin
        File.rename s, d
      rescue Errno::EXDEV,
             Errno::EPERM # move from unencrypted to encrypted dir (ext4)
        copy_entry s, d, true
        if secure
          remove_entry_secure s, force
        else
          remove_entry s, force
        end
      end
    rescue SystemCallError
      raise unless force
    end
  end
end

def pwd


Related: Bundler::FileUtils.cd.

Bundler::FileUtils.pwd # => "/rdoc/fileutils"

Returns a string containing the path to the current directory:
def pwd
  Dir.pwd
end

def remove_dir(path, force = false)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

raised exceptions of StandardError and its descendants.
Optional argument +force+ specifies whether to ignore

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
Argument +path+

or a directory.
which should be the entry for a regular file, a symbolic link,
Recursively removes the directory entry given by +path+,
def remove_dir(path, force = false)
  remove_entry path, force   # FIXME?? check if it is a directory
end

def remove_entry(path, force = false)


Related: Bundler::FileUtils.remove_entry_secure.

raised exceptions of StandardError and its descendants.
Optional argument +force+ specifies whether to ignore

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
Argument +path+

or a directory.
which should be the entry for a regular file, a symbolic link,
Removes the entry given by +path+,
def remove_entry(path, force = false)
  Entry_.new(path).postorder_traverse do |ent|
    begin
      ent.remove
    rescue
      raise unless force
    end
  end
rescue
  raise unless force
end

def remove_entry_secure(path, force = false)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

raised exceptions of StandardError and its descendants.
Optional argument +force+ specifies whether to ignore

see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
Avoids a local vulnerability that can exist in certain circumstances;

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
Argument +path+

or a directory.
which should be the entry for a regular file, a symbolic link,
Securely removes the entry given by +path+,
def remove_entry_secure(path, force = false)
  unless fu_have_symlink?
    remove_entry path, force
    return
  end
  fullpath = File.expand_path(path)
  st = File.lstat(fullpath)
  unless st.directory?
    File.unlink fullpath
    return
  end
  # is a directory.
  parent_st = File.stat(File.dirname(fullpath))
  unless parent_st.world_writable?
    remove_entry path, force
    return
  end
  unless parent_st.sticky?
    raise ArgumentError, "parent directory is world writable, Bundler::FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
  end
  # freeze tree root
  euid = Process.euid
  dot_file = fullpath + "/."
  begin
    File.open(dot_file) {|f|
      unless fu_stat_identical_entry?(st, f.stat)
        # symlink (TOC-to-TOU attack?)
        File.unlink fullpath
        return
      end
      f.chown euid, -1
      f.chmod 0700
    }
  rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs
    File.lstat(dot_file).tap {|fstat|
      unless fu_stat_identical_entry?(st, fstat)
        # symlink (TOC-to-TOU attack?)
        File.unlink fullpath
        return
      end
      File.chown euid, -1, dot_file
      File.chmod 0700, dot_file
    }
  end
  unless fu_stat_identical_entry?(st, File.lstat(fullpath))
    # TOC-to-TOU attack?
    File.unlink fullpath
    return
  end
  # ---- tree root is frozen ----
  root = Entry_.new(path)
  root.preorder_traverse do |ent|
    if ent.directory?
      ent.chown euid, -1
      ent.chmod 0700
    end
  end
  root.postorder_traverse do |ent|
    begin
      ent.remove
    rescue
      raise unless force
    end
  end
rescue
  raise unless force
end

def remove_file(path, force = false)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

raised exceptions of StandardError and its descendants.
Optional argument +force+ specifies whether to ignore

should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
Argument +path+

which should be the entry for a regular file or a symbolic link.
Removes the file entry given by +path+,
def remove_file(path, force = false)
  Entry_.new(path).remove_file
rescue
  raise unless force
end

def remove_trailing_slash(dir) #:nodoc:

:nodoc:
def remove_trailing_slash(dir)   #:nodoc:
  dir == '/' ? dir : dir.chomp(?/)
end

def rm(list, force: nil, noop: nil, verbose: nil)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

rm src0.dat src0.txt

Output:

Bundler::FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not remove files; returns +nil+.
and its descendants.
- force: true - ignores raised exceptions of StandardError

Keyword arguments:

Bundler::FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"]
Bundler::FileUtils.touch(['src0.txt', 'src0.dat'])

With no keyword arguments, removes files at the paths given in +list+:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

returns +list+, if it is an array, [list] otherwise.
(a single path or an array of paths)
Removes entries at the paths in the given +list+
def rm(list, force: nil, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
  return if noop
  list.each do |path|
    remove_file path, force
  end
end

def rm_f(list, noop: nil, verbose: nil)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

See Bundler::FileUtils.rm for keyword arguments.

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ (a single path or an array of paths)

Bundler::FileUtils.rm(list, force: true, **kwargs)

Equivalent to:
def rm_f(list, noop: nil, verbose: nil)
  rm list, force: true, noop: noop, verbose: verbose
end

def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

rm -r src1
rm -r src0.dat src0.txt

Output:

Bundler::FileUtils.rm_r('src1', noop: true, verbose: true)
Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true)

- verbose: true - prints an equivalent command:
see details at Bundler::FileUtils.remove_entry_secure.
- secure: true - removes +src+ securely;
- noop: true - does not remove entries; returns +nil+.
and its descendants.
- force: true - ignores raised exceptions of StandardError

Keyword arguments:

File.exist?('src1') # => false
Bundler::FileUtils.rm_r('src1')
# `-- src3.txt
# |-- src2.txt
# `-- dir1
# | `-- src1.txt
# | |-- src0.txt
# |-- dir0
# => src1
tree('src1')

For each directory path, recursively removes files and directories:

File.exist?('src0.dat') # => false
File.exist?('src0.txt') # => false
Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt'])
Bundler::FileUtils.touch(['src0.txt', 'src0.dat'])

For each file path, removes the file at that path:

see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
secure: true;
May cause a local vulnerability if not called with keyword argument

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

returns +list+, if it is an array, [list] otherwise.
(a single path or an array of paths);
Removes entries at the paths in the given +list+
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
  return if noop
  list.each do |path|
    if secure
      remove_entry_secure path, force
    else
      remove_entry path, force
    end
  end
end

def rm_rf(list, noop: nil, verbose: nil, secure: nil)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

See Bundler::FileUtils.rm_r for keyword arguments.

see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
secure: true;
May cause a local vulnerability if not called with keyword argument

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

Bundler::FileUtils.rm_r(list, force: true, **kwargs)

Equivalent to:
def rm_rf(list, noop: nil, verbose: nil, secure: nil)
  rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
end

def rmdir(list, parents: nil, noop: nil, verbose: nil)


Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].

or if for any reason a directory cannot be removed.
Raises an exception if a directory does not exist

rmdir -p tmp4/tmp5
rmdir -p tmp0/tmp1 tmp2/tmp3

Output:

Bundler::FileUtils.rmdir('tmp4/tmp5', parents: true, verbose: true)
Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3], parents: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not remove directories.
if empty.
- parents: true - removes successive ancestor directories

Keyword arguments:

Bundler::FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"]
Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]

see {Dir.rmdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-rmdir]:
by calling: Dir.rmdir(path);
With no keyword arguments, removes the directory at each +path+ in +list+,

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

returns +list+, if it is an array, [list] otherwise.
(a single path or an array of paths);
Removes directories at the paths in the given +list+
def rmdir(list, parents: nil, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
  return if noop
  list.each do |dir|
    Dir.rmdir(dir = remove_trailing_slash(dir))
    if parents
      begin
        until (parent = File.dirname(dir)) == '.' or parent == dir
          dir = parent
          Dir.rmdir(dir)
        end
      rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
      end
    end
  end
end

def symbolic_modes_to_i(mode_sym, path) #:nodoc:

:nodoc:
def symbolic_modes_to_i(mode_sym, path)  #:nodoc:
  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?
    target = 'a' if target.empty?
    user_mask = user_mask(target)
    actions.each_slice(2) do |op, perm|
      need_apply = op == '='
      mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
        case chr
        when "r"
          mask | 0444
        when "w"
          mask | 0222
        when "x"
          mask | 0111
        when "X"
          if path.directory?
            mask | 0111
          else
            mask
          end
        when "s"
          mask | 06000
        when "t"
          mask | 01000
        when "u", "g", "o"
          if mask.nonzero?
            current_mode = apply_mask(current_mode, user_mask, op, mask)
          end
          need_apply = false
          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}"
        end
      end
      if mode_mask.nonzero? || need_apply
        current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
      end
    end
    current_mode
  end
end

def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)


Related: Bundler::FileUtils.uptodate?.

touch src0.txt
touch src0.txt src0.dat
touch src0.txt

Output:

Bundler::FileUtils.touch(path, noop: true, verbose: true)
Bundler::FileUtils.touch(['src0.txt', 'src0.dat'], noop: true, verbose: true)
Bundler::FileUtils.touch('src0.txt', noop: true, verbose: true)

- verbose: true - prints an equivalent command:
- noop: true - does not touch entries; returns +nil+.
- nocreate: true - raises an exception if the entry does not exist.
instead of the current time.
- mtime: time - sets the entry's mtime to the given time,

Keyword arguments:

Bundler::FileUtils.touch(['src0.txt', 'src0.dat'])
# Array of paths.

f.mtime # => 2022-06-11 08:28:09.8185343 -0700
f.atime # => 2022-06-11 08:28:09.8185343 -0700
f = File.new('src0.txt')
Bundler::FileUtils.touch('src0.txt')
f.mtime # => 2022-06-10 11:11:21.200277 -0700
f.atime # => 2022-06-10 11:11:21.200277 -0700
f = File.new('src0.txt') # Existing file.
# Single path.

Examples:

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
Argument +list+ or its elements

use keyword argument +nocreate+ to raise an exception instead.
By default, creates an empty file for any path to a non-existent entry;

returns +list+ if it is an array, [list] otherwise.
(a single path or an array of paths);
of the entries given by the paths in +list+
Updates modification times (mtime) and access times (atime)
def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
  list = fu_list(list)
  t = mtime
  if verbose
    fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
  end
  return if noop
  list.each do |path|
    created = nocreate
    begin
      File.utime(t, t, path)
    rescue Errno::ENOENT
      raise if created
      File.open(path, 'a') {
        ;
      }
      created = true
      retry if t
    end
  end
end

def uptodate?(new, old_list)


Related: Bundler::FileUtils.touch.

A non-existent file is considered to be infinitely old.

Bundler::FileUtils.uptodate?('Gemfile', ['Rakefile', 'README.md']) # => false
Bundler::FileUtils.uptodate?('Rakefile', ['Gemfile', 'README.md']) # => true

should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]:
Argument +new+ and the elements of +old_list+

+false+ otherwise.
is newer than all the files at paths in array +old_list+;
Returns +true+ if the file at path +new+
def uptodate?(new, old_list)
  return false unless File.exist?(new)
  new_time = File.mtime(new)
  old_list.each do |old|
    if File.exist?(old)
      return false unless new_time > File.mtime(old)
    end
  end
  true
end

def user_mask(target) #:nodoc:

:nodoc:
def user_mask(target)  #:nodoc:
  target.each_char.inject(0) do |mask, chr|
    case chr
    when "u"
      mask | 04700
    when "g"
      mask | 02070
    when "o"
      mask | 01007
    when "a"
      mask | 07777
    else
      raise ArgumentError, "invalid 'who' symbol in file mode: #{chr}"
    end
  end
end