mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* 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
This commit is contained in:
parent
98272fe035
commit
49e2c2e058
2 changed files with 265 additions and 133 deletions
11
ChangeLog
11
ChangeLog
|
|
@ -1,3 +1,14 @@
|
|||
Sat Aug 14 13:09:10 2004 Minero Aoki <aamine@loveruby.net>
|
||||
|
||||
* 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.
|
||||
|
||||
Sat Aug 14 02:48:16 2004 Dave Thomas <dave@pragprog.com>
|
||||
|
||||
* lib/rdoc/usage.rb: Added. Allows command line programs
|
||||
|
|
|
|||
387
lib/fileutils.rb
387
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
|
||||
|
||||
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
|
||||
end
|
||||
private :fu_preserve_attr
|
||||
private :fu_traverse
|
||||
|
||||
#
|
||||
# Copies file +src+ to +dest+.
|
||||
# Both of +src+ and +dest+ must be a filename.
|
||||
# Copies a file system entry +src+ to +dest+.
|
||||
# This method preserves file types, c.f. FIFO, device files, directory....
|
||||
#
|
||||
def copy_file(src, dest)
|
||||
File.open(src, 'rb') {|r|
|
||||
File.open(dest, 'wb') {|w|
|
||||
copy_stream r, w
|
||||
}
|
||||
}
|
||||
# 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
|
||||
|
||||
#
|
||||
# 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)
|
||||
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
|
||||
rescue SystemCallError
|
||||
begin
|
||||
copy_entry s, d, true
|
||||
File.unlink s
|
||||
else
|
||||
st = File.stat(s)
|
||||
copy_file s, d
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue