# Copyright (c) 2000-2006 Minero Aoki
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# Namespace for several file utility methods for copying, moving, removing, etc.
# cd(dir, options) {|dir| .... }
# ln(list, destdir, options)
# ln_s(old, new, options)
# ln_s(list, destdir, options)
# ln_sf(src, dest, options)
# cp_r(src, dest, options)
# cp_r(list, dir, options)
# install(src, dest, mode = <src's>, options)
# chmod(mode, list, options)
# chmod_R(mode, list, options)
# chown(user, group, list, options)
# chown_R(user, group, list, options)
# The <tt>options</tt> parameter is a hash of options, taken from the list
# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
# <tt>:noop</tt> means that no changes are made. The other two are obvious.
# Each method documents the options that it honours.
# 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.
# There are some `low level' methods, which do not accept any option:
# copy_entry(src, dest, preserve = false, dereference = false)
# copy_file(src, dest, preserve = false, dereference = true)
# copy_stream(srcstream, deststream)
# remove_entry(path, force = false)
# remove_entry_secure(path, force = false)
# remove_file(path, force = false)
# compare_file(path_a, path_b)
# compare_stream(stream_a, stream_b)
# uptodate?(file, cmp_list)
# == module FileUtils::Verbose
# This module has all methods of FileUtils module, but it outputs messages
# before acting. This equates to passing the <tt>:verbose</tt> flag to methods
# == module FileUtils::NoWrite
# This module has all methods of FileUtils module, but never changes
# files/directories. This equates to passing the <tt>:noop</tt> flag to methods
# == module FileUtils::DryRun
# This module has all methods of FileUtils module, but never changes
# files/directories. This equates to passing the <tt>:noop</tt> and
# <tt>:verbose</tt> flags to methods in FileUtils.
def self.private_module_function(name) #:nodoc:
private_class_method name
# This hash table holds command options.
OPT_TABLE = {} #:nodoc: internal use only
# Returns the name of the current directory.
# Changes the current directory to the directory +dir+.
# If this method is called with block, resumes to the old
# working directory after the block execution finished.
# FileUtils.cd('/', :verbose => true) # chdir and report it
def cd(dir, options = {}, &block) # :yield: dir
fu_check_options options, OPT_TABLE['cd']
fu_output_message "cd #{dir}" if options[:verbose]
fu_output_message 'cd -' if options[:verbose] and block
OPT_TABLE['chdir'] = [:verbose]
# Returns true if +newer+ is newer than all +old_list+.
# Non-existent files are older than any file.
# FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
def uptodate?(new, old_list, options = nil)
raise ArgumentError, 'uptodate? does not accept any option' if options
return false unless File.exist?(new)
new_time = File.mtime(new)
return false unless new_time > File.mtime(old)
module_function :uptodate?
# Options: mode noop verbose
# Creates one or more directories.
# FileUtils.mkdir %w( tmp data )
# FileUtils.mkdir 'notexist', :noop => true # Does not really create.
# FileUtils.mkdir 'tmp', :mode => 0700
def mkdir(list, options = {})
fu_check_options options, OPT_TABLE['mkdir']
fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
fu_mkdir dir, options[:mode]
OPT_TABLE['mkdir'] = [:mode, :noop, :verbose]
# Options: mode noop verbose
# Creates a directory and all its parent directories.
# FileUtils.mkdir_p '/usr/local/lib/ruby'
# causes to make following directories, if it does not exist.
# You can pass several directories at a time in a list.
def mkdir_p(list, options = {})
fu_check_options options, OPT_TABLE['mkdir_p']
fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
return *list if options[:noop]
list.map {|path| path.sub(%r</\z>, '') }.each do |path|
# optimize for the most common case
fu_mkdir path, options[:mode]
next if File.directory?(path)
until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
path = File.dirname(path)
stack.reverse_each do |path|
fu_mkdir path, options[:mode]
rescue SystemCallError => err
raise unless File.directory?(path)
module_function :makedirs
OPT_TABLE['makedirs'] = [:mode, :noop, :verbose]
def fu_mkdir(path, mode) #:nodoc:
path = path.sub(%r</\z>, '')
private_module_function :fu_mkdir
# Removes one or more directories.
# FileUtils.rmdir 'somedir'
# FileUtils.rmdir %w(somedir anydir otherdir)
# # Does not really remove directory; outputs message.
# FileUtils.rmdir 'somedir', :verbose => true, :noop => true
def rmdir(list, options = {})
fu_check_options options, OPT_TABLE['rmdir']
fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
Dir.rmdir dir.sub(%r</\z>, '')
OPT_TABLE['rmdir'] = [:noop, :verbose]
# Options: force noop verbose
# <b><tt>ln(old, new, options = {})</tt></b>
# Creates a hard link +new+ which points to +old+.
# If +new+ already exists and it is a directory, creates a link +new/old+.
# If +new+ already exists and it is not a directory, raises Errno::EEXIST.
# But if :force option is set, overwrite +new+.
# FileUtils.ln 'gcc', 'cc', :verbose => true
# FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
# <b><tt>ln(list, destdir, options = {})</tt></b>
# Creates several hard links in a directory, with each one pointing to the
# item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR.
# FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
def ln(src, dest, options = {})
fu_check_options options, OPT_TABLE['ln']
fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if options[:force]
OPT_TABLE['link'] = [:force, :noop, :verbose]
# Options: force noop verbose
# <b><tt>ln_s(old, new, options = {})</tt></b>
# Creates a symbolic link +new+ which points to +old+. If +new+ already
# exists and it is a directory, creates a symbolic link +new/old+. If +new+
# already exists and it is not a directory, raises Errno::EEXIST. But if
# :force option is set, overwrite +new+.
# FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
# FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
# <b><tt>ln_s(list, destdir, options = {})</tt></b>
# Creates several symbolic links in a directory, with each one pointing to the
# item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR.
# If +destdir+ is not a directory, raises Errno::ENOTDIR.
# FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
def ln_s(src, dest, options = {})
fu_check_options options, OPT_TABLE['ln_s']
fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if options[:force]
OPT_TABLE['symlink'] = [:force, :noop, :verbose]
# #ln_s(src, dest, :force)
def ln_sf(src, dest, options = {})
fu_check_options options, OPT_TABLE['ln_sf']
OPT_TABLE['ln_sf'] = [:noop, :verbose]
# Options: preserve noop verbose
# Copies a file content +src+ to +dest+. If +dest+ is a directory,
# copies +src+ to +dest/src+.
# If +src+ is a list of files, then +dest+ must be a directory.
# FileUtils.cp 'eval.c', 'eval.c.org'
# FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
# FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
# FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
def cp(src, dest, options = {})
fu_check_options options, OPT_TABLE['cp']
fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
fu_each_src_dest(src, dest) do |s, d|
copy_file s, d, options[:preserve]
OPT_TABLE['copy'] = [:preserve, :noop, :verbose]
# Options: preserve noop verbose dereference_root remove_destination
# Copies +src+ to +dest+. If +src+ is a directory, this method copies
# all its contents recursively. If +dest+ is a directory, copies
# +src+ can be a list of files.
# # Installing ruby library "mylib" under the site_ruby
# FileUtils.rm_r site_ruby + '/mylib', :force
# FileUtils.cp_r 'lib/', site_ruby + '/mylib'
# # Examples of copying several files to target directory.
# FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
# FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/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,
# FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest,
def cp_r(src, dest, options = {})
fu_check_options options, OPT_TABLE['cp_r']
fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
options[:dereference_root] = true unless options.key?(:dereference_root)
fu_each_src_dest(src, dest) do |s, d|
copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
:dereference_root, :remove_destination]
# 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)
# Both of +src+ and +dest+ must be a path name.
# +src+ must exist, +dest+ must not exist.
# If +preserve+ is true, this method preserves owner, group, permissions
# If +dereference_root+ is true, this method dereference tree root.
# If +remove_destination+ is true, this method removes each destination file before copy.
def copy_entry(src, dest, preserve = false, 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.copy_metadata destent.path if preserve
module_function :copy_entry
# Copies file contents of +src+ to +dest+.
# Both of +src+ and +dest+ must be a path name.
def copy_file(src, dest, preserve = false, dereference = true)
ent = Entry_.new(src, nil, dereference)
ent.copy_metadata dest if preserve
module_function :copy_file
# Copies stream +src+ to +dest+.
# +src+ must respond to #read(n) and
# +dest+ must respond to #write(str).
def copy_stream(src, dest)
fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
module_function :copy_stream
# Options: force noop verbose
# Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
# disk partition, the file is copied instead.
# FileUtils.mv 'badname.rb', 'goodname.rb'
# FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
# FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
# FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
def mv(src, dest, options = {})
fu_check_options options, OPT_TABLE['mv']
fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
fu_each_src_dest(src, dest) do |s, d|
destent = Entry_.new(d, nil, true)
raise Errno::EEXIST, dest