diff options
Diffstat (limited to 'lib/fileutils.rb')
| -rw-r--r-- | lib/fileutils.rb | 1114 |
1 files changed, 588 insertions, 526 deletions
diff --git a/lib/fileutils.rb b/lib/fileutils.rb index 043a24d2b5..f56d7f9cb9 100644 --- a/lib/fileutils.rb +++ b/lib/fileutils.rb @@ -1,50 +1,53 @@ -# +# frozen_string_literal: true +# # = fileutils.rb -# -# Copyright (c) 2000-2005 Minero Aoki <aamine@loveruby.net> -# +# +# Copyright (c) 2000-2007 Minero Aoki +# # This program is free software. # You can distribute/modify this program under the same terms of ruby. -# +# # == module FileUtils -# +# # Namespace for several file utility methods for copying, moving, removing, etc. -# +# # === Module Functions -# -# cd(dir, options) -# cd(dir, options) {|dir| .... } -# pwd() -# mkdir(dir, options) -# mkdir(list, options) -# mkdir_p(dir, options) -# mkdir_p(list, options) -# rmdir(dir, options) -# rmdir(list, options) -# ln(old, new, options) -# ln(list, destdir, options) -# ln_s(old, new, options) -# ln_s(list, destdir, options) -# ln_sf(src, dest, options) -# cp(src, dest, options) -# cp(list, dir, options) -# cp_r(src, dest, options) -# cp_r(list, dir, options) -# mv(src, dest, options) -# mv(list, dir, options) -# rm(list, options) -# rm_r(list, options) -# rm_rf(list, 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) -# touch(list, options) +# +# require 'fileutils' +# +# FileUtils.cd(dir, options) +# FileUtils.cd(dir, options) {|dir| block } +# FileUtils.pwd() +# FileUtils.mkdir(dir, options) +# FileUtils.mkdir(list, options) +# FileUtils.mkdir_p(dir, options) +# FileUtils.mkdir_p(list, options) +# FileUtils.rmdir(dir, options) +# FileUtils.rmdir(list, options) +# FileUtils.ln(target, link, options) +# FileUtils.ln(targets, dir, options) +# FileUtils.ln_s(target, link, options) +# FileUtils.ln_s(targets, dir, options) +# FileUtils.ln_sf(target, link, options) +# FileUtils.cp(src, dest, options) +# FileUtils.cp(list, dir, options) +# FileUtils.cp_r(src, dest, options) +# FileUtils.cp_r(list, dir, options) +# FileUtils.mv(src, dest, options) +# FileUtils.mv(list, dir, options) +# FileUtils.rm(list, options) +# FileUtils.rm_r(list, options) +# FileUtils.rm_rf(list, options) +# FileUtils.install(src, dest, options) +# FileUtils.chmod(mode, list, options) +# FileUtils.chmod_R(mode, list, options) +# FileUtils.chown(user, group, list, options) +# FileUtils.chown_R(user, group, list, options) +# FileUtils.touch(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. +# <tt>:noop</tt> means that no changes are made. The other three are obvious. # Each method documents the options that it honours. # # All methods that have the concept of a "source" file or directory can take @@ -53,47 +56,44 @@ # # 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) +# FileUtils.copy_entry(src, dest, preserve = false, dereference = false) +# FileUtils.copy_file(src, dest, preserve = false, dereference = true) +# FileUtils.copy_stream(srcstream, deststream) +# FileUtils.remove_entry(path, force = false) +# FileUtils.remove_entry_secure(path, force = false) +# FileUtils.remove_file(path, force = false) +# FileUtils.compare_file(path_a, path_b) +# FileUtils.compare_stream(stream_a, stream_b) +# FileUtils.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 # in FileUtils. -# +# # == 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 # in FileUtils. -# +# # == 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. -# +# module FileUtils + VERSION = "1.0.2" + def self.private_module_function(name) #:nodoc: module_function name private_class_method name end - # This hash table holds command options. - OPT_TABLE = {} #:nodoc: internal use only - - # - # Options: (none) # # Returns the name of the current directory. # @@ -106,41 +106,35 @@ module FileUtils module_function :getwd # - # Options: verbose - # # 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, :verbose - fu_output_message "cd #{dir}" if options[:verbose] + # + # FileUtils.cd('/') do # chdir + # # ... # do something + # end # return to original directory + # + def cd(dir, verbose: nil, &block) # :yield: dir + fu_output_message "cd #{dir}" if verbose Dir.chdir(dir, &block) - fu_output_message 'cd -' if options[:verbose] and block + fu_output_message 'cd -' if verbose and block end module_function :cd alias chdir cd module_function :chdir - OPT_TABLE['cd'] = - OPT_TABLE['chdir'] = %w( verbose ) - # - # Options: (none) - # - # Returns true if +newer+ is newer than all +old_list+. + # Returns true if +new+ is newer than all +old_list+. # Non-existent files are older than any file. - # + # # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ # system 'make hello.o' - # - def uptodate?(new, old_list, options = nil) - raise ArgumentError, 'uptodate? does not accept any option' if options - + # + def uptodate?(new, old_list) return false unless File.exist?(new) new_time = File.mtime(new) old_list.each do |old| @@ -152,56 +146,54 @@ module FileUtils end module_function :uptodate? + def remove_trailing_slash(dir) #:nodoc: + dir == '/' ? dir : dir.chomp(?/) + end + private_module_function :remove_trailing_slash + # - # Options: mode noop verbose - # # Creates one or more directories. - # + # # FileUtils.mkdir 'test' # 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, :mode, :noop, :verbose + # + def mkdir(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) - fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] - return if options[:noop] + fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose + return if noop list.each do |dir| - fu_mkdir dir, options[:mode] + fu_mkdir dir, mode end end module_function :mkdir - OPT_TABLE['mkdir'] = %w( noop verbose mode ) - # - # Options: mode noop verbose - # # Creates a directory and all its parent directories. # For example, - # + # # FileUtils.mkdir_p '/usr/local/lib/ruby' - # + # # causes to make following directories, if it does not exist. - # * /usr - # * /usr/local - # * /usr/local/lib - # * /usr/local/lib/ruby + # + # * /usr + # * /usr/local + # * /usr/local/lib + # * /usr/local/lib/ruby # # You can pass several directories at a time in a list. - # - def mkdir_p(list, options = {}) - fu_check_options options, :mode, :noop, :verbose + # + def mkdir_p(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) - fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] - return *list if options[:noop] + fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose + return *list if noop - list.map {|path| path.sub(%r</\z>, '') }.each do |path| + list.map {|path| remove_trailing_slash(path)}.each do |path| # optimize for the most common case begin - fu_mkdir path, options[:mode] + fu_mkdir path, mode next rescue SystemCallError next if File.directory?(path) @@ -212,11 +204,12 @@ module FileUtils stack.push path path = File.dirname(path) end - stack.reverse_each do |path| + stack.pop # root directory should exist + stack.reverse_each do |dir| begin - fu_mkdir path, options[:mode] - rescue SystemCallError => err - raise unless File.directory?(path) + fu_mkdir dir, mode + rescue SystemCallError + raise unless File.directory?(dir) end end end @@ -230,12 +223,8 @@ module FileUtils module_function :mkpath module_function :makedirs - OPT_TABLE['mkdir_p'] = - OPT_TABLE['mkpath'] = - OPT_TABLE['makedirs'] = %w( noop verbose ) - def fu_mkdir(path, mode) #:nodoc: - path = path.sub(%r</\z>, '') + path = remove_trailing_slash(path) if mode Dir.mkdir path, mode File.chmod mode, path @@ -246,56 +235,58 @@ module FileUtils private_module_function :fu_mkdir # - # Options: noop, verbose - # # 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, :noop, :verbose + # + def rmdir(list, parents: nil, noop: nil, verbose: nil) list = fu_list(list) - fu_output_message "rmdir #{list.join ' '}" if options[:verbose] - return if options[:noop] + fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose + return if noop list.each do |dir| - Dir.rmdir dir.sub(%r</\z>, '') + 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 module_function :rmdir - OPT_TABLE['rmdir'] = %w( noop verbose ) - # - # Options: force noop verbose + # :call-seq: + # FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil) + # FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil) + # FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil) # - # <b><tt>ln(old, new, options = {})</tt></b> + # 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+. # - # 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 '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. - # - # include FileUtils - # cd '/sbin' + # + # 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. + # + # FileUtils.cd '/sbin' # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. - # - def ln(src, dest, options = {}) - fu_check_options options, :force, :noop, :verbose - fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] - return if options[:noop] + # + 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 options[:force] + remove_file d, true if force File.link s, d end end @@ -304,37 +295,31 @@ module FileUtils alias link ln module_function :link - OPT_TABLE['ln'] = - OPT_TABLE['link'] = %w( noop verbose force ) - # - # Options: force noop verbose + # :call-seq: + # FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil) + # FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil) + # FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil) + # + # In the first form, creates a symbolic link +link+ which points to +target+. + # If +link+ already exists, raises Errno::EEXIST. + # But if the :force option is set, overwrites +link+. # - # <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, :force, :noop, :verbose - fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] - return if options[:noop] + # FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true + # + # 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. + # + # FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin' + # + def ln_s(src, dest, force: nil, noop: nil, verbose: nil) + 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 options[:force] + remove_file d, true if force File.symlink s, d end end @@ -343,27 +328,19 @@ module FileUtils alias symlink ln_s module_function :symlink - OPT_TABLE['ln_s'] = - OPT_TABLE['symlink'] = %w( noop verbose force ) - # - # Options: noop verbose - # + # :call-seq: + # FileUtils.ln_sf(*args) + # # Same as - # #ln_s(src, dest, :force) - # - def ln_sf(src, dest, options = {}) - fu_check_options options, :noop, :verbose - options = options.dup - options[:force] = true - ln_s src, dest, options + # + # FileUtils.ln_s(*args, force: true) + # + def ln_sf(src, dest, noop: nil, verbose: nil) + ln_s src, dest, force: true, noop: noop, verbose: verbose end module_function :ln_sf - OPT_TABLE['ln_sf'] = %w( noop verbose ) - - # - # Options: preserve noop verbose # # Copies a file content +src+ to +dest+. If +dest+ is a directory, # copies +src+ to +dest/src+. @@ -374,13 +351,12 @@ module FileUtils # 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, :preserve, :noop, :verbose - fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] - return if options[:noop] + # + 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, options[:preserve] + copy_file s, d, preserve end end module_function :cp @@ -388,45 +364,37 @@ module FileUtils alias copy cp module_function :copy - OPT_TABLE['cp'] = - OPT_TABLE['copy'] = %w( noop verbose preserve ) - # - # Options: preserve noop verbose dereference_root - # # 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. - # - # # Installing ruby library "mylib" under the site_ruby + # + # # 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 + # 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. - # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest, + # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, # # but this doesn't. - # - def cp_r(src, dest, options = {}) - fu_check_options options, :preserve, :noop, :verbose, :dereference_root - fu_output_message "cp -r#{options[:preserve] ? 'p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] - return if options[:noop] - options[:dereference_root] = true unless options.key?(:dereference_root) + # + 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, options[:preserve], options[:dereference_root] + copy_entry s, d, preserve, dereference_root, remove_destination end end module_function :cp_r - OPT_TABLE['cp_r'] = %w( noop verbose preserve dereference_root ) - # # Copies a file system entry +src+ to +dest+. # If +src+ is a directory, this method copies its contents recursively. @@ -436,17 +404,22 @@ module FileUtils # 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 - # and modified time. + # If +preserve+ is true, this method preserves owner, group, and + # modified time. Permissions are copied regardless +preserve+. # # If +dereference_root+ is true, this method dereference tree root. # - def copy_entry(src, dest, preserve = false, dereference_root = false) - Entry_.new(src, nil, dereference_root).traverse do |ent| + # 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).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) end module_function :copy_entry @@ -467,32 +440,29 @@ module FileUtils # +dest+ must respond to #write(str). # def copy_stream(src, dest) - fu_copy_stream0 src, dest, fu_stream_blksize(src, dest) + IO.copy_stream(src, dest) end 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. - # + # disk partition, the file is copied then the original file is removed. + # # 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 %w(junk.txt dust.txt), '/home/foo/.trash/' # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true - # - def mv(src, dest, options = {}) - fu_check_options options, :force, :noop, :verbose, :secure - fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] - return if options[:noop] + # + 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, dest + raise Errno::EEXIST, d else destent.remove_file if rename_cannot_overwrite_file? end @@ -501,14 +471,14 @@ module FileUtils File.rename s, d rescue Errno::EXDEV copy_entry s, d, true - if options[:secure] - remove_entry_secure s, options[:force] + if secure + remove_entry_secure s, force else - remove_entry s, options[:force] + remove_entry s, force end end rescue SystemCallError - raise unless options[:force] + raise unless force end end end @@ -517,32 +487,26 @@ module FileUtils alias move mv module_function :move - OPT_TABLE['mv'] = - OPT_TABLE['move'] = %w[force noop verbose secure] - def rename_cannot_overwrite_file? #:nodoc: - /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + /emx/ =~ RUBY_PLATFORM end private_module_function :rename_cannot_overwrite_file? # - # Options: force noop verbose - # # Remove file(s) specified in +list+. This method cannot remove directories. # All StandardErrors are ignored when the :force option is set. - # + # # FileUtils.rm %w( junk.txt dust.txt ) # FileUtils.rm Dir.glob('*.so') # FileUtils.rm 'NotExistFile', :force => true # never raises exception - # - def rm(list, options = {}) - fu_check_options options, :force, :noop, :verbose + # + def rm(list, force: nil, noop: nil, verbose: nil) list = fu_list(list) - fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] - return if options[:noop] + fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose + return if noop list.each do |path| - remove_file path, options[:force] + remove_file path, force end end module_function :rm @@ -550,39 +514,26 @@ module FileUtils alias remove rm module_function :remove - OPT_TABLE['rm'] = - OPT_TABLE['remove'] = %w( noop verbose force ) - # - # Options: noop verbose - # # Equivalent to # - # #rm(list, :force => true) + # FileUtils.rm(list, :force => true) # - def rm_f(list, options = {}) - fu_check_options options, :noop, :verbose - options = options.dup - options[:force] = true - rm list, options + def rm_f(list, noop: nil, verbose: nil) + rm list, force: true, noop: noop, verbose: verbose end module_function :rm_f alias safe_unlink rm_f module_function :safe_unlink - OPT_TABLE['rm_f'] = - OPT_TABLE['safe_unlink'] = %w( noop verbose ) - # - # Options: force noop verbose secure - # # 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. - # + # # FileUtils.rm_r Dir.glob('/tmp/*') - # FileUtils.rm_r '/', :force => true # :-) + # FileUtils.rm_r 'some_dir', :force => true # # WARNING: This method causes local vulnerability # if one of parent directories or removing directory tree are world @@ -594,49 +545,37 @@ module FileUtils # # NOTE: This method calls #remove_entry_secure if :secure option is set. # See also #remove_entry_secure. - # - def rm_r(list, options = {}) - fu_check_options options, :force, :noop, :verbose, :secure - # options[:secure] = true unless options.key?(:secure) + # + def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil) list = fu_list(list) - fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] - return if options[:noop] + fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose + return if noop list.each do |path| - if options[:secure] - remove_entry_secure path, options[:force] + if secure + remove_entry_secure path, force else - remove_entry path, options[:force] + remove_entry path, force end end end module_function :rm_r - OPT_TABLE['rm_r'] = %w( noop verbose force secure ) - # - # Options: noop verbose secure - # # Equivalent to # - # #rm_r(list, :force => true) + # FileUtils.rm_r(list, :force => true) # # WARNING: This method causes local vulnerability. # Read the documentation of #rm_r first. - # - def rm_rf(list, options = {}) - fu_check_options options, :noop, :verbose, :secure - options = options.dup - options[:force] = true - rm_r list, options + # + def rm_rf(list, noop: nil, verbose: nil, secure: nil) + rm_r list, force: true, noop: noop, verbose: verbose, secure: secure end module_function :rm_rf alias rmtree rm_rf module_function :rmtree - OPT_TABLE['rm_rf'] = - OPT_TABLE['rmtree'] = %w( noop verbose secure ) - # # This method removes a file system entry +path+. +path+ shall be a # regular file, a directory, or something. If +path+ is a directory, @@ -644,19 +583,19 @@ module FileUtils # (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. + # * 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). # - # WARNING: You must ensure that *ALL* parent directories are not - # world writable. Otherwise this method does not work. - # Only exception is temporary directory like /tmp and /var/tmp, - # whose permission is 1777. + # 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. # # WARNING: Only the owner of the removing directory tree, or Unix super # user (root) should invoke this method. Otherwise this method does not @@ -664,8 +603,8 @@ module FileUtils # # For details of this security vulnerability, see Perl's case: # - # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 - # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 + # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 + # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 # # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. # @@ -682,7 +621,7 @@ module FileUtils end # is a directory. parent_st = File.stat(File.dirname(fullpath)) - unless fu_world_writable?(parent_st) + unless parent_st.world_writable? remove_entry path, force return end @@ -699,6 +638,11 @@ module FileUtils end f.chown euid, -1 f.chmod 0700 + 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) @@ -720,16 +664,11 @@ module FileUtils end module_function :remove_entry_secure - def fu_world_writable?(st) - (st.mode & 0002) != 0 - end - private_module_function :fu_world_writable? - - def fu_have_symlink? #:nodoc + def fu_have_symlink? #:nodoc: File.symlink nil, nil rescue NotImplementedError return false - rescue + rescue TypeError return true end private_module_function :fu_have_symlink? @@ -780,10 +719,10 @@ module FileUtils module_function :remove_dir # - # Returns true if the contents of a file A and a file B are identical. - # - # FileUtils.compare_file('somefile', 'somefile') #=> true - # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false + # Returns true if the contents of a file +a+ and a file +b+ are identical. + # + # FileUtils.compare_file('somefile', 'somefile') #=> true + # FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false # def compare_file(a, b) return false unless File.size(a) == File.size(b) @@ -805,118 +744,226 @@ module FileUtils # def compare_stream(a, b) bsize = fu_stream_blksize(a, b) - sa = sb = nil - while sa == sb - sa = a.read(bsize) - sb = b.read(bsize) - unless sa and sb - if sa.nil? and sb.nil? - return true - end - end - end + 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 module_function :compare_stream # - # Options: mode noop verbose - # # 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. + # # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true - # - def install(src, dest, options = {}) - fu_check_options options, :mode, :preserve, :noop, :verbose - fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] - return if options[:noop] + # + 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 - st = File.stat(s) if options[:preserve] copy_file s, d - File.utime st.atime, st.mtime, d if options[:preserve] - File.chmod options[:mode], d if options[:mode] + 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 module_function :install - OPT_TABLE['install'] = %w( noop verbose preserve mode ) + 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 + private_module_function :user_mask + + 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 + 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 + 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 FileTest.directory? path + 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 + private_module_function :symbolic_modes_to_i + + def fu_mode(mode, path) #:nodoc: + mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode + end + private_module_function :fu_mode + + def mode_to_s(mode) #:nodoc: + mode.is_a?(String) ? mode : "%o" % mode + end + private_module_function :mode_to_s # - # Options: noop verbose - # # 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 # FileUtils.chmod 0755, 'somecommand' # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true - # - def chmod(mode, list, options = {}) - fu_check_options options, :noop, :verbose + # + # Symbolic mode is + # FileUtils.chmod "u=wrx,go=rx", 'somecommand' + # FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) + # 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 %o %s', mode, list.join(' ')) if options[:verbose] - return if options[:noop] + 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 mode + Entry_.new(path).chmod(fu_mode(mode, path)) end end module_function :chmod - OPT_TABLE['chmod'] = %w( noop verbose ) - # - # Options: noop verbose force - # # Changes permission bits on the named files (in +list+) # to the bit pattern represented by +mode+. - # + # # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" - # - def chmod_R(mode, list, options = {}) - fu_check_options options, :noop, :verbose, :force + # FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" + # + def chmod_R(mode, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) - fu_output_message sprintf('chmod -R%s %o %s', - (options[:force] ? 'f' : ''), - mode, list.join(' ')) if options[:verbose] - return if options[:noop] + 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 mode + ent.chmod(fu_mode(mode, ent.path)) rescue - raise unless options[:force] + raise unless force end end end end module_function :chmod_R - OPT_TABLE['chmod_R'] = %w( noop verbose ) - # - # Options: noop verbose - # # 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. - # + # # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true - # - def chown(user, group, list, options = {}) - fu_check_options options, :noop, :verbose + # + def chown(user, group, list, noop: nil, verbose: nil) list = fu_list(list) - fu_output_message sprintf('chown %s%s', - [user,group].compact.join(':') + ' ', - list.join(' ')) if options[:verbose] - return if options[:noop] + 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| @@ -925,124 +972,109 @@ module FileUtils end module_function :chown - OPT_TABLE['chown'] = %w( noop verbose ) - # - # Options: noop verbose force - # # 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. - # + # # FileUtils.chown_R 'www', 'www', '/var/www/htdocs' # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true - # - def chown_R(user, group, list, options = {}) - fu_check_options options, :noop, :verbose, :force + # + 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', - (options[:force] ? 'f' : ''), - [user,group].compact.join(':') + ' ', - list.join(' ')) if options[:verbose] - return if options[:noop] + 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) - return unless uid or gid list.each do |root| Entry_.new(root).traverse do |ent| begin ent.chown uid, gid rescue - raise unless options[:force] + raise unless force end end end end module_function :chown_R - OPT_TABLE['chown_R'] = %w( noop verbose ) - begin require 'etc' + rescue LoadError # rescue LoadError for miniruby + end - def fu_get_uid(user) #:nodoc: - return nil unless user - user = user.to_s - if /\A\d+\z/ =~ user - then user.to_i - else Etc.getpwnam(user).uid - end - end - private_module_function :fu_get_uid - - def fu_get_gid(group) #:nodoc: - return nil unless group - if /\A\d+\z/ =~ group - then group.to_i - else Etc.getgrnam(group).gid - end - end - private_module_function :fu_get_gid - - rescue LoadError - # need Win32 support??? - - def fu_get_uid(user) #:nodoc: - user # FIXME + def fu_get_uid(user) #:nodoc: + return nil unless user + case user + when Integer + user + when /\A\d+\z/ + user.to_i + else + Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil end - private_module_function :fu_get_uid - - def fu_get_gid(group) #:nodoc: - group # FIXME + end + private_module_function :fu_get_uid + + def fu_get_gid(group) #:nodoc: + return nil unless group + case group + when Integer + group + when /\A\d+\z/ + group.to_i + else + Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil end - private_module_function :fu_get_gid end + private_module_function :fu_get_gid # - # Options: noop verbose - # # Updates modification time (mtime) and access time (atime) of file(s) in # +list+. Files are created if they don't exist. - # + # # FileUtils.touch 'timestamp' # FileUtils.touch Dir.glob('*.c'); system 'make' - # - def touch(list, options = {}) - fu_check_options options, :noop, :verbose + # + def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil) list = fu_list(list) - fu_output_message "touch #{list.join ' '}" if options[:verbose] - return if options[:noop] - t = Time.now + 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 module_function :touch - OPT_TABLE['touch'] = %w( noop verbose ) - private module StreamUtils_ private def fu_windows? - /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM end - def fu_copy_stream0(src, dest, blksize) #:nodoc: - # FIXME: readpartial? - while s = src.read(blksize) - dest.write s - end + def fu_copy_stream0(src, dest, blksize = nil) #:nodoc: + IO.copy_stream(src, dest) end def fu_stream_blksize(*streams) @@ -1091,7 +1123,7 @@ module FileUtils def path if @path - @path.to_str + File.path(@path) else join(@prefix, @rel) end @@ -1110,7 +1142,12 @@ module FileUtils end def exist? - lstat! ? true : false + begin + lstat + true + rescue Errno::ENOENT + false + end end def file? @@ -1156,7 +1193,9 @@ module FileUtils end def entries - Dir.entries(path())\ + opts = {} + opts[:encoding] = ::Encoding::UTF_8 if fu_windows? + Dir.entries(path(), opts)\ .reject {|n| n == '.' or n == '..' }\ .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) } end @@ -1214,10 +1253,14 @@ module FileUtils end def copy(dest) + lstat case when file? copy_file dest when directory? + if !File.exist?(dest) and descendant_directory?(dest, path) + raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest] + end begin Dir.mkdir dest rescue @@ -1245,24 +1288,39 @@ module FileUtils end def copy_file(dest) - st = stat() - File.open(path(), 'rb') {|r| - File.open(dest, 'wb', st.mode) {|w| - fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize()) - } - } + File.open(path()) do |s| + File.open(dest, 'wb', s.stat.mode) do |f| + IO.copy_stream(s, f) + end + end end def copy_metadata(path) st = lstat() - File.utime st.atime, st.mtime, path + if !st.symlink? + File.utime st.atime, st.mtime, path + end + mode = st.mode begin - File.chown st.uid, st.gid, path - rescue Errno::EPERM + if st.symlink? + begin + File.lchown st.uid, st.gid, path + rescue NotImplementedError + end + else + File.chown st.uid, st.gid, path + end + rescue Errno::EPERM, Errno::EACCES # clear setuid/setgid - File.chmod st.mode & 01777, path + mode &= 01777 + end + if st.symlink? + begin + File.lchmod mode, path + rescue NotImplementedError + end else - File.chmod st.mode, path + File.chmod mode, path end end @@ -1276,7 +1334,7 @@ module FileUtils def remove_dir1 platform_support { - Dir.rmdir path().sub(%r</\z>, '') + Dir.rmdir path().chomp(?/) } end @@ -1324,9 +1382,20 @@ module FileUtils end end end + ensure yield self end + def wrap_traverse(pre, post) + pre.call self + if directory? + entries.each do |ent| + ent.wrap_traverse pre, post + end + end + post.call self + end + private $fileutils_rb_have_lchmod = nil @@ -1366,14 +1435,25 @@ module FileUtils end def join(dir, base) - return dir.to_str if not base or base == '.' - return base.to_str if not dir or dir == '.' + return File.path(dir) if not base or base == '.' + return File.path(base) if not dir or dir == '.' File.join(dir, base) end + + if File::ALT_SEPARATOR + DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)" + else + DIRECTORY_TERM = "(?=/|\\z)" + end + SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : "" + + def descendant_directory?(descendant, ascendant) + /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant) + end end # class Entry_ def fu_list(arg) #:nodoc: - [arg].flatten.map {|path| path.to_str } + [arg].flatten.map {|path| File.path(path) } end private_module_function :fu_list @@ -1386,59 +1466,27 @@ module FileUtils private_module_function :fu_each_src_dest def fu_each_src_dest0(src, dest) #:nodoc: - if src.is_a?(Array) - src.each do |s| - s = s.to_str + if tmp = Array.try_convert(src) + tmp.each do |s| + s = File.path(s) yield s, File.join(dest, File.basename(s)) end else - src = src.to_str + src = File.path(src) if File.directory?(dest) yield src, File.join(dest, File.basename(src)) else - yield src, dest.to_str + yield src, File.path(dest) end end end private_module_function :fu_each_src_dest0 def fu_same?(a, b) #:nodoc: - if fu_have_st_ino? - st1 = File.stat(a) - st2 = File.stat(b) - st1.dev == st2.dev and st1.ino == st2.ino - else - File.expand_path(a) == File.expand_path(b) - end - rescue Errno::ENOENT - return false + File.identical?(a, b) end private_module_function :fu_same? - def fu_have_st_ino? #:nodoc: - not fu_windows? - end - private_module_function :fu_have_st_ino? - - def fu_check_options(options, *optdecl) #:nodoc: - h = options.dup - optdecl.each do |name| - h.delete name - end - raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? - end - private_module_function :fu_check_options - - def fu_update_option(args, new) #:nodoc: - if args.last.is_a?(Hash) - args[-1] = args.last.dup.update(new) - else - args.push new - end - args - end - private_module_function :fu_update_option - @fileutils_output = $stderr @fileutils_label = '' @@ -1449,14 +1497,19 @@ module FileUtils end private_module_function :fu_output_message - METHODS = singleton_methods() - ['private_module_function'] + # This hash table holds command options. + OPT_TABLE = {} #:nodoc: internal use only + (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name| + (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact! + tbl + } # # Returns an Array of method names which have any options. # # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] # - def FileUtils.commands + def self.commands OPT_TABLE.keys end @@ -1465,8 +1518,8 @@ module FileUtils # # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] # - def FileUtils.options - OPT_TABLE.values.flatten.uniq + def self.options + OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } end # @@ -1474,20 +1527,20 @@ module FileUtils # # p FileUtils.have_option?(:cp, :noop) #=> true # p FileUtils.have_option?(:rm, :force) #=> true - # p FileUtils.have_option?(:rm, :perserve) #=> false + # p FileUtils.have_option?(:rm, :preserve) #=> false # - def FileUtils.have_option?(mid, opt) + def self.have_option?(mid, opt) li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" - li.include?(opt.to_s) + li.include?(opt) end # # Returns an Array of option names of the method +mid+. # - # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] + # p FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"] # - def FileUtils.options_of(mid) - OPT_TABLE[mid.to_s] + def self.options_of(mid) + OPT_TABLE[mid.to_s].map {|sym| sym.to_s } end # @@ -1495,83 +1548,92 @@ module FileUtils # # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] # - def FileUtils.collect_method(opt) - OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt.to_s) } + def self.collect_method(opt) + OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } + end + + LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern) + module LowMethods + private + def _do_nothing(*)end + ::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing} end - # + METHODS = singleton_methods() - [:private_module_function, + :commands, :options, :have_option?, :options_of, :collect_method] + + # # 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 in FileUtils. - # + # module Verbose include FileUtils @fileutils_output = $stderr @fileutils_label = '' - ::FileUtils.collect_method('verbose').each do |name| + names = ::FileUtils.collect_method(:verbose) + names.each do |name| module_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{name}(*args) - super(*fu_update_option(args, :verbose => true)) + def #{name}(*args, **options) + super(*args, **options, verbose: true) end - private :#{name} EOS end + private(*names) extend self class << self - ::FileUtils::METHODS.each do |m| - public m - end + public(*::FileUtils::METHODS) end end - # + # # This module has all methods of FileUtils module, but never changes # files/directories. This equates to passing the <tt>:noop</tt> flag # to methods in FileUtils. - # + # module NoWrite include FileUtils + include LowMethods @fileutils_output = $stderr @fileutils_label = '' - ::FileUtils.collect_method('noop').each do |name| + names = ::FileUtils.collect_method(:noop) + names.each do |name| module_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{name}(*args) - super(*fu_update_option(args, :noop => true)) + def #{name}(*args, **options) + super(*args, **options, noop: true) end - private :#{name} EOS end + private(*names) extend self class << self - ::FileUtils::METHODS.each do |m| - public m - end + public(*::FileUtils::METHODS) end end - # + # # This module has all methods of FileUtils module, but never changes # files/directories, with printing message before acting. # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag # to methods in FileUtils. - # + # module DryRun include FileUtils + include LowMethods @fileutils_output = $stderr @fileutils_label = '' - ::FileUtils.collect_method('noop').each do |name| + names = ::FileUtils.collect_method(:noop) + names.each do |name| module_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{name}(*args) - super(*fu_update_option(args, :noop => true, :verbose => true)) + def #{name}(*args, **options) + super(*args, **options, noop: true, verbose: true) end - private :#{name} EOS end + private(*names) extend self class << self - ::FileUtils::METHODS.each do |m| - public m - end + public(*::FileUtils::METHODS) end end |
