diff options
author | aamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-08-14 04:09:33 +0000 |
---|---|---|
committer | aamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-08-14 04:09:33 +0000 |
commit | 49e2c2e058d00708c6de2ba210070e838a9e0581 (patch) | |
tree | 83810c99dd2c07049b89d6fb6e2436142b9b481f /lib/fileutils.rb | |
parent | 98272fe035c7c4bd005007995159bffd7333ed39 (diff) |
* lib/fileutils.rb: backport from CVS HEAD (rev1.44).
* lib/fileutils.rb: cp_r should copy symlink itself, except cp_r root.
* lib/fileutils.rb: new option mv :force.
* lib/fileutils.rb: new module FileUtils::DryRun.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8@6772 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/fileutils.rb')
-rw-r--r-- | lib/fileutils.rb | 381 |
1 files changed, 251 insertions, 130 deletions
diff --git a/lib/fileutils.rb b/lib/fileutils.rb index 99e4feeaa3..323c373480 100644 --- a/lib/fileutils.rb +++ b/lib/fileutils.rb @@ -1,7 +1,7 @@ # # = fileutils.rb # -# Copyright (c) 2000-2003 Minero Aoki <aamine@loveruby.net> +# Copyright (c) 2000-2004 Minero Aoki <aamine@loveruby.net> # # This program is free software. # You can distribute/modify this program under the same terms of ruby. @@ -37,9 +37,9 @@ # touch(list, options) # # The <tt>options</tt> parameter is a hash of options, taken from the list -# +:force+, +:noop+, +:preserve+, and +:verbose+. +:noop+ means that no changes -# are made. The other two are obvious. Each method documents the options that -# it honours. +# <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 @@ -48,22 +48,29 @@ # There are some `low level' methods, which does not accept any option: # # uptodate?(file, cmp_list) -# copy_file(srcfilename, destfilename) +# copy_entry(src, dest, preserve = false, dereference = false) +# copy_file(src, dest, preserve = false, dereference = true) # copy_stream(srcstream, deststream) -# compare_file(file_a, file_b) +# compare_file(path_a, path_b) # compare_stream(stream_a, stream_b) # # == module FileUtils::Verbose # # This module has all methods of FileUtils module, but it outputs messages -# before acting. This equates to passing the +:verbose+ flag to methods in -# FileUtils. +# 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 +:noop+ flag to methods in -# FileUtils. +# 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. # @@ -171,6 +178,14 @@ module FileUtils mode = options[:mode] || (0777 & ~File.umask) list.map {|path| path.sub(%r</\z>, '') }.each do |path| + # optimize for the most common case + begin + Dir.mkdir path + next + rescue SystemCallError + next if File.directory?(path) + end + stack = [] until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" stack.push path @@ -316,9 +331,7 @@ module FileUtils return if options[:noop] fu_each_src_dest(src, dest) do |s,d| - fu_preserve_attr(options[:preserve], s, d) { - copy_file s, d - } + copy_file s, d, options[:preserve] end end @@ -340,6 +353,12 @@ module FileUtils # # 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, :verbose + # + # # 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, + # # but this doesn't. # def cp_r(src, dest, options = {}) fu_check_options options, :preserve, :noop, :verbose @@ -348,68 +367,53 @@ module FileUtils fu_each_src_dest(src, dest) do |s,d| if File.directory?(s) - fu_copy_dir s, d, '.', options[:preserve] + fu_traverse(s) {|rel, deref, st| + ctx = CopyContext_.new(options[:preserve], deref, st) + ctx.copy_entry "#{s}/#{rel}", "#{d}/#{rel}" + } else - fu_p_copy s, d, options[:preserve] + copy_file s, d, options[:preserve] end end end - def fu_copy_dir(src, dest, rel, preserve) #:nodoc: - fu_preserve_attr(preserve, "#{src}/#{rel}", "#{dest}/#{rel}") {|s,d| - begin - Dir.mkdir File.expand_path(d) - rescue => err - raise unless File.directory?(d) - end - } - Dir.entries("#{src}/#{rel}").each do |fname| - if File.directory?(File.join(src,rel,fname)) - next if /\A\.\.?\z/ === fname - fu_copy_dir src, dest, "#{rel}/#{fname}", preserve - else - fu_p_copy File.join(src,rel,fname), File.join(dest,rel,fname), preserve + def fu_traverse(prefix, dereference_root = true) #:nodoc: + stack = ['.'] + deref = dereference_root + while rel = stack.pop + st = File.lstat("#{prefix}/#{rel}") + if st.directory? and (deref or not st.symlink?) + stack.concat Dir.entries("#{prefix}/#{rel}")\ + .reject {|ent| ent == '.' or ent == '..' }\ + .map {|ent| "#{rel}/#{ent}" }.reverse end + yield rel, deref, st + deref = false end end - private :fu_copy_dir + private :fu_traverse - def fu_p_copy(src, dest, really) #:nodoc: - fu_preserve_attr(really, src, dest) { - copy_file src, dest - } - end - private :fu_p_copy - - def fu_preserve_attr(really, src, dest) #:nodoc: - unless really - yield src, dest - return - end - - st = File.stat(src) - yield src, dest - File.utime st.atime, st.mtime, dest - begin - File.chown st.uid, st.gid, dest - rescue Errno::EPERM - File.chmod st.mode & 01777, dest # clear setuid/setgid - else - File.chmod st.mode, dest - end + # + # Copies a file system entry +src+ to +dest+. + # This method preserves file types, c.f. FIFO, device files, directory.... + # + # 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 and permissions. + # If +dereference+ is true, this method copies a target of symbolic link + # instead of a symbolic link itself. + # + def copy_entry(src, dest, preserve = false, dereference = false) + CopyContext_.new(preserve, dereference).copy_entry src, dest end - private :fu_preserve_attr # - # Copies file +src+ to +dest+. - # Both of +src+ and +dest+ must be a filename. + # Copies file contents of +src+ to +dest+. + # Both of +src+ and +dest+ must be a path name. # - def copy_file(src, dest) - File.open(src, 'rb') {|r| - File.open(dest, 'wb') {|w| - copy_stream r, w - } - } + def copy_file(src, dest, preserve = false, dereference = true) + CopyContext_.new(preserve, dereference).copy_content src, dest end # @@ -417,53 +421,162 @@ module FileUtils # Both of +src+ and +dest+ must be a IO. # def copy_stream(src, dest) - bsize = fu_stream_blksize(src, dest) + fu_copy_stream0 src, dest, fu_stream_blksize(src, dest) + end + + def fu_copy_stream0(src, dest, blksize) #:nodoc: begin while true - dest.syswrite src.sysread(bsize) + dest.syswrite src.sysread(blksize) end rescue EOFError end end + private :fu_copy_stream0 + + class CopyContext_ + include ::FileUtils + + def initialize(preserve = false, dereference = false, stat = nil) + @preserve = preserve + @dereference = dereference + @stat = stat + end + + def copy_entry(src, dest) + preserve(src, dest) { + _copy_entry src, dest + } + end + + def copy_content(src, dest) + preserve(src, dest) { + _copy_content src, dest + } + end + + private + + def _copy_entry(src, dest) + st = stat(src) + case + when st.file? + _copy_content src, dest + when st.directory? + begin + Dir.mkdir File.expand_path(dest) + rescue => err + raise unless File.directory?(dest) + end + when st.symlink? + File.symlink File.readlink(src), dest + when st.chardev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?c, 0666, st.rdev + when st.blockdev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?b, 0666, st.rdev + when st.socket? + raise "cannot handle socket" unless File.respond_to?(:mknod) + mknod dest, nil, st.mode, 0 + when st.pipe? + raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) + mkfifo dest, 0666 + when (st.mode & 0xF000) == (_S_IF_DOOR = 0xD000) # door + raise "cannot handle door: #{src}" + else + raise "unknown file type: #{src}" + end + end + + def _copy_content(src, dest) + st = stat(src) + File.open(src, 'rb') {|r| + File.open(dest, 'wb', st.mode) {|w| + fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize()) + } + } + end + + def preserve(src, dest) + return yield unless @preserve + st = stat(src) + yield + File.utime st.atime, st.mtime, dest + begin + chown st.uid, st.gid, dest + rescue Errno::EPERM + # clear setuid/setgid + chmod st.mode & 01777, dest + else + chmod st.mode, dest + end + end + + def stat(path) + @stat ||= ::File.stat(path) + end + + def chmod(mode, path) + if @dereference + ::File.chmod mode, path + else + begin + ::File.lchmod mode, path + rescue NotImplementedError + # just ignore this because chmod(symlink) changes attributes of + # symlink target, which is not our intent. + end + end + end + + def chown(uid, gid, path) + if @dereference + ::File.chown uid, gid, path + else + begin + ::File.lchown uid, gid, path + rescue NotImplementedError + # just ignore this because chown(symlink) changes attributes of + # symlink target, which is not our intent. + end + end + end + end # - # Options: noop verbose + # 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', 'lib/ruby', :force => true + # 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, :verbose => true + # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true # def mv(src, dest, options = {}) - fu_check_options options, :noop, :verbose - fu_output_message "mv #{[src,dest].flatten.join ' '}" if options[:verbose] + fu_check_options options, :force, :noop, :verbose + fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] fu_each_src_dest(src, dest) do |s,d| if rename_cannot_overwrite_file? and File.file?(d) - File.unlink d + begin + File.unlink d + rescue SystemCallError + raise unless options[:force] + end end - begin File.rename s, d - rescue - if File.symlink?(s) - File.symlink File.readlink(s), dest - File.unlink s - else - st = File.stat(s) - copy_file s, d + rescue SystemCallError + begin + copy_entry s, d, true File.unlink s - File.utime st.atime, st.mtime, d - begin - File.chown st.uid, st.gid, d - rescue - # ignore - end + rescue SystemCallError + raise unless options[:force] end end end @@ -481,7 +594,7 @@ module FileUtils # Options: force noop verbose # # Remove file(s) specified in +list+. This method cannot remove directories. - # All errors are ignored when the :force option is set. + # All StandardErrors are ignored when the :force option is set. # # FileUtils.rm %w( junk.txt dust.txt ) # FileUtils.rm Dir.glob('*.so') @@ -760,9 +873,20 @@ module FileUtils def fu_stream_blksize(*streams) streams.each do |s| next unless s.respond_to?(:stat) - size = s.stat.blksize - return size unless size.nil? or size.zero? + size = fu_blksize(s.stat) + return size if size end + fu_default_blksize() + end + + def fu_blksize(st) + s = st.blksize + return nil unless s + return nil if s == 0 + s + end + + def fu_default_blksize 1024 end @@ -789,98 +913,95 @@ module FileUtils OPT_TABLE = { - 'pwd' => %w(), 'cd' => %w( noop verbose ), 'chdir' => %w( noop verbose ), 'chmod' => %w( noop verbose ), - 'copy' => %w( preserve noop verbose ), - 'cp' => %w( preserve noop verbose ), - 'cp_r' => %w( preserve noop verbose ), - 'install' => %w( preserve mode noop verbose ), - 'link' => %w( force noop verbose ), - 'ln' => %w( force noop verbose ), - 'ln_s' => %w( force noop verbose ), + 'copy' => %w( noop verbose preserve ), + 'cp' => %w( noop verbose preserve ), + 'cp_r' => %w( noop verbose preserve ), + 'install' => %w( noop verbose preserve mode ), + 'link' => %w( noop verbose force ), + 'ln' => %w( noop verbose force ), + 'ln_s' => %w( noop verbose force ), 'ln_sf' => %w( noop verbose ), 'makedirs' => %w( noop verbose ), - 'mkdir' => %w( mode noop verbose ), - 'mkdir_p' => %w( mode noop verbose ), + 'mkdir' => %w( noop verbose mode ), + 'mkdir_p' => %w( noop verbose mode ), 'mkpath' => %w( noop verbose ), - 'move' => %w( noop verbose ), - 'mv' => %w( noop verbose ), - 'remove' => %w( force noop verbose ), - 'rm' => %w( force noop verbose ), + 'move' => %w( noop verbose force ), + 'mv' => %w( noop verbose force ), + 'remove' => %w( noop verbose force ), + 'rm' => %w( noop verbose force ), 'rm_f' => %w( noop verbose ), - 'rm_r' => %w( force noop verbose ), + 'rm_r' => %w( noop verbose force ), 'rm_rf' => %w( noop verbose ), 'rmtree' => %w( noop verbose ), 'rmdir' => %w( noop verbose ), 'safe_unlink' => %w( noop verbose ), - 'symlink' => %w( force noop verbose ), + 'symlink' => %w( noop verbose force ), 'touch' => %w( noop verbose ) } # # This module has all methods of FileUtils module, but it outputs messages - # before acting. This equates to passing the +:verbose+ flag to methods in - # FileUtils. + # 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_verbose = true - FileUtils::OPT_TABLE.each do |name, opts| next unless opts.include?('verbose') module_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{name}(*args) - @fileutils_verbose = true unless defined?(@fileutils_verbose) - super(*fu_update_option(args, :verbose => @fileutils_verbose)) + super(*fu_update_option(args, :verbose => true)) end EOS end - extend self - end - # # This module has all methods of FileUtils module, but never changes - # files/directories. This equates to passing the +:noop+ flag to methods in - # FileUtils. + # files/directories. This equates to passing the <tt>:noop</tt> flag + # to methods in FileUtils. # module NoWrite - include FileUtils - @fileutils_output = $stderr @fileutils_label = '' - @fileutils_nowrite = true - FileUtils::OPT_TABLE.each do |name, opts| next unless opts.include?('noop') module_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{name}(*args) - unless defined?(@fileutils_nowrite) - @fileutils_nowrite ||= true - end super(*fu_update_option(args, :noop => true)) end EOS end + extend self + 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 + @fileutils_output = $stderr + @fileutils_label = '' + FileUtils::OPT_TABLE.each do |name, opts| + next unless opts.include?('noop') + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :noop => true, :verbose => true)) + end + EOS + end extend self - end end - - -# Documentation comments: -# - Some RDoc markup used here doesn't work (namely, +file1+, +:noop+, -# +dir/file+). I consider this a bug and expect that these will be valid in -# the near future. |