summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-08-14 04:09:33 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-08-14 04:09:33 +0000
commit49e2c2e058d00708c6de2ba210070e838a9e0581 (patch)
tree83810c99dd2c07049b89d6fb6e2436142b9b481f /lib
parent98272fe035c7c4bd005007995159bffd7333ed39 (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')
-rw-r--r--lib/fileutils.rb381
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.