├── var ├── name ├── version ├── created ├── title ├── summary ├── organization ├── authors ├── requirements ├── repositories ├── description ├── copyrights └── resources ├── Gemfile ├── .gitignore ├── .travis.yml ├── lib ├── fileutils2 │ └── override.rb └── fileutils2.rb ├── test ├── fileutils2 │ ├── test_verbose.rb │ ├── test_dryrun.rb │ ├── test_nowrite.rb │ ├── test_inclusion.rb │ ├── visibility_tests.rb │ ├── clobber.rb │ ├── fileasserts.rb │ └── test_fileutils.rb └── runner.rb ├── HISTORY.md ├── work ├── build.md └── reference │ └── fileutils.rb ├── .index ├── LICENSE.txt ├── .gemspec └── README.md /var/name: -------------------------------------------------------------------------------- 1 | fileutils2 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2011-07-04 2 | -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | FileUtils2 2 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | FileUtils refactored 2 | -------------------------------------------------------------------------------- /var/organization: -------------------------------------------------------------------------------- 1 | --- 2 | - Rubyworks 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Trans 3 | - Minero Aoki 4 | 5 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - minitest (test) 3 | - ergo (build) 4 | 5 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/fileutils2.git 3 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | FileUtils2 is an improved design of Ruby's built-in FileUtils library. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.lock 3 | .ergo/digest 4 | .yardoc 5 | doc/ 6 | log/ 7 | pkg/ 8 | tmp/ 9 | web/ 10 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - Copyright (c) 2011 Rubyworks (BSD-2-Clause) 3 | - Copyright (c) 2000 Minero Aoki (Ruby) 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec ruby test/runner.rb" 4 | rvm: 5 | - 1.9.2 6 | - 1.9.3 7 | # - rbx-19mode 8 | # - jruby-19mode 9 | 10 | -------------------------------------------------------------------------------- /lib/fileutils2/override.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils2' 2 | 3 | verbose = $VERBOSE 4 | begin 5 | $VERBOSE = false 6 | FileUtils = ::FileUtils2 7 | ensure 8 | $VERBOSE = verbose 9 | end 10 | 11 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/fileutils2 3 | code: http://github.com/rubyworks/fileutils2 4 | docs: http://rubydoc.info/gems/fileutils2 5 | bugs: http://github.com/rubyworks/fileutils2/issues 6 | 7 | -------------------------------------------------------------------------------- /test/fileutils2/test_verbose.rb: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | require 'fileutils2' 4 | require 'fileutils2/visibility_tests' 5 | require 'test/unit' 6 | 7 | class TestFileUtils2Verbose < Test::Unit::TestCase 8 | 9 | include FileUtils2::Verbose 10 | include TestFileUtils2::Visibility 11 | 12 | def setup 13 | super 14 | @fu_module = FileUtils2::Verbose 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.2.0 / 2013-03-19 4 | 5 | This is the initial release of FileUtils2. 6 | 7 | Changes: 8 | 9 | * Override #include to handle Module Inclusion Problem. 10 | * Fixes issue of missing StreamUtils methods. 11 | 12 | 13 | ## 0.1.0 / 2011-07-04 14 | 15 | This is the release that was accepted into Ruby MRI distribution 16 | upto Ruby 2.0-rc1. 17 | 18 | Changes: 19 | 20 | * All of them. 21 | 22 | -------------------------------------------------------------------------------- /test/fileutils2/test_dryrun.rb: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | require 'fileutils2' 4 | require 'fileutils2/visibility_tests' 5 | require 'fileutils2/clobber' 6 | require 'test/unit' 7 | 8 | class TestFileUtils2DryRun < Test::Unit::TestCase 9 | 10 | include FileUtils2::DryRun 11 | include TestFileUtils2::Clobber 12 | include TestFileUtils2::Visibility 13 | 14 | def setup 15 | super 16 | @fu_module = FileUtils2::DryRun 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/fileutils2/test_nowrite.rb: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | require 'fileutils2' 4 | require 'fileutils2/visibility_tests' 5 | require 'fileutils2/clobber' 6 | require 'test/unit' 7 | 8 | class TestFileUtils2NoWrite < Test::Unit::TestCase 9 | 10 | include FileUtils2::NoWrite 11 | include TestFileUtils2::Visibility 12 | include TestFileUtils2::Clobber 13 | 14 | def setup 15 | super 16 | @fu_module = FileUtils2::NoWrite 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/runner.rb: -------------------------------------------------------------------------------- 1 | if defined?(::FileUtils) 2 | warn "WARNING! Original FileUtils library is already loaded!" 3 | end 4 | 5 | root = File.expand_path('..', File.dirname(__FILE__)) 6 | 7 | $LOAD_PATH.unshift(File.join(root, 'test')) 8 | $LOAD_PATH.unshift(File.join(root, 'lib')) 9 | 10 | require 'minitest/autorun' 11 | 12 | test_files = Dir[File.join(root, 'test', 'fileutils2', 'test_*.rb')] 13 | test_files.each do |test_file| 14 | require test_file 15 | end 16 | 17 | -------------------------------------------------------------------------------- /work/build.md: -------------------------------------------------------------------------------- 1 | ## Build Status 2 | 3 | This is failing b/c Travis CI is pre-loading fileutils.rb. Unless that can be fixed, then there 4 | is no way to propery fest FileUtils2 on Travis CI as is. 5 | 6 | [![Build Status](https://secure.travis-ci.org/rubyworks/fileutils2.png)](http://travis-ci.org/rubyworks/fileutils2) 7 | 8 | I could rename the module to FileUtils2, set FileUtils = FileUtils2 and then change all the tests to use FileUtils2 instead of FileUtils. That should work, but I am not sure it is a good idea to maintain all these references changes to the tests --I'd rather they reflect the original tests as much as possible. 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/fileutils2/test_inclusion.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils2' 2 | require 'test/unit' 3 | 4 | class TestFileUtils2Inclusion < Test::Unit::TestCase 5 | 6 | module Foo 7 | def foo?; true; end 8 | end 9 | 10 | def test_include_into_all_submodules 11 | ::FileUtils2.send(:include, Foo) 12 | 13 | assert_includes ::FileUtils2.ancestors, Foo 14 | assert_includes ::FileUtils2::NoWrite.ancestors, Foo 15 | assert_includes ::FileUtils2::Verbose.ancestors, Foo 16 | assert_includes ::FileUtils2::DryRun.ancestors, Foo 17 | 18 | assert ::FileUtils2::NoWrite.foo? 19 | assert ::FileUtils2::Verbose.foo? 20 | assert ::FileUtils2::DryRun.foo? 21 | assert ::FileUtils2.foo? 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /test/fileutils2/visibility_tests.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils2' 2 | require 'test/unit' 3 | 4 | class TestFileUtils2 < Test::Unit::TestCase 5 | end 6 | 7 | ## 8 | # These tests are reused in the FileUtils::Verbose, FileUtils::NoWrite and 9 | # FileUtils::DryRun tests 10 | # 11 | module TestFileUtils2::Visibility 12 | 13 | FileUtils2::METHODS.each do |m| 14 | define_method "test_singleton_visibility_#{m}" do 15 | assert @fu_module.respond_to?(m, true), 16 | "#{@fu_module}.#{m} is not defined" 17 | assert @fu_module.respond_to?(m, false), 18 | "#{@fu_module}.#{m} is not public" 19 | end 20 | 21 | define_method "test_visibility_#{m}" do 22 | assert respond_to?(m, true), 23 | "#{@fu_module}\##{m} is not defined" 24 | assert @fu_module.private_method_defined?(m), 25 | "#{@fu_module}\##{m} is not private" 26 | end 27 | end 28 | 29 | FileUtils2::StreamUtils_.private_instance_methods.each do |m| 30 | define_method "test_singleton_visibility_#{m}" do 31 | assert @fu_module.respond_to?(m, true), 32 | "#{@fu_module}\##{m} is not defined" 33 | end 34 | 35 | define_method "test_visibility_#{m}" do 36 | assert respond_to?(m, true), 37 | "#{@fu_module}\##{m} is not defined" 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - var 6 | authors: 7 | - name: Trans 8 | email: transfire@gmail.com 9 | - name: Minero Aoki 10 | organizations: [] 11 | requirements: 12 | - groups: 13 | - test 14 | development: true 15 | name: minitest 16 | - groups: 17 | - build 18 | development: true 19 | name: ergo 20 | conflicts: [] 21 | alternatives: [] 22 | resources: 23 | - type: home 24 | uri: http://rubyworks.github.com/fileutils2 25 | label: Homepage 26 | - type: code 27 | uri: http://github.com/rubyworks/fileutils2 28 | label: Source Code 29 | - type: docs 30 | uri: http://rubydoc.info/gems/fileutils2 31 | label: Documentation 32 | - type: bugs 33 | uri: http://github.com/rubyworks/fileutils2/issues 34 | label: Issue Tracker 35 | repositories: 36 | - name: upstream 37 | scm: git 38 | uri: git://github.com/rubyworks/fileutils2.git 39 | categories: [] 40 | copyrights: 41 | - holder: Rubyworks 42 | year: '2011' 43 | license: BSD-2-Clause 44 | - holder: Minero Aoki 45 | year: '2000' 46 | license: Ruby 47 | customs: [] 48 | paths: 49 | lib: 50 | - lib 51 | created: '2011-07-04' 52 | summary: FileUtils refactored 53 | title: FileUtils2 54 | version: 0.2.0 55 | name: fileutils2 56 | description: FileUtils2 is an improved design of Ruby's built-in FileUtils library. 57 | date: '2013-03-19' 58 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause License) 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of 7 | conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 10 | of conditions and the following disclaimer in the documentation and/or other materials 11 | provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY Rubyworks ``AS IS'' AND ANY EXPRESS OR IMPLIED 14 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rubyworks OR 16 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 17 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 21 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | The views and conclusions contained in the software and documentation are those of the 24 | authors and should not be interpreted as representing official policies, either expressed 25 | or implied, of Rubyworks. 26 | 27 | -------------------------------------------------------------------------------- /test/fileutils2/clobber.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils2' 2 | require 'fileutils2/fileasserts' 3 | require 'tmpdir' 4 | require 'test/unit' 5 | 6 | class TestFileUtils2 < Test::Unit::TestCase 7 | end 8 | 9 | module TestFileUtils2::Clobber 10 | include Test::Unit::FileAssertions 11 | 12 | def my_rm_rf(path) 13 | if File.exist?('/bin/rm') 14 | system %Q[/bin/rm -rf "#{path}"] 15 | else 16 | FileUtils.rm_rf path 17 | end 18 | end 19 | 20 | SRC = 'data/src' 21 | COPY = 'data/copy' 22 | 23 | def setup 24 | @prevdir = Dir.pwd 25 | class << (@fileutils_output = "") 26 | alias puts << 27 | end 28 | tmproot = "#{Dir.tmpdir}/fileutils.rb.#{$$}" 29 | Dir.mkdir tmproot unless File.directory?(tmproot) 30 | Dir.chdir tmproot 31 | my_rm_rf 'data'; Dir.mkdir 'data' 32 | my_rm_rf 'tmp'; Dir.mkdir 'tmp' 33 | File.open(SRC, 'w') {|f| f.puts 'dummy' } 34 | File.open(COPY, 'w') {|f| f.puts 'dummy' } 35 | end 36 | 37 | def teardown 38 | tmproot = Dir.pwd 39 | Dir.chdir @prevdir 40 | my_rm_rf tmproot 41 | end 42 | 43 | def test_cp 44 | cp SRC, 'tmp/cp' 45 | check 'tmp/cp' 46 | end 47 | 48 | def test_mv 49 | mv SRC, 'tmp/mv' 50 | check 'tmp/mv' 51 | end 52 | 53 | def check(dest, message=nil) 54 | assert_file_not_exist dest, message 55 | assert_file_exist SRC, message 56 | assert_same_file SRC, COPY, message 57 | end 58 | 59 | def test_rm 60 | rm SRC 61 | assert_file_exist SRC 62 | assert_same_file SRC, COPY 63 | end 64 | 65 | def test_rm_f 66 | rm_f SRC 67 | assert_file_exist SRC 68 | assert_same_file SRC, COPY 69 | end 70 | 71 | def test_rm_rf 72 | rm_rf SRC 73 | assert_file_exist SRC 74 | assert_same_file SRC, COPY 75 | end 76 | 77 | def test_mkdir 78 | mkdir 'dir' 79 | assert_file_not_exist 'dir' 80 | end 81 | 82 | def test_mkdir_p 83 | mkdir 'dir/dir/dir' 84 | assert_file_not_exist 'dir' 85 | end 86 | 87 | def test_copy_entry 88 | copy_entry SRC, 'tmp/copy_entry' 89 | check 'tmp/copy_entry', bug4331 = '[ruby-dev:43129]' 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/fileutils2/fileasserts.rb: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | module Test 4 | module Unit 5 | module FileAssertions 6 | def assert_same_file(from, to, message=nil) 7 | assert_equal(File.read(from), File.read(to), "file #{from} != #{to}#{message&&': '}#{message||''}") 8 | end 9 | 10 | def assert_same_entry(from, to, message=nil) 11 | a = File.stat(from) 12 | b = File.stat(to) 13 | msg = "#{message&&': '}#{message||''}" 14 | assert_equal a.mode, b.mode, "mode #{a.mode} != #{b.mode}#{msg}" 15 | #assert_equal a.atime, b.atime 16 | assert_equal_timestamp a.mtime, b.mtime, "mtime #{a.mtime} != #{b.mtime}#{msg}" 17 | assert_equal a.uid, b.uid, "uid #{a.uid} != #{b.uid}#{msg}" 18 | assert_equal a.gid, b.gid, "gid #{a.gid} != #{b.gid}#{msg}" 19 | end 20 | 21 | def assert_file_exist(path, message=nil) 22 | assert(File.exist?(path), "file not exist: #{path}#{message&&': '}#{message||''}") 23 | end 24 | 25 | def assert_file_not_exist(path, message=nil) 26 | assert(!File.exist?(path), "file exist: #{path}#{message&&': '}#{message||''}") 27 | end 28 | 29 | def assert_directory(path, message=nil) 30 | assert(File.directory?(path), "is not directory: #{path}#{message&&': '}#{message||''}") 31 | end 32 | 33 | def assert_symlink(path, message=nil) 34 | assert(File.symlink?(path), "is not a symlink: #{path}#{message&&': '}#{message||''}") 35 | end 36 | 37 | def assert_not_symlink(path) 38 | assert(!File.symlink?(path), "is a symlink: #{path}#{message&&': '}#{message||''}") 39 | end 40 | 41 | def assert_equal_time(expected, actual, message=nil) 42 | expected_str = expected.to_s 43 | actual_str = actual.to_s 44 | if expected_str == actual_str 45 | expected_str << " (nsec=#{expected.nsec})" 46 | actual_str << " (nsec=#{actual.nsec})" 47 | end 48 | full_message = build_message(message, < expected but was 50 | <#{actual_str}>. 51 | EOT 52 | assert_equal(expected, actual, full_message) 53 | end 54 | 55 | def assert_equal_timestamp(expected, actual, message=nil) 56 | expected_str = expected.to_s 57 | actual_str = actual.to_s 58 | if expected_str == actual_str 59 | expected_str << " (nsec=#{expected.nsec})" 60 | actual_str << " (nsec=#{actual.nsec})" 61 | end 62 | full_message = build_message(message, < expected but was 64 | <#{actual_str}>. 65 | EOT 66 | # subsecond timestamp is not portable. 67 | assert_equal(expected.tv_sec, actual.tv_sec, full_message) 68 | end 69 | 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FileUtils2 2 | 3 | *A Refactorization of Ruby's Standard FileUtils Library* 4 | 5 | [Homepage](http://rubyworks.github.com/fileutils2) / 6 | [Documentation](http://rubydoc.info/gems/fileutils2) / 7 | [Report Issue](http://github.com/rubyworks/fileutils2/issues) / 8 | [Source Code](http://github.com/rubyworks/fileutils2) 9 | 10 | [![Build Status](https://secure.travis-ci.org/rubyworks/fileutils2.png)](http://travis-ci.org/rubyworks/fileutils2) 11 | [![Gem Version](https://badge.fury.io/rb/fileutils2.png)](http://badge.fury.io/rb/fileutils2)     12 | [![Flattr Me](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/324911/Rubyworks-Ruby-Development-Fund) 13 | 14 | 15 | ## About 16 | 17 | FileUtils as provided in Ruby suffers from the following design issues: 18 | 19 | 1. By using `module_function` FileUtils creates two copies of every method. 20 | Overriding an instance method will not override the corresponding class 21 | method, and vice-versa. 22 | 23 | 2. The design makes it inordinately more difficult to properly extend 24 | FileUtils than it needs to be, because one has to manually ensure any 25 | new method added to FileUtils are also added to the submodules. 26 | 27 | 3. The meta-programming aspect of the design requires the direct modification 28 | of a constant, `OPT_TABLE`. 29 | 30 | 4. Ruby's Module Inclusion Problem prevents extension modules from being included 31 | into FileUtils without additional steps being taken to include the module 32 | in every submodule as well. 33 | 34 | Lets take a simple example. Lets say we want to add a recursive linking 35 | method. 36 | 37 | ```ruby 38 | module FileUtils 39 | def ln_r(dir, dest, options={}) 40 | ... 41 | end 42 | module_function :ln_r 43 | end 44 | ``` 45 | 46 | That would seem like the right code, would it not? Unfortunately you would be 47 | way off the mark. Instead one would need to do the following: 48 | 49 | ```ruby 50 | module FileUtils 51 | OPT_TABLE['ln_r'] = [:force, :noop, :verbose] 52 | 53 | def ln_r(dir, dest, options={}) 54 | fu_check_options options, OPT_TABLE['ln_r'] 55 | ... 56 | end 57 | module_function :ln_r 58 | 59 | module Verbose 60 | include FileUtils 61 | module_eval(<<-EOS, __FILE__, __LINE__ + 1) 62 | def ln_r(*args) 63 | super(*fu_update_option(args, :verbose => true)) 64 | end 65 | private :ln_r 66 | EOS 67 | extend self 68 | end 69 | 70 | module NoWrite 71 | include FileUtils 72 | module_eval(<<-EOS, __FILE__, __LINE__ + 1) 73 | def ln_r(*args) 74 | super(*fu_update_option(args, :noop => true)) 75 | end 76 | private :ln_r 77 | EOS 78 | extend self 79 | end 80 | 81 | module DryRun 82 | include FileUtils 83 | module_eval(<<-EOS, __FILE__, __LINE__ + 1) 84 | def ln_r(*args) 85 | super(*fu_update_option(args, :noop => true, :verbose => true)) 86 | end 87 | private :ln_r 88 | EOS 89 | extend self 90 | end 91 | end 92 | ``` 93 | 94 | FileUtils2 fixes all this by doing three thing: 95 | 96 | 1. Use `self extend` instead of `module_function`. 97 | 2. Overriding `#include` to ensure inclusion at all levels. 98 | 3. Define a single *smart* DSL method called, #define_command`. 99 | 100 | With these changes the above code becomes simply: 101 | 102 | ```ruby 103 | module FileUtils2 104 | def ln_r(dir, dest, options={}) 105 | fu_check_options options, OPT_TABLE['ln_r'] 106 | ... 107 | end 108 | 109 | define_command('ln_r', :force, :noop, :verbose) 110 | end 111 | ``` 112 | 113 | Notice we still check the `OPT_TABLE` to ensure only the supported options 114 | are provided. So there is still room for some improvement in the design. 115 | This "second phase" will come later, after the initial phase has been put 116 | through its paces. (At least, that was the plan. See "Why a Gem" below.) 117 | 118 | Also note that this refactorization does not change the underlying functionality 119 | or the FileUtils methods in any way. They remain the same as in Ruby's standard 120 | library. 121 | 122 | 123 | ## Mixins for FileUtils 124 | 125 | Of course it might be unusual to want to include a mixin module into FileUtils. 126 | Nonetheless it should still be possible and not be inordinately mind-boggling 127 | to do so. With FileUtils2 it is almost as easy as simple as just including the 128 | mixin module. But there is still the `define_command` method that must be 129 | invoked. Thankfully it is not difficult to get that call in. 130 | 131 | For the sake of example lets say the above `#ln_r` method is nicely namespaced 132 | in it's own module and we wish to extend FileUtils with it. 133 | 134 | ```ruby 135 | module MyApp::FileUtilsMixin 136 | def self.included(fileutils) 137 | fileutils.module_eval do 138 | define_command('ln_r', :force, :noop, :verbose) 139 | end 140 | end 141 | 142 | def ln_r(dir, dest, options={}) 143 | fu_check_options options, OPT_TABLE['ln_r'] 144 | ... 145 | end 146 | end 147 | 148 | module FileUtils 149 | include MyApp::FileUtilsMixin 150 | end 151 | ``` 152 | 153 | Notice that defining the `included` callback, we were able to invoke the 154 | `define_command` method on the FileUtils module just as if we were adding 155 | the new method directly. 156 | 157 | 158 | ## Overriding FileUtils 159 | 160 | You can use FileUtils2 in place of FileUtils simple by setting FileUtils 161 | equal to FileUtils2. 162 | 163 | ```ruby 164 | require 'fileutils2' 165 | FileUtils = FileUtils2 166 | ``` 167 | 168 | It will issue a warning if FileUtils is already loaded, but it should work fine 169 | in either case. In fact, it may be wise to first `require 'fileutils'` in anycase 170 | to make sure it's not loaded later by some other script, which could cause some 171 | unspecified results due to method clobbering. Of course there should plenty 172 | of warnings in the output in that case, so you could just keep an eye out for 173 | it instead. 174 | 175 | For the sake of simply being overly thurough, included in the gem is a script 176 | that takes care of most of this for you called, `override.rb`. 177 | 178 | ```ruby 179 | require 'fileutils2/override' 180 | ``` 181 | 182 | It requires fileutils2.rb for you and sets `FileUtils = FileUtils2` while 183 | supressing the usual warning. It doesn't preload the old fileutils.rb library 184 | first though. That's your call. 185 | 186 | 187 | ## JRuby and Rubinius Users 188 | 189 | FileUtils2, as well as the original FileUtils library for that matter, produce 190 | a few test failures (out of a 1000+) when run again JRuby or Rubinius. At this 191 | point it is unclear exactly what the issues are. If you are involved in either 192 | of these projects and can spare a little time to try and fix these issues, that 193 | would be really great of you! Have a look at the 194 | [Rubinius build](https://travis-ci.org/rubyworks/fileutils2/jobs/5634466) 195 | and the [JRuby build](https://travis-ci.org/rubyworks/fileutils2/jobs/5634467) 196 | for these test results. 197 | 198 | 199 | ## Why a Gem? 200 | 201 | You might be wondering why this is a Gem and not part of Ruby's standard library. 202 | Unfortunately, due to to what I believe to be nothing more than "clique politics" 203 | among some of the Ruby Core members, this code has been rejected. 204 | 205 | Actually it was accepted, but after the discovery a bug (easily fixed) it was 206 | reverted. Despite the code passing all tests, and the fact that this bug made it 207 | clear that the tests themselves were missing something (that's a good thing to 208 | discover!), the code was reverted to the old design. Sadly, I am certain there 209 | was no other reason for it than the simple fact that the three main core members 210 | from Seattle.rb begrudge me, and go out their way to undermine everything I do. 211 | This behavior is fairly well documented in the archives of the ruby-talk mailing 212 | list. I don't like to think that their personal opinions of me would influence 213 | the design of the Ruby programming language, which should be of the utmost 214 | professional character, but it is clearly not the case, as is evidenced by 215 | the fact that they were not willing to discuss the design, let alone actually fix 216 | it, but instead summarily declared themselves the new maintainers of the code, 217 | reverted the code to the old design and pronounced the issue closed. Period. 218 | 219 | * https://bugs.ruby-lang.org/issues/4970 220 | * https://bugs.ruby-lang.org/issues/7958 221 | 222 | 223 | ## Legal 224 | 225 | Copyright (c) 2011 Rubyworks 226 | 227 | Copyright (c) 2000 Minero Aoki 228 | 229 | This program is distributed under the terms of the 230 | [BSD-2-Clause](http://www.spdx.org/licenses/BSD-2-Clause) license. 231 | 232 | See LICENSE.txt file for details. 233 | 234 | -------------------------------------------------------------------------------- /test/fileutils2/test_fileutils.rb: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | require 'fileutils2' 4 | require 'fileutils2/fileasserts' 5 | 6 | require 'pathname' 7 | require 'tmpdir' 8 | require 'test/unit' 9 | 10 | class TestFileUtils2 < Test::Unit::TestCase 11 | TMPROOT = "#{Dir.tmpdir}/fileutils.rb.#{$$}" 12 | include Test::Unit::FileAssertions 13 | end 14 | 15 | prevdir = Dir.pwd 16 | tmproot = TestFileUtils2::TMPROOT 17 | Dir.mkdir tmproot unless File.directory?(tmproot) 18 | Dir.chdir tmproot 19 | 20 | def have_drive_letter? 21 | /mswin(?!ce)|mingw|bcc|emx/ =~ RUBY_PLATFORM 22 | end 23 | 24 | def have_file_perm? 25 | /mswin|mingw|bcc|emx/ !~ RUBY_PLATFORM 26 | end 27 | 28 | $fileutils_rb_have_symlink = nil 29 | 30 | def have_symlink? 31 | if $fileutils_rb_have_symlink == nil 32 | $fileutils_rb_have_symlink = check_have_symlink? 33 | end 34 | $fileutils_rb_have_symlink 35 | end 36 | 37 | def check_have_symlink? 38 | File.symlink nil, nil 39 | rescue NotImplementedError 40 | return false 41 | rescue 42 | return true 43 | end 44 | 45 | $fileutils_rb_have_hardlink = nil 46 | 47 | def have_hardlink? 48 | if $fileutils_rb_have_hardlink == nil 49 | $fileutils_rb_have_hardlink = check_have_hardlink? 50 | end 51 | $fileutils_rb_have_hardlink 52 | end 53 | 54 | def check_have_hardlink? 55 | File.link nil, nil 56 | rescue NotImplementedError 57 | return false 58 | rescue 59 | return true 60 | end 61 | 62 | begin 63 | Dir.mkdir("\n") 64 | Dir.rmdir("\n") 65 | def lf_in_path_allowed? 66 | true 67 | end 68 | rescue 69 | def lf_in_path_allowed? 70 | false 71 | end 72 | end 73 | 74 | Dir.chdir prevdir 75 | Dir.rmdir tmproot 76 | 77 | class TestFileUtils2 78 | 79 | include FileUtils2 80 | 81 | def check_singleton(name) 82 | assert_respond_to ::FileUtils2, name 83 | end 84 | 85 | def my_rm_rf(path) 86 | if File.exist?('/bin/rm') 87 | system %Q[/bin/rm -rf "#{path}"] 88 | else 89 | FileUtils2.rm_rf path 90 | end 91 | end 92 | 93 | def mymkdir(path) 94 | Dir.mkdir path 95 | File.chown nil, Process.gid, path if have_file_perm? 96 | end 97 | 98 | def setup 99 | @prevdir = Dir.pwd 100 | tmproot = TMPROOT 101 | mymkdir tmproot unless File.directory?(tmproot) 102 | Dir.chdir tmproot 103 | my_rm_rf 'data'; mymkdir 'data' 104 | my_rm_rf 'tmp'; mymkdir 'tmp' 105 | prepare_data_file 106 | end 107 | 108 | def teardown 109 | Dir.chdir @prevdir 110 | my_rm_rf TMPROOT 111 | end 112 | 113 | 114 | TARGETS = %w( data/a data/all data/random data/zero ) 115 | 116 | def prepare_data_file 117 | File.open('data/a', 'w') {|f| 118 | 32.times do 119 | f.puts 'a' * 50 120 | end 121 | } 122 | 123 | all_chars = (0..255).map {|n| n.chr }.join('') 124 | File.open('data/all', 'w') {|f| 125 | 32.times do 126 | f.puts all_chars 127 | end 128 | } 129 | 130 | random_chars = (0...50).map { rand(256).chr }.join('') 131 | File.open('data/random', 'w') {|f| 132 | 32.times do 133 | f.puts random_chars 134 | end 135 | } 136 | 137 | File.open('data/zero', 'w') {|f| 138 | ; 139 | } 140 | end 141 | 142 | BIGFILE = 'data/big' 143 | 144 | def prepare_big_file 145 | File.open('data/big', 'w') {|f| 146 | (4 * 1024 * 1024 / 256).times do # 4MB 147 | f.print "aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa\n" 148 | end 149 | } 150 | end 151 | 152 | def prepare_time_data 153 | File.open('data/old', 'w') {|f| f.puts 'dummy' } 154 | File.open('data/newer', 'w') {|f| f.puts 'dummy' } 155 | File.open('data/newest', 'w') {|f| f.puts 'dummy' } 156 | t = Time.now 157 | File.utime t-8, t-8, 'data/old' 158 | File.utime t-4, t-4, 'data/newer' 159 | end 160 | 161 | def each_srcdest 162 | TARGETS.each do |path| 163 | yield path, "tmp/#{File.basename(path)}" 164 | end 165 | end 166 | 167 | # 168 | # Test Cases 169 | # 170 | 171 | def test_pwd 172 | check_singleton :pwd 173 | 174 | assert_equal Dir.pwd, pwd() 175 | 176 | cwd = Dir.pwd 177 | root = have_drive_letter? ? 'C:/' : '/' 178 | cd(root) { 179 | assert_equal root, pwd() 180 | } 181 | assert_equal cwd, pwd() 182 | end 183 | 184 | def test_cmp 185 | check_singleton :cmp 186 | 187 | TARGETS.each do |fname| 188 | assert cmp(fname, fname), 'not same?' 189 | end 190 | assert_raise(ArgumentError) { 191 | cmp TARGETS[0], TARGETS[0], :undefinedoption => true 192 | } 193 | 194 | # pathname 195 | touch 'tmp/cmptmp' 196 | assert_nothing_raised { 197 | cmp Pathname.new('tmp/cmptmp'), 'tmp/cmptmp' 198 | cmp 'tmp/cmptmp', Pathname.new('tmp/cmptmp') 199 | cmp Pathname.new('tmp/cmptmp'), Pathname.new('tmp/cmptmp') 200 | } 201 | end 202 | 203 | def test_cp 204 | check_singleton :cp 205 | 206 | each_srcdest do |srcpath, destpath| 207 | cp srcpath, destpath 208 | assert_same_file srcpath, destpath 209 | 210 | cp srcpath, File.dirname(destpath) 211 | assert_same_file srcpath, destpath 212 | 213 | cp srcpath, File.dirname(destpath) + '/' 214 | assert_same_file srcpath, destpath 215 | 216 | cp srcpath, destpath, :preserve => true 217 | assert_same_file srcpath, destpath 218 | assert_same_entry srcpath, destpath 219 | end 220 | 221 | assert_raise(Errno::ENOENT) { 222 | cp 'tmp/cptmp', 'tmp/cptmp_new' 223 | } 224 | assert_file_not_exist('tmp/cptmp_new') 225 | 226 | # src==dest (1) same path 227 | touch 'tmp/cptmp' 228 | assert_raise(ArgumentError) { 229 | cp 'tmp/cptmp', 'tmp/cptmp' 230 | } 231 | end 232 | 233 | def test_cp_preserve_permissions 234 | bug4507 = '[ruby-core:35518]' 235 | touch 'tmp/cptmp' 236 | chmod 0755, 'tmp/cptmp' 237 | cp 'tmp/cptmp', 'tmp/cptmp2' 238 | assert_equal(File.stat('tmp/cptmp').mode, 239 | File.stat('tmp/cptmp2').mode, 240 | bug4507) 241 | end 242 | 243 | def test_cp_preserve_permissions_dir 244 | bug7246 = '[ruby-core:48603]' 245 | mkdir 'tmp/cptmp' 246 | mkdir 'tmp/cptmp/d1' 247 | chmod 0745, 'tmp/cptmp/d1' 248 | mkdir 'tmp/cptmp/d2' 249 | chmod 0700, 'tmp/cptmp/d2' 250 | cp_r 'tmp/cptmp', 'tmp/cptmp2', :preserve => true 251 | assert_equal(File.stat('tmp/cptmp/d1').mode, 252 | File.stat('tmp/cptmp2/d1').mode, 253 | bug7246) 254 | assert_equal(File.stat('tmp/cptmp/d2').mode, 255 | File.stat('tmp/cptmp2/d2').mode, 256 | bug7246) 257 | end 258 | 259 | def test_cp_symlink 260 | touch 'tmp/cptmp' 261 | # src==dest (2) symlink and its target 262 | File.symlink 'cptmp', 'tmp/cptmp_symlink' 263 | assert_raise(ArgumentError) { 264 | cp 'tmp/cptmp', 'tmp/cptmp_symlink' 265 | } 266 | assert_raise(ArgumentError) { 267 | cp 'tmp/cptmp_symlink', 'tmp/cptmp' 268 | } 269 | # src==dest (3) looped symlink 270 | File.symlink 'symlink', 'tmp/symlink' 271 | assert_raise(Errno::ELOOP) { 272 | cp 'tmp/symlink', 'tmp/symlink' 273 | } 274 | end if have_symlink? 275 | 276 | def test_cp_pathname 277 | # pathname 278 | touch 'tmp/cptmp' 279 | assert_nothing_raised { 280 | cp 'tmp/cptmp', Pathname.new('tmp/tmpdest') 281 | cp Pathname.new('tmp/cptmp'), 'tmp/tmpdest' 282 | cp Pathname.new('tmp/cptmp'), Pathname.new('tmp/tmpdest') 283 | mkdir 'tmp/tmpdir' 284 | cp ['tmp/cptmp', 'tmp/tmpdest'], Pathname.new('tmp/tmpdir') 285 | } 286 | end 287 | 288 | def test_cp_r 289 | check_singleton :cp_r 290 | 291 | cp_r 'data', 'tmp' 292 | TARGETS.each do |fname| 293 | assert_same_file fname, "tmp/#{fname}" 294 | end 295 | 296 | cp_r 'data', 'tmp2', :preserve => true 297 | TARGETS.each do |fname| 298 | assert_same_entry fname, "tmp2/#{File.basename(fname)}" 299 | assert_same_file fname, "tmp2/#{File.basename(fname)}" 300 | end 301 | 302 | # a/* -> b/* 303 | mkdir 'tmp/cpr_src' 304 | mkdir 'tmp/cpr_dest' 305 | File.open('tmp/cpr_src/a', 'w') {|f| f.puts 'a' } 306 | File.open('tmp/cpr_src/b', 'w') {|f| f.puts 'b' } 307 | File.open('tmp/cpr_src/c', 'w') {|f| f.puts 'c' } 308 | mkdir 'tmp/cpr_src/d' 309 | cp_r 'tmp/cpr_src/.', 'tmp/cpr_dest' 310 | assert_same_file 'tmp/cpr_src/a', 'tmp/cpr_dest/a' 311 | assert_same_file 'tmp/cpr_src/b', 'tmp/cpr_dest/b' 312 | assert_same_file 'tmp/cpr_src/c', 'tmp/cpr_dest/c' 313 | assert_directory 'tmp/cpr_dest/d' 314 | my_rm_rf 'tmp/cpr_src' 315 | my_rm_rf 'tmp/cpr_dest' 316 | 317 | bug3588 = '[ruby-core:31360]' 318 | assert_nothing_raised(ArgumentError, bug3588) do 319 | cp_r 'tmp', 'tmp2' 320 | end 321 | assert_directory 'tmp2/tmp' 322 | assert_raise(ArgumentError, bug3588) do 323 | cp_r 'tmp2', 'tmp2/new_tmp2' 324 | end 325 | end 326 | 327 | def test_cp_r_symlink 328 | # symlink in a directory 329 | mkdir 'tmp/cpr_src' 330 | ln_s 'SLdest', 'tmp/cpr_src/symlink' 331 | cp_r 'tmp/cpr_src', 'tmp/cpr_dest' 332 | assert_symlink 'tmp/cpr_dest/symlink' 333 | assert_equal 'SLdest', File.readlink('tmp/cpr_dest/symlink') 334 | 335 | # root is a symlink 336 | ln_s 'cpr_src', 'tmp/cpr_src2' 337 | cp_r 'tmp/cpr_src2', 'tmp/cpr_dest2' 338 | assert_directory 'tmp/cpr_dest2' 339 | assert_not_symlink 'tmp/cpr_dest2' 340 | assert_symlink 'tmp/cpr_dest2/symlink' 341 | assert_equal 'SLdest', File.readlink('tmp/cpr_dest2/symlink') 342 | end if have_symlink? 343 | 344 | def test_cp_r_symlink_preserve 345 | mkdir 'tmp/cross' 346 | mkdir 'tmp/cross/a' 347 | mkdir 'tmp/cross/b' 348 | touch 'tmp/cross/a/f' 349 | touch 'tmp/cross/b/f' 350 | ln_s '../a/f', 'tmp/cross/b/l' 351 | ln_s '../b/f', 'tmp/cross/a/l' 352 | assert_nothing_raised { 353 | cp_r 'tmp/cross', 'tmp/cross2', :preserve => true 354 | } 355 | end if have_symlink? 356 | 357 | def test_cp_r_pathname 358 | # pathname 359 | touch 'tmp/cprtmp' 360 | assert_nothing_raised { 361 | cp_r Pathname.new('tmp/cprtmp'), 'tmp/tmpdest' 362 | cp_r 'tmp/cprtmp', Pathname.new('tmp/tmpdest') 363 | cp_r Pathname.new('tmp/cprtmp'), Pathname.new('tmp/tmpdest') 364 | } 365 | end 366 | 367 | def test_mv 368 | check_singleton :mv 369 | 370 | mkdir 'tmp/dest' 371 | TARGETS.each do |fname| 372 | cp fname, 'tmp/mvsrc' 373 | mv 'tmp/mvsrc', 'tmp/mvdest' 374 | assert_same_file fname, 'tmp/mvdest' 375 | 376 | mv 'tmp/mvdest', 'tmp/dest/' 377 | assert_same_file fname, 'tmp/dest/mvdest' 378 | 379 | mv 'tmp/dest/mvdest', 'tmp' 380 | assert_same_file fname, 'tmp/mvdest' 381 | end 382 | 383 | mkdir 'tmp/tmpdir' 384 | mkdir_p 'tmp/dest2/tmpdir' 385 | assert_raise(Errno::EEXIST) { 386 | mv 'tmp/tmpdir', 'tmp/dest2' 387 | } 388 | mkdir 'tmp/dest2/tmpdir/junk' 389 | assert_raise(Errno::EEXIST, "[ruby-talk:124368]") { 390 | mv 'tmp/tmpdir', 'tmp/dest2' 391 | } 392 | 393 | # src==dest (1) same path 394 | touch 'tmp/cptmp' 395 | assert_raise(ArgumentError) { 396 | mv 'tmp/cptmp', 'tmp/cptmp' 397 | } 398 | end 399 | 400 | def test_mv_symlink 401 | touch 'tmp/cptmp' 402 | # src==dest (2) symlink and its target 403 | File.symlink 'cptmp', 'tmp/cptmp_symlink' 404 | assert_raise(ArgumentError) { 405 | mv 'tmp/cptmp', 'tmp/cptmp_symlink' 406 | } 407 | assert_raise(ArgumentError) { 408 | mv 'tmp/cptmp_symlink', 'tmp/cptmp' 409 | } 410 | # src==dest (3) looped symlink 411 | File.symlink 'symlink', 'tmp/symlink' 412 | assert_raise(Errno::ELOOP) { 413 | mv 'tmp/symlink', 'tmp/symlink' 414 | } 415 | end if have_symlink? 416 | 417 | def test_mv_pathname 418 | # pathname 419 | assert_nothing_raised { 420 | touch 'tmp/mvtmpsrc' 421 | mv Pathname.new('tmp/mvtmpsrc'), 'tmp/mvtmpdest' 422 | touch 'tmp/mvtmpsrc' 423 | mv 'tmp/mvtmpsrc', Pathname.new('tmp/mvtmpdest') 424 | touch 'tmp/mvtmpsrc' 425 | mv Pathname.new('tmp/mvtmpsrc'), Pathname.new('tmp/mvtmpdest') 426 | } 427 | end 428 | 429 | def test_rm 430 | check_singleton :rm 431 | 432 | TARGETS.each do |fname| 433 | cp fname, 'tmp/rmsrc' 434 | rm 'tmp/rmsrc' 435 | assert_file_not_exist 'tmp/rmsrc' 436 | end 437 | 438 | # pathname 439 | touch 'tmp/rmtmp1' 440 | touch 'tmp/rmtmp2' 441 | touch 'tmp/rmtmp3' 442 | assert_nothing_raised { 443 | rm Pathname.new('tmp/rmtmp1') 444 | rm [Pathname.new('tmp/rmtmp2'), Pathname.new('tmp/rmtmp3')] 445 | } 446 | assert_file_not_exist 'tmp/rmtmp1' 447 | assert_file_not_exist 'tmp/rmtmp2' 448 | assert_file_not_exist 'tmp/rmtmp3' 449 | end 450 | 451 | def test_rm_f 452 | check_singleton :rm_f 453 | 454 | TARGETS.each do |fname| 455 | cp fname, 'tmp/rmsrc' 456 | rm_f 'tmp/rmsrc' 457 | assert_file_not_exist 'tmp/rmsrc' 458 | end 459 | end 460 | 461 | def test_rm_symlink 462 | File.open('tmp/lnf_symlink_src', 'w') {|f| f.puts 'dummy' } 463 | File.symlink 'tmp/lnf_symlink_src', 'tmp/lnf_symlink_dest' 464 | rm_f 'tmp/lnf_symlink_dest' 465 | assert_file_not_exist 'tmp/lnf_symlink_dest' 466 | assert_file_exist 'tmp/lnf_symlink_src' 467 | 468 | rm_f 'notexistdatafile' 469 | rm_f 'tmp/notexistdatafile' 470 | my_rm_rf 'tmpdatadir' 471 | Dir.mkdir 'tmpdatadir' 472 | # rm_f 'tmpdatadir' 473 | Dir.rmdir 'tmpdatadir' 474 | end if have_symlink? 475 | 476 | def test_rm_f_2 477 | Dir.mkdir 'tmp/tmpdir' 478 | File.open('tmp/tmpdir/a', 'w') {|f| f.puts 'dummy' } 479 | File.open('tmp/tmpdir/c', 'w') {|f| f.puts 'dummy' } 480 | rm_f ['tmp/tmpdir/a', 'tmp/tmpdir/b', 'tmp/tmpdir/c'] 481 | assert_file_not_exist 'tmp/tmpdir/a' 482 | assert_file_not_exist 'tmp/tmpdir/c' 483 | Dir.rmdir 'tmp/tmpdir' 484 | end 485 | 486 | def test_rm_pathname 487 | # pathname 488 | touch 'tmp/rmtmp1' 489 | touch 'tmp/rmtmp2' 490 | touch 'tmp/rmtmp3' 491 | touch 'tmp/rmtmp4' 492 | assert_nothing_raised { 493 | rm_f Pathname.new('tmp/rmtmp1') 494 | rm_f [Pathname.new('tmp/rmtmp2'), Pathname.new('tmp/rmtmp3')] 495 | } 496 | assert_file_not_exist 'tmp/rmtmp1' 497 | assert_file_not_exist 'tmp/rmtmp2' 498 | assert_file_not_exist 'tmp/rmtmp3' 499 | assert_file_exist 'tmp/rmtmp4' 500 | 501 | # [ruby-dev:39345] 502 | touch 'tmp/[rmtmp]' 503 | FileUtils2.rm_f 'tmp/[rmtmp]' 504 | assert_file_not_exist 'tmp/[rmtmp]' 505 | end 506 | 507 | def test_rm_r 508 | check_singleton :rm_r 509 | 510 | my_rm_rf 'tmpdatadir' 511 | 512 | Dir.mkdir 'tmpdatadir' 513 | rm_r 'tmpdatadir' 514 | assert_file_not_exist 'tmpdatadir' 515 | 516 | Dir.mkdir 'tmpdatadir' 517 | rm_r 'tmpdatadir/' 518 | assert_file_not_exist 'tmpdatadir' 519 | 520 | Dir.mkdir 'tmp/tmpdir' 521 | rm_r 'tmp/tmpdir/' 522 | assert_file_not_exist 'tmp/tmpdir' 523 | assert_file_exist 'tmp' 524 | 525 | Dir.mkdir 'tmp/tmpdir' 526 | rm_r 'tmp/tmpdir' 527 | assert_file_not_exist 'tmp/tmpdir' 528 | assert_file_exist 'tmp' 529 | 530 | Dir.mkdir 'tmp/tmpdir' 531 | File.open('tmp/tmpdir/a', 'w') {|f| f.puts 'dummy' } 532 | File.open('tmp/tmpdir/b', 'w') {|f| f.puts 'dummy' } 533 | File.open('tmp/tmpdir/c', 'w') {|f| f.puts 'dummy' } 534 | rm_r 'tmp/tmpdir' 535 | assert_file_not_exist 'tmp/tmpdir' 536 | assert_file_exist 'tmp' 537 | 538 | Dir.mkdir 'tmp/tmpdir' 539 | File.open('tmp/tmpdir/a', 'w') {|f| f.puts 'dummy' } 540 | File.open('tmp/tmpdir/c', 'w') {|f| f.puts 'dummy' } 541 | rm_r ['tmp/tmpdir/a', 'tmp/tmpdir/b', 'tmp/tmpdir/c'], :force => true 542 | assert_file_not_exist 'tmp/tmpdir/a' 543 | assert_file_not_exist 'tmp/tmpdir/c' 544 | Dir.rmdir 'tmp/tmpdir' 545 | end 546 | 547 | def test_rm_r_symlink 548 | # [ruby-talk:94635] a symlink to the directory 549 | Dir.mkdir 'tmp/tmpdir' 550 | File.symlink '..', 'tmp/tmpdir/symlink_to_dir' 551 | rm_r 'tmp/tmpdir' 552 | assert_file_not_exist 'tmp/tmpdir' 553 | assert_file_exist 'tmp' 554 | end if have_symlink? 555 | 556 | def test_rm_r_pathname 557 | # pathname 558 | Dir.mkdir 'tmp/tmpdir1'; touch 'tmp/tmpdir1/tmp' 559 | Dir.mkdir 'tmp/tmpdir2'; touch 'tmp/tmpdir2/tmp' 560 | Dir.mkdir 'tmp/tmpdir3'; touch 'tmp/tmpdir3/tmp' 561 | assert_nothing_raised { 562 | rm_r Pathname.new('tmp/tmpdir1') 563 | rm_r [Pathname.new('tmp/tmpdir2'), Pathname.new('tmp/tmpdir3')] 564 | } 565 | assert_file_not_exist 'tmp/tmpdir1' 566 | assert_file_not_exist 'tmp/tmpdir2' 567 | assert_file_not_exist 'tmp/tmpdir3' 568 | end 569 | 570 | def test_remove_entry_secure 571 | check_singleton :remove_entry_secure 572 | 573 | my_rm_rf 'tmpdatadir' 574 | 575 | Dir.mkdir 'tmpdatadir' 576 | remove_entry_secure 'tmpdatadir' 577 | assert_file_not_exist 'tmpdatadir' 578 | 579 | Dir.mkdir 'tmpdatadir' 580 | remove_entry_secure 'tmpdatadir/' 581 | assert_file_not_exist 'tmpdatadir' 582 | 583 | Dir.mkdir 'tmp/tmpdir' 584 | remove_entry_secure 'tmp/tmpdir/' 585 | assert_file_not_exist 'tmp/tmpdir' 586 | assert_file_exist 'tmp' 587 | 588 | Dir.mkdir 'tmp/tmpdir' 589 | remove_entry_secure 'tmp/tmpdir' 590 | assert_file_not_exist 'tmp/tmpdir' 591 | assert_file_exist 'tmp' 592 | 593 | Dir.mkdir 'tmp/tmpdir' 594 | File.open('tmp/tmpdir/a', 'w') {|f| f.puts 'dummy' } 595 | File.open('tmp/tmpdir/b', 'w') {|f| f.puts 'dummy' } 596 | File.open('tmp/tmpdir/c', 'w') {|f| f.puts 'dummy' } 597 | remove_entry_secure 'tmp/tmpdir' 598 | assert_file_not_exist 'tmp/tmpdir' 599 | assert_file_exist 'tmp' 600 | 601 | Dir.mkdir 'tmp/tmpdir' 602 | File.open('tmp/tmpdir/a', 'w') {|f| f.puts 'dummy' } 603 | File.open('tmp/tmpdir/c', 'w') {|f| f.puts 'dummy' } 604 | remove_entry_secure 'tmp/tmpdir/a', true 605 | remove_entry_secure 'tmp/tmpdir/b', true 606 | remove_entry_secure 'tmp/tmpdir/c', true 607 | assert_file_not_exist 'tmp/tmpdir/a' 608 | assert_file_not_exist 'tmp/tmpdir/c' 609 | Dir.rmdir 'tmp/tmpdir' 610 | end 611 | 612 | def test_remove_entry_secure_symlink 613 | # [ruby-talk:94635] a symlink to the directory 614 | Dir.mkdir 'tmp/tmpdir' 615 | File.symlink '..', 'tmp/tmpdir/symlink_to_dir' 616 | remove_entry_secure 'tmp/tmpdir' 617 | assert_file_not_exist 'tmp/tmpdir' 618 | assert_file_exist 'tmp' 619 | end if have_symlink? 620 | 621 | def test_remove_entry_secure_pathname 622 | # pathname 623 | Dir.mkdir 'tmp/tmpdir1'; touch 'tmp/tmpdir1/tmp' 624 | assert_nothing_raised { 625 | remove_entry_secure Pathname.new('tmp/tmpdir1') 626 | } 627 | assert_file_not_exist 'tmp/tmpdir1' 628 | end 629 | 630 | def test_with_big_file 631 | prepare_big_file 632 | 633 | cp BIGFILE, 'tmp/cpdest' 634 | assert_same_file BIGFILE, 'tmp/cpdest' 635 | assert cmp(BIGFILE, 'tmp/cpdest'), 'orig != copied' 636 | 637 | mv 'tmp/cpdest', 'tmp/mvdest' 638 | assert_same_file BIGFILE, 'tmp/mvdest' 639 | assert_file_not_exist 'tmp/cpdest' 640 | 641 | rm 'tmp/mvdest' 642 | assert_file_not_exist 'tmp/mvdest' 643 | end 644 | 645 | def test_ln 646 | TARGETS.each do |fname| 647 | ln fname, 'tmp/lndest' 648 | assert_same_file fname, 'tmp/lndest' 649 | File.unlink 'tmp/lndest' 650 | end 651 | 652 | ln TARGETS, 'tmp' 653 | TARGETS.each do |fname| 654 | assert_same_file fname, 'tmp/' + File.basename(fname) 655 | end 656 | TARGETS.each do |fname| 657 | File.unlink 'tmp/' + File.basename(fname) 658 | end 659 | 660 | # src==dest (1) same path 661 | touch 'tmp/cptmp' 662 | assert_raise(Errno::EEXIST) { 663 | ln 'tmp/cptmp', 'tmp/cptmp' 664 | } 665 | end if have_hardlink? 666 | 667 | def test_ln_symlink 668 | touch 'tmp/cptmp' 669 | # src==dest (2) symlink and its target 670 | File.symlink 'cptmp', 'tmp/symlink' 671 | assert_raise(Errno::EEXIST) { 672 | ln 'tmp/cptmp', 'tmp/symlink' # normal file -> symlink 673 | } 674 | assert_raise(Errno::EEXIST) { 675 | ln 'tmp/symlink', 'tmp/cptmp' # symlink -> normal file 676 | } 677 | # src==dest (3) looped symlink 678 | File.symlink 'cptmp_symlink', 'tmp/cptmp_symlink' 679 | begin 680 | ln 'tmp/cptmp_symlink', 'tmp/cptmp_symlink' 681 | rescue => err 682 | assert_kind_of SystemCallError, err 683 | end 684 | end if have_symlink? 685 | 686 | def test_ln_pathname 687 | # pathname 688 | touch 'tmp/lntmp' 689 | assert_nothing_raised { 690 | ln Pathname.new('tmp/lntmp'), 'tmp/lndesttmp1' 691 | ln 'tmp/lntmp', Pathname.new('tmp/lndesttmp2') 692 | ln Pathname.new('tmp/lntmp'), Pathname.new('tmp/lndesttmp3') 693 | } 694 | end if have_hardlink? 695 | 696 | def test_ln_s 697 | check_singleton :ln_s 698 | 699 | TARGETS.each do |fname| 700 | ln_s fname, 'tmp/lnsdest' 701 | assert FileTest.symlink?('tmp/lnsdest'), 'not symlink' 702 | assert_equal fname, File.readlink('tmp/lnsdest') 703 | rm_f 'tmp/lnsdest' 704 | end 705 | assert_nothing_raised { 706 | ln_s 'symlink', 'tmp/symlink' 707 | } 708 | assert_symlink 'tmp/symlink' 709 | 710 | # pathname 711 | touch 'tmp/lnsdest' 712 | assert_nothing_raised { 713 | ln_s Pathname.new('lnsdest'), 'tmp/symlink_tmp1' 714 | ln_s 'lnsdest', Pathname.new('tmp/symlink_tmp2') 715 | ln_s Pathname.new('lnsdest'), Pathname.new('tmp/symlink_tmp3') 716 | } 717 | end if have_symlink? 718 | 719 | def test_ln_sf 720 | check_singleton :ln_sf 721 | 722 | TARGETS.each do |fname| 723 | ln_sf fname, 'tmp/lnsdest' 724 | assert FileTest.symlink?('tmp/lnsdest'), 'not symlink' 725 | assert_equal fname, File.readlink('tmp/lnsdest') 726 | ln_sf fname, 'tmp/lnsdest' 727 | ln_sf fname, 'tmp/lnsdest' 728 | end 729 | assert_nothing_raised { 730 | ln_sf 'symlink', 'tmp/symlink' 731 | } 732 | 733 | # pathname 734 | touch 'tmp/lns_dest' 735 | assert_nothing_raised { 736 | ln_sf Pathname.new('lns_dest'), 'tmp/symlink_tmp1' 737 | ln_sf 'lns_dest', Pathname.new('tmp/symlink_tmp2') 738 | ln_sf Pathname.new('lns_dest'), Pathname.new('tmp/symlink_tmp3') 739 | } 740 | end if have_symlink? 741 | 742 | def test_mkdir 743 | check_singleton :mkdir 744 | 745 | my_rm_rf 'tmpdatadir' 746 | mkdir 'tmpdatadir' 747 | assert_directory 'tmpdatadir' 748 | Dir.rmdir 'tmpdatadir' 749 | 750 | mkdir 'tmpdatadir/' 751 | assert_directory 'tmpdatadir' 752 | Dir.rmdir 'tmpdatadir' 753 | 754 | mkdir 'tmp/mkdirdest' 755 | assert_directory 'tmp/mkdirdest' 756 | Dir.rmdir 'tmp/mkdirdest' 757 | 758 | mkdir 'tmp/tmp', :mode => 0700 759 | assert_directory 'tmp/tmp' 760 | assert_equal 0700, (File.stat('tmp/tmp').mode & 0777) if have_file_perm? 761 | Dir.rmdir 'tmp/tmp' 762 | end 763 | 764 | def test_mkdir_file_perm 765 | mkdir 'tmp/tmp', :mode => 07777 766 | assert_directory 'tmp/tmp' 767 | assert_equal 07777, (File.stat('tmp/tmp').mode & 07777) 768 | Dir.rmdir 'tmp/tmp' 769 | end if have_file_perm? 770 | 771 | def test_mkdir_lf_in_path 772 | mkdir "tmp-first-line\ntmp-second-line" 773 | assert_directory "tmp-first-line\ntmp-second-line" 774 | Dir.rmdir "tmp-first-line\ntmp-second-line" 775 | end if lf_in_path_allowed? 776 | 777 | def test_mkdir_pathname 778 | # pathname 779 | assert_nothing_raised { 780 | mkdir Pathname.new('tmp/tmpdirtmp') 781 | mkdir [Pathname.new('tmp/tmpdirtmp2'), Pathname.new('tmp/tmpdirtmp3')] 782 | } 783 | end 784 | 785 | def test_mkdir_p 786 | check_singleton :mkdir_p 787 | 788 | dirs = %w( 789 | tmpdir/dir/ 790 | tmpdir/dir/./ 791 | tmpdir/dir/./.././dir/ 792 | tmpdir/a 793 | tmpdir/a/ 794 | tmpdir/a/b 795 | tmpdir/a/b/ 796 | tmpdir/a/b/c/ 797 | tmpdir/a/b/c 798 | tmpdir/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a 799 | tmpdir/a/a 800 | ) 801 | my_rm_rf 'tmpdir' 802 | dirs.each do |d| 803 | mkdir_p d 804 | assert_directory d 805 | assert_file_not_exist "#{d}/a" 806 | assert_file_not_exist "#{d}/b" 807 | assert_file_not_exist "#{d}/c" 808 | my_rm_rf 'tmpdir' 809 | end 810 | dirs.each do |d| 811 | mkdir_p d 812 | assert_directory d 813 | end 814 | rm_rf 'tmpdir' 815 | dirs.each do |d| 816 | mkdir_p "#{Dir.pwd}/#{d}" 817 | assert_directory d 818 | end 819 | rm_rf 'tmpdir' 820 | 821 | mkdir_p 'tmp/tmp/tmp', :mode => 0700 822 | assert_directory 'tmp/tmp' 823 | assert_directory 'tmp/tmp/tmp' 824 | assert_equal 0700, (File.stat('tmp/tmp').mode & 0777) if have_file_perm? 825 | assert_equal 0700, (File.stat('tmp/tmp/tmp').mode & 0777) if have_file_perm? 826 | rm_rf 'tmp/tmp' 827 | 828 | mkdir_p 'tmp/tmp', :mode => 0 829 | assert_directory 'tmp/tmp' 830 | assert_equal 0, (File.stat('tmp/tmp').mode & 0777) if have_file_perm? 831 | # DO NOT USE rm_rf here. 832 | # (rm(1) try to chdir to parent directory, it fails to remove directory.) 833 | Dir.rmdir 'tmp/tmp' 834 | Dir.rmdir 'tmp' 835 | end 836 | 837 | def test_mkdir_p_file_perm 838 | mkdir_p 'tmp/tmp/tmp', :mode => 07777 839 | assert_directory 'tmp/tmp/tmp' 840 | assert_equal 07777, (File.stat('tmp/tmp/tmp').mode & 07777) 841 | Dir.rmdir 'tmp/tmp/tmp' 842 | Dir.rmdir 'tmp/tmp' 843 | end if have_file_perm? 844 | 845 | def test_mkdir_p_pathname 846 | # pathname 847 | assert_nothing_raised { 848 | mkdir_p Pathname.new('tmp/tmp/tmp') 849 | } 850 | end 851 | 852 | def test_install 853 | check_singleton :install 854 | 855 | File.open('tmp/aaa', 'w') {|f| f.puts 'aaa' } 856 | File.open('tmp/bbb', 'w') {|f| f.puts 'bbb' } 857 | install 'tmp/aaa', 'tmp/bbb', :mode => 0600 858 | assert_equal "aaa\n", File.read('tmp/bbb') 859 | assert_equal 0600, (File.stat('tmp/bbb').mode & 0777) if have_file_perm? 860 | 861 | t = File.mtime('tmp/bbb') 862 | install 'tmp/aaa', 'tmp/bbb' 863 | assert_equal "aaa\n", File.read('tmp/bbb') 864 | assert_equal 0600, (File.stat('tmp/bbb').mode & 0777) if have_file_perm? 865 | assert_equal_time t, File.mtime('tmp/bbb') 866 | 867 | File.unlink 'tmp/aaa' 868 | File.unlink 'tmp/bbb' 869 | 870 | # src==dest (1) same path 871 | touch 'tmp/cptmp' 872 | assert_raise(ArgumentError) { 873 | install 'tmp/cptmp', 'tmp/cptmp' 874 | } 875 | end 876 | 877 | def test_install_symlink 878 | touch 'tmp/cptmp' 879 | # src==dest (2) symlink and its target 880 | File.symlink 'cptmp', 'tmp/cptmp_symlink' 881 | assert_raise(ArgumentError) { 882 | install 'tmp/cptmp', 'tmp/cptmp_symlink' 883 | } 884 | assert_raise(ArgumentError) { 885 | install 'tmp/cptmp_symlink', 'tmp/cptmp' 886 | } 887 | # src==dest (3) looped symlink 888 | File.symlink 'symlink', 'tmp/symlink' 889 | assert_raise(Errno::ELOOP) { 890 | # File#install invokes open(2), always ELOOP must be raised 891 | install 'tmp/symlink', 'tmp/symlink' 892 | } 893 | end if have_symlink? 894 | 895 | def test_install_pathname 896 | # pathname 897 | assert_nothing_raised { 898 | rm_f 'tmp/a'; touch 'tmp/a' 899 | install 'tmp/a', Pathname.new('tmp/b') 900 | rm_f 'tmp/a'; touch 'tmp/a' 901 | install Pathname.new('tmp/a'), 'tmp/b' 902 | rm_f 'tmp/a'; touch 'tmp/a' 903 | install Pathname.new('tmp/a'), Pathname.new('tmp/b') 904 | rm_f 'tmp/a' 905 | touch 'tmp/a' 906 | touch 'tmp/b' 907 | mkdir 'tmp/dest' 908 | install [Pathname.new('tmp/a'), Pathname.new('tmp/b')], 'tmp/dest' 909 | my_rm_rf 'tmp/dest' 910 | mkdir 'tmp/dest' 911 | install [Pathname.new('tmp/a'), Pathname.new('tmp/b')], Pathname.new('tmp/dest') 912 | } 913 | end 914 | 915 | def test_chmod 916 | check_singleton :chmod 917 | 918 | touch 'tmp/a' 919 | chmod 0700, 'tmp/a' 920 | assert_equal 0700, File.stat('tmp/a').mode & 0777 921 | chmod 0500, 'tmp/a' 922 | assert_equal 0500, File.stat('tmp/a').mode & 0777 923 | end if have_file_perm? 924 | 925 | def test_chmod_symbol_mode 926 | check_singleton :chmod 927 | 928 | touch 'tmp/a' 929 | chmod "u=wrx,g=,o=", 'tmp/a' 930 | assert_equal 0700, File.stat('tmp/a').mode & 0777 931 | chmod "u=rx,go=", 'tmp/a' 932 | assert_equal 0500, File.stat('tmp/a').mode & 0777 933 | chmod "+wrx", 'tmp/a' 934 | assert_equal 0777, File.stat('tmp/a').mode & 0777 935 | chmod "u+s,o=s", 'tmp/a' 936 | assert_equal 04770, File.stat('tmp/a').mode & 07777 937 | chmod "u-w,go-wrx", 'tmp/a' 938 | assert_equal 04500, File.stat('tmp/a').mode & 07777 939 | chmod "+s", 'tmp/a' 940 | assert_equal 06500, File.stat('tmp/a').mode & 07777 941 | 942 | # FreeBSD ufs and tmpfs don't allow to change sticky bit against 943 | # regular file. It's slightly strange. Anyway it's no effect bit. 944 | # see /usr/src/sys/ufs/ufs/ufs_chmod() 945 | # NetBSD, OpenBSD and Solaris also denies it. 946 | if /freebsd|netbsd|openbsd|solaris/ !~ RUBY_PLATFORM 947 | chmod "u+t,o+t", 'tmp/a' 948 | assert_equal 07500, File.stat('tmp/a').mode & 07777 949 | chmod "a-t,a-s", 'tmp/a' 950 | assert_equal 0500, File.stat('tmp/a').mode & 07777 951 | end 952 | 953 | end if have_file_perm? 954 | 955 | 956 | def test_chmod_R 957 | check_singleton :chmod_R 958 | 959 | mkdir_p 'tmp/dir/dir' 960 | touch %w( tmp/dir/file tmp/dir/dir/file ) 961 | chmod_R 0700, 'tmp/dir' 962 | assert_equal 0700, File.stat('tmp/dir').mode & 0777 963 | assert_equal 0700, File.stat('tmp/dir/file').mode & 0777 964 | assert_equal 0700, File.stat('tmp/dir/dir').mode & 0777 965 | assert_equal 0700, File.stat('tmp/dir/dir/file').mode & 0777 966 | chmod_R 0500, 'tmp/dir' 967 | assert_equal 0500, File.stat('tmp/dir').mode & 0777 968 | assert_equal 0500, File.stat('tmp/dir/file').mode & 0777 969 | assert_equal 0500, File.stat('tmp/dir/dir').mode & 0777 970 | assert_equal 0500, File.stat('tmp/dir/dir/file').mode & 0777 971 | chmod_R 0700, 'tmp/dir' # to remove 972 | end if have_file_perm? 973 | 974 | def test_chmod_symbol_mode_R 975 | check_singleton :chmod_R 976 | 977 | mkdir_p 'tmp/dir/dir' 978 | touch %w( tmp/dir/file tmp/dir/dir/file ) 979 | chmod_R "u=wrx,g=,o=", 'tmp/dir' 980 | assert_equal 0700, File.stat('tmp/dir').mode & 0777 981 | assert_equal 0700, File.stat('tmp/dir/file').mode & 0777 982 | assert_equal 0700, File.stat('tmp/dir/dir').mode & 0777 983 | assert_equal 0700, File.stat('tmp/dir/dir/file').mode & 0777 984 | chmod_R "u=xr,g+X,o=", 'tmp/dir' 985 | assert_equal 0510, File.stat('tmp/dir').mode & 0777 986 | assert_equal 0500, File.stat('tmp/dir/file').mode & 0777 987 | assert_equal 0510, File.stat('tmp/dir/dir').mode & 0777 988 | assert_equal 0500, File.stat('tmp/dir/dir/file').mode & 0777 989 | chmod_R 0700, 'tmp/dir' # to remove 990 | end if have_file_perm? 991 | 992 | def test_chmod_verbose 993 | check_singleton :chmod 994 | 995 | r, w = IO.pipe 996 | stderr_back = $stderr 997 | read, $stderr = IO.pipe 998 | th = Thread.new { read.read } 999 | 1000 | touch 'tmp/a' 1001 | chmod 0700, 'tmp/a', :verbose => true 1002 | assert_equal 0700, File.stat('tmp/a').mode & 0777 1003 | chmod 0500, 'tmp/a', :verbose => true 1004 | assert_equal 0500, File.stat('tmp/a').mode & 0777 1005 | 1006 | $stderr.close 1007 | lines = th.value.lines.map {|l| l.chomp } 1008 | assert_equal(["chmod 700 tmp/a", "chmod 500 tmp/a"], lines) 1009 | ensure 1010 | $stderr = stderr_back if stderr_back 1011 | end if have_file_perm? 1012 | 1013 | # FIXME: How can I test this method? 1014 | def test_chown 1015 | check_singleton :chown 1016 | end if have_file_perm? 1017 | 1018 | # FIXME: How can I test this method? 1019 | def test_chown_R 1020 | check_singleton :chown_R 1021 | end if have_file_perm? 1022 | 1023 | def test_copy_entry 1024 | check_singleton :copy_entry 1025 | 1026 | each_srcdest do |srcpath, destpath| 1027 | copy_entry srcpath, destpath 1028 | assert_same_file srcpath, destpath 1029 | assert_equal File.stat(srcpath).ftype, File.stat(destpath).ftype 1030 | end 1031 | end 1032 | 1033 | def test_copy_entry_symlink 1034 | # root is a symlink 1035 | File.symlink 'somewhere', 'tmp/symsrc' 1036 | copy_entry 'tmp/symsrc', 'tmp/symdest' 1037 | assert_symlink 'tmp/symdest' 1038 | assert_equal 'somewhere', File.readlink('tmp/symdest') 1039 | 1040 | # content is a symlink 1041 | mkdir 'tmp/dir' 1042 | File.symlink 'somewhere', 'tmp/dir/sym' 1043 | copy_entry 'tmp/dir', 'tmp/dirdest' 1044 | assert_directory 'tmp/dirdest' 1045 | assert_not_symlink 'tmp/dirdest' 1046 | assert_symlink 'tmp/dirdest/sym' 1047 | assert_equal 'somewhere', File.readlink('tmp/dirdest/sym') 1048 | end if have_symlink? 1049 | 1050 | def test_copy_file 1051 | check_singleton :copy_file 1052 | 1053 | each_srcdest do |srcpath, destpath| 1054 | copy_file srcpath, destpath 1055 | assert_same_file srcpath, destpath 1056 | end 1057 | end 1058 | 1059 | def test_copy_stream 1060 | check_singleton :copy_stream 1061 | # IO 1062 | each_srcdest do |srcpath, destpath| 1063 | File.open(srcpath, 'rb') {|src| 1064 | File.open(destpath, 'wb') {|dest| 1065 | copy_stream src, dest 1066 | } 1067 | } 1068 | assert_same_file srcpath, destpath 1069 | end 1070 | end 1071 | 1072 | def test_copy_stream_duck 1073 | check_singleton :copy_stream 1074 | # duck typing test [ruby-dev:25369] 1075 | each_srcdest do |srcpath, destpath| 1076 | File.open(srcpath, 'rb') {|src| 1077 | File.open(destpath, 'wb') {|dest| 1078 | copy_stream Stream.new(src), Stream.new(dest) 1079 | } 1080 | } 1081 | assert_same_file srcpath, destpath 1082 | end 1083 | end 1084 | 1085 | def test_remove_file 1086 | check_singleton :remove_file 1087 | File.open('data/tmp', 'w') {|f| f.puts 'dummy' } 1088 | remove_file 'data/tmp' 1089 | assert_file_not_exist 'data/tmp' 1090 | end 1091 | 1092 | def test_remove_file_file_perm 1093 | File.open('data/tmp', 'w') {|f| f.puts 'dummy' } 1094 | File.chmod 0, 'data/tmp' 1095 | remove_file 'data/tmp' 1096 | assert_file_not_exist 'data/tmp' 1097 | end if have_file_perm? 1098 | 1099 | def test_remove_dir 1100 | check_singleton :remove_dir 1101 | Dir.mkdir 'data/tmpdir' 1102 | File.open('data/tmpdir/a', 'w') {|f| f.puts 'dummy' } 1103 | remove_dir 'data/tmpdir' 1104 | assert_file_not_exist 'data/tmpdir' 1105 | end 1106 | 1107 | def test_remove_dir_file_perm 1108 | Dir.mkdir 'data/tmpdir' 1109 | File.chmod 0555, 'data/tmpdir' 1110 | remove_dir 'data/tmpdir' 1111 | assert_file_not_exist 'data/tmpdir' 1112 | end if have_file_perm? 1113 | 1114 | def test_compare_file 1115 | check_singleton :compare_file 1116 | # FIXME 1117 | end 1118 | 1119 | def test_compare_stream 1120 | check_singleton :compare_stream 1121 | # FIXME 1122 | end 1123 | 1124 | class Stream 1125 | def initialize(f) 1126 | @f = f 1127 | end 1128 | 1129 | def read(*args) 1130 | @f.read(*args) 1131 | end 1132 | 1133 | def write(str) 1134 | @f.write str 1135 | end 1136 | end 1137 | 1138 | def test_uptodate? 1139 | check_singleton :uptodate? 1140 | prepare_time_data 1141 | Dir.chdir('data') { 1142 | assert( uptodate?('newest', %w(old newer notexist)) ) 1143 | assert( ! uptodate?('newer', %w(old newest notexist)) ) 1144 | assert( ! uptodate?('notexist', %w(old newest newer)) ) 1145 | } 1146 | 1147 | # pathname 1148 | touch 'tmp/a' 1149 | touch 'tmp/b' 1150 | touch 'tmp/c' 1151 | assert_nothing_raised { 1152 | uptodate? Pathname.new('tmp/a'), ['tmp/b', 'tmp/c'] 1153 | uptodate? 'tmp/a', [Pathname.new('tmp/b'), 'tmp/c'] 1154 | uptodate? 'tmp/a', ['tmp/b', Pathname.new('tmp/c')] 1155 | uptodate? Pathname.new('tmp/a'), [Pathname.new('tmp/b'), Pathname.new('tmp/c')] 1156 | } 1157 | # [Bug #6708] [ruby-core:46256] 1158 | assert_raises_with_message(ArgumentError, "wrong number of arguments (3 for 2)") { 1159 | uptodate?('new',['old', 'oldest'], {}) 1160 | } 1161 | end 1162 | 1163 | def assert_raises_with_message(klass, message) 1164 | begin 1165 | yield 1166 | flunk("Expected Exception #{klass} didn't raise") 1167 | rescue klass => ex 1168 | if message.kind_of? String 1169 | flag = !!(ex.message == message) 1170 | assert(flag, "Expected Exception(#{klass}) was raised, but the message doesn't match") 1171 | elsif message.kind_of? Regexp 1172 | flag = !!(ex.message =~ message) 1173 | assert(flag, "Expected Exception(#{klass}) was raised, but the message doesn't match") 1174 | else 1175 | raise 1176 | end 1177 | end 1178 | end 1179 | private :assert_raises_with_message 1180 | 1181 | def test_cd 1182 | check_singleton :cd 1183 | end 1184 | 1185 | def test_chdir 1186 | check_singleton :chdir 1187 | end 1188 | 1189 | def test_getwd 1190 | check_singleton :getwd 1191 | end 1192 | 1193 | def test_identical? 1194 | check_singleton :identical? 1195 | end 1196 | 1197 | def test_link 1198 | check_singleton :link 1199 | end 1200 | 1201 | def test_makedirs 1202 | check_singleton :makedirs 1203 | end 1204 | 1205 | def test_mkpath 1206 | check_singleton :mkpath 1207 | end 1208 | 1209 | def test_move 1210 | check_singleton :move 1211 | end 1212 | 1213 | def test_rm_rf 1214 | check_singleton :rm_rf 1215 | end 1216 | 1217 | def test_rmdir 1218 | check_singleton :rmdir 1219 | end 1220 | 1221 | def test_rmtree 1222 | check_singleton :rmtree 1223 | end 1224 | 1225 | def test_safe_unlink 1226 | check_singleton :safe_unlink 1227 | end 1228 | 1229 | def test_symlink 1230 | check_singleton :symlink 1231 | end 1232 | 1233 | def test_touch 1234 | check_singleton :touch 1235 | end 1236 | 1237 | def test_collect_methods 1238 | end 1239 | 1240 | def test_commands 1241 | end 1242 | 1243 | def test_have_option? 1244 | end 1245 | 1246 | def test_options 1247 | end 1248 | 1249 | def test_options_of 1250 | end 1251 | 1252 | end 1253 | -------------------------------------------------------------------------------- /work/reference/fileutils.rb: -------------------------------------------------------------------------------- 1 | # 2 | # = fileutils.rb 3 | # 4 | # Copyright (c) 2000-2007 Minero Aoki 5 | # 6 | # This program is free software. 7 | # You can distribute/modify this program under the same terms of ruby. 8 | # 9 | # == module FileUtils 10 | # 11 | # Namespace for several file utility methods for copying, moving, removing, etc. 12 | # 13 | # === Module Functions 14 | # 15 | # cd(dir, options) 16 | # cd(dir, options) {|dir| .... } 17 | # pwd() 18 | # mkdir(dir, options) 19 | # mkdir(list, options) 20 | # mkdir_p(dir, options) 21 | # mkdir_p(list, options) 22 | # rmdir(dir, options) 23 | # rmdir(list, options) 24 | # ln(old, new, options) 25 | # ln(list, destdir, options) 26 | # ln_s(old, new, options) 27 | # ln_s(list, destdir, options) 28 | # ln_sf(src, dest, options) 29 | # cp(src, dest, options) 30 | # cp(list, dir, options) 31 | # cp_r(src, dest, options) 32 | # cp_r(list, dir, options) 33 | # mv(src, dest, options) 34 | # mv(list, dir, options) 35 | # rm(list, options) 36 | # rm_r(list, options) 37 | # rm_rf(list, options) 38 | # install(src, dest, mode = , options) 39 | # chmod(mode, list, options) 40 | # chmod_R(mode, list, options) 41 | # chown(user, group, list, options) 42 | # chown_R(user, group, list, options) 43 | # touch(list, options) 44 | # 45 | # The options parameter is a hash of options, taken from the list 46 | # :force, :noop, :preserve, and :verbose. 47 | # :noop means that no changes are made. The other two are obvious. 48 | # Each method documents the options that it honours. 49 | # 50 | # All methods that have the concept of a "source" file or directory can take 51 | # either one file or a list of files in that argument. See the method 52 | # documentation for examples. 53 | # 54 | # There are some `low level' methods, which do not accept any option: 55 | # 56 | # copy_entry(src, dest, preserve = false, dereference = false) 57 | # copy_file(src, dest, preserve = false, dereference = true) 58 | # copy_stream(srcstream, deststream) 59 | # remove_entry(path, force = false) 60 | # remove_entry_secure(path, force = false) 61 | # remove_file(path, force = false) 62 | # compare_file(path_a, path_b) 63 | # compare_stream(stream_a, stream_b) 64 | # uptodate?(file, cmp_list) 65 | # 66 | # == module FileUtils::Verbose 67 | # 68 | # This module has all methods of FileUtils module, but it outputs messages 69 | # before acting. This equates to passing the :verbose flag to methods 70 | # in FileUtils. 71 | # 72 | # == module FileUtils::NoWrite 73 | # 74 | # This module has all methods of FileUtils module, but never changes 75 | # files/directories. This equates to passing the :noop flag to methods 76 | # in FileUtils. 77 | # 78 | # == module FileUtils::DryRun 79 | # 80 | # This module has all methods of FileUtils module, but never changes 81 | # files/directories. This equates to passing the :noop and 82 | # :verbose flags to methods in FileUtils. 83 | # 84 | 85 | module FileUtils 86 | 87 | def self.private_module_function(name) #:nodoc: 88 | module_function name 89 | private_class_method name 90 | end 91 | 92 | # This hash table holds command options. 93 | OPT_TABLE = {} #:nodoc: internal use only 94 | 95 | # 96 | # Options: (none) 97 | # 98 | # Returns the name of the current directory. 99 | # 100 | def pwd 101 | Dir.pwd 102 | end 103 | module_function :pwd 104 | 105 | alias getwd pwd 106 | module_function :getwd 107 | 108 | # 109 | # Options: verbose 110 | # 111 | # Changes the current directory to the directory +dir+. 112 | # 113 | # If this method is called with block, resumes to the old 114 | # working directory after the block execution finished. 115 | # 116 | # FileUtils.cd('/', :verbose => true) # chdir and report it 117 | # 118 | def cd(dir, options = {}, &block) # :yield: dir 119 | fu_check_options options, OPT_TABLE['cd'] 120 | fu_output_message "cd #{dir}" if options[:verbose] 121 | Dir.chdir(dir, &block) 122 | fu_output_message 'cd -' if options[:verbose] and block 123 | end 124 | module_function :cd 125 | 126 | alias chdir cd 127 | module_function :chdir 128 | 129 | OPT_TABLE['cd'] = 130 | OPT_TABLE['chdir'] = [:verbose] 131 | 132 | # 133 | # Options: (none) 134 | # 135 | # Returns true if +newer+ is newer than all +old_list+. 136 | # Non-existent files are older than any file. 137 | # 138 | # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ 139 | # system 'make hello.o' 140 | # 141 | def uptodate?(new, old_list, options = nil) 142 | raise ArgumentError, 'uptodate? does not accept any option' if options 143 | 144 | return false unless File.exist?(new) 145 | new_time = File.mtime(new) 146 | old_list.each do |old| 147 | if File.exist?(old) 148 | return false unless new_time > File.mtime(old) 149 | end 150 | end 151 | true 152 | end 153 | module_function :uptodate? 154 | 155 | # 156 | # Options: mode noop verbose 157 | # 158 | # Creates one or more directories. 159 | # 160 | # FileUtils.mkdir 'test' 161 | # FileUtils.mkdir %w( tmp data ) 162 | # FileUtils.mkdir 'notexist', :noop => true # Does not really create. 163 | # FileUtils.mkdir 'tmp', :mode => 0700 164 | # 165 | def mkdir(list, options = {}) 166 | fu_check_options options, OPT_TABLE['mkdir'] 167 | list = fu_list(list) 168 | fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] 169 | return if options[:noop] 170 | 171 | list.each do |dir| 172 | fu_mkdir dir, options[:mode] 173 | end 174 | end 175 | module_function :mkdir 176 | 177 | OPT_TABLE['mkdir'] = [:mode, :noop, :verbose] 178 | 179 | # 180 | # Options: mode noop verbose 181 | # 182 | # Creates a directory and all its parent directories. 183 | # For example, 184 | # 185 | # FileUtils.mkdir_p '/usr/local/lib/ruby' 186 | # 187 | # causes to make following directories, if it does not exist. 188 | # * /usr 189 | # * /usr/local 190 | # * /usr/local/lib 191 | # * /usr/local/lib/ruby 192 | # 193 | # You can pass several directories at a time in a list. 194 | # 195 | def mkdir_p(list, options = {}) 196 | fu_check_options options, OPT_TABLE['mkdir_p'] 197 | list = fu_list(list) 198 | fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] 199 | return *list if options[:noop] 200 | 201 | list.map {|path| path.sub(%r, '') }.each do |path| 202 | # optimize for the most common case 203 | begin 204 | fu_mkdir path, options[:mode] 205 | next 206 | rescue SystemCallError 207 | next if File.directory?(path) 208 | end 209 | 210 | stack = [] 211 | until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" 212 | stack.push path 213 | path = File.dirname(path) 214 | end 215 | stack.reverse_each do |dir| 216 | begin 217 | fu_mkdir dir, options[:mode] 218 | rescue SystemCallError => err 219 | raise unless File.directory?(dir) 220 | end 221 | end 222 | end 223 | 224 | return *list 225 | end 226 | module_function :mkdir_p 227 | 228 | alias mkpath mkdir_p 229 | alias makedirs mkdir_p 230 | module_function :mkpath 231 | module_function :makedirs 232 | 233 | OPT_TABLE['mkdir_p'] = 234 | OPT_TABLE['mkpath'] = 235 | OPT_TABLE['makedirs'] = [:mode, :noop, :verbose] 236 | 237 | def fu_mkdir(path, mode) #:nodoc: 238 | path = path.sub(%r, '') 239 | if mode 240 | Dir.mkdir path, mode 241 | File.chmod mode, path 242 | else 243 | Dir.mkdir path 244 | end 245 | end 246 | private_module_function :fu_mkdir 247 | 248 | # 249 | # Options: noop, verbose 250 | # 251 | # Removes one or more directories. 252 | # 253 | # FileUtils.rmdir 'somedir' 254 | # FileUtils.rmdir %w(somedir anydir otherdir) 255 | # # Does not really remove directory; outputs message. 256 | # FileUtils.rmdir 'somedir', :verbose => true, :noop => true 257 | # 258 | def rmdir(list, options = {}) 259 | fu_check_options options, OPT_TABLE['rmdir'] 260 | list = fu_list(list) 261 | fu_output_message "rmdir #{list.join ' '}" if options[:verbose] 262 | return if options[:noop] 263 | list.each do |dir| 264 | Dir.rmdir dir.sub(%r, '') 265 | end 266 | end 267 | module_function :rmdir 268 | 269 | OPT_TABLE['rmdir'] = [:noop, :verbose] 270 | 271 | # 272 | # Options: force noop verbose 273 | # 274 | # ln(old, new, options = {}) 275 | # 276 | # Creates a hard link +new+ which points to +old+. 277 | # If +new+ already exists and it is a directory, creates a link +new/old+. 278 | # If +new+ already exists and it is not a directory, raises Errno::EEXIST. 279 | # But if :force option is set, overwrite +new+. 280 | # 281 | # FileUtils.ln 'gcc', 'cc', :verbose => true 282 | # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' 283 | # 284 | # ln(list, destdir, options = {}) 285 | # 286 | # Creates several hard links in a directory, with each one pointing to the 287 | # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. 288 | # 289 | # include FileUtils 290 | # cd '/sbin' 291 | # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. 292 | # 293 | def ln(src, dest, options = {}) 294 | fu_check_options options, OPT_TABLE['ln'] 295 | fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 296 | return if options[:noop] 297 | fu_each_src_dest0(src, dest) do |s,d| 298 | remove_file d, true if options[:force] 299 | File.link s, d 300 | end 301 | end 302 | module_function :ln 303 | 304 | alias link ln 305 | module_function :link 306 | 307 | OPT_TABLE['ln'] = 308 | OPT_TABLE['link'] = [:force, :noop, :verbose] 309 | 310 | # 311 | # Options: force noop verbose 312 | # 313 | # ln_s(old, new, options = {}) 314 | # 315 | # Creates a symbolic link +new+ which points to +old+. If +new+ already 316 | # exists and it is a directory, creates a symbolic link +new/old+. If +new+ 317 | # already exists and it is not a directory, raises Errno::EEXIST. But if 318 | # :force option is set, overwrite +new+. 319 | # 320 | # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' 321 | # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true 322 | # 323 | # ln_s(list, destdir, options = {}) 324 | # 325 | # Creates several symbolic links in a directory, with each one pointing to the 326 | # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. 327 | # 328 | # If +destdir+ is not a directory, raises Errno::ENOTDIR. 329 | # 330 | # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin' 331 | # 332 | def ln_s(src, dest, options = {}) 333 | fu_check_options options, OPT_TABLE['ln_s'] 334 | fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 335 | return if options[:noop] 336 | fu_each_src_dest0(src, dest) do |s,d| 337 | remove_file d, true if options[:force] 338 | File.symlink s, d 339 | end 340 | end 341 | module_function :ln_s 342 | 343 | alias symlink ln_s 344 | module_function :symlink 345 | 346 | OPT_TABLE['ln_s'] = 347 | OPT_TABLE['symlink'] = [:force, :noop, :verbose] 348 | 349 | # 350 | # Options: noop verbose 351 | # 352 | # Same as 353 | # #ln_s(src, dest, :force) 354 | # 355 | def ln_sf(src, dest, options = {}) 356 | fu_check_options options, OPT_TABLE['ln_sf'] 357 | options = options.dup 358 | options[:force] = true 359 | ln_s src, dest, options 360 | end 361 | module_function :ln_sf 362 | 363 | OPT_TABLE['ln_sf'] = [:noop, :verbose] 364 | 365 | # 366 | # Options: preserve noop verbose 367 | # 368 | # Copies a file content +src+ to +dest+. If +dest+ is a directory, 369 | # copies +src+ to +dest/src+. 370 | # 371 | # If +src+ is a list of files, then +dest+ must be a directory. 372 | # 373 | # FileUtils.cp 'eval.c', 'eval.c.org' 374 | # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' 375 | # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true 376 | # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink 377 | # 378 | def cp(src, dest, options = {}) 379 | fu_check_options options, OPT_TABLE['cp'] 380 | fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 381 | return if options[:noop] 382 | fu_each_src_dest(src, dest) do |s, d| 383 | copy_file s, d, options[:preserve] 384 | end 385 | end 386 | module_function :cp 387 | 388 | alias copy cp 389 | module_function :copy 390 | 391 | OPT_TABLE['cp'] = 392 | OPT_TABLE['copy'] = [:preserve, :noop, :verbose] 393 | 394 | # 395 | # Options: preserve noop verbose dereference_root remove_destination 396 | # 397 | # Copies +src+ to +dest+. If +src+ is a directory, this method copies 398 | # all its contents recursively. If +dest+ is a directory, copies 399 | # +src+ to +dest/src+. 400 | # 401 | # +src+ can be a list of files. 402 | # 403 | # # Installing ruby library "mylib" under the site_ruby 404 | # FileUtils.rm_r site_ruby + '/mylib', :force 405 | # FileUtils.cp_r 'lib/', site_ruby + '/mylib' 406 | # 407 | # # Examples of copying several files to target directory. 408 | # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' 409 | # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true 410 | # 411 | # # If you want to copy all contents of a directory instead of the 412 | # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, 413 | # # use following code. 414 | # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest, 415 | # # but this doesn't. 416 | # 417 | def cp_r(src, dest, options = {}) 418 | fu_check_options options, OPT_TABLE['cp_r'] 419 | fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 420 | return if options[:noop] 421 | fu_each_src_dest(src, dest) do |s, d| 422 | copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination] 423 | end 424 | end 425 | module_function :cp_r 426 | 427 | OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose, 428 | :dereference_root, :remove_destination] 429 | 430 | # 431 | # Copies a file system entry +src+ to +dest+. 432 | # If +src+ is a directory, this method copies its contents recursively. 433 | # This method preserves file types, c.f. symlink, directory... 434 | # (FIFO, device files and etc. are not supported yet) 435 | # 436 | # Both of +src+ and +dest+ must be a path name. 437 | # +src+ must exist, +dest+ must not exist. 438 | # 439 | # If +preserve+ is true, this method preserves owner, group, permissions 440 | # and modified time. 441 | # 442 | # If +dereference_root+ is true, this method dereference tree root. 443 | # 444 | # If +remove_destination+ is true, this method removes each destination file before copy. 445 | # 446 | def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) 447 | Entry_.new(src, nil, dereference_root).traverse do |ent| 448 | destent = Entry_.new(dest, ent.rel, false) 449 | File.unlink destent.path if remove_destination && File.file?(destent.path) 450 | ent.copy destent.path 451 | ent.copy_metadata destent.path if preserve 452 | end 453 | end 454 | module_function :copy_entry 455 | 456 | # 457 | # Copies file contents of +src+ to +dest+. 458 | # Both of +src+ and +dest+ must be a path name. 459 | # 460 | def copy_file(src, dest, preserve = false, dereference = true) 461 | ent = Entry_.new(src, nil, dereference) 462 | ent.copy_file dest 463 | ent.copy_metadata dest if preserve 464 | end 465 | module_function :copy_file 466 | 467 | # 468 | # Copies stream +src+ to +dest+. 469 | # +src+ must respond to #read(n) and 470 | # +dest+ must respond to #write(str). 471 | # 472 | def copy_stream(src, dest) 473 | fu_copy_stream0 src, dest, fu_stream_blksize(src, dest) 474 | end 475 | module_function :copy_stream 476 | 477 | # 478 | # Options: force noop verbose 479 | # 480 | # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different 481 | # disk partition, the file is copied instead. 482 | # 483 | # FileUtils.mv 'badname.rb', 'goodname.rb' 484 | # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error 485 | # 486 | # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/' 487 | # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true 488 | # 489 | def mv(src, dest, options = {}) 490 | fu_check_options options, OPT_TABLE['mv'] 491 | fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 492 | return if options[:noop] 493 | fu_each_src_dest(src, dest) do |s, d| 494 | destent = Entry_.new(d, nil, true) 495 | begin 496 | if destent.exist? 497 | if destent.directory? 498 | raise Errno::EEXIST, dest 499 | else 500 | destent.remove_file if rename_cannot_overwrite_file? 501 | end 502 | end 503 | begin 504 | File.rename s, d 505 | rescue Errno::EXDEV 506 | copy_entry s, d, true 507 | if options[:secure] 508 | remove_entry_secure s, options[:force] 509 | else 510 | remove_entry s, options[:force] 511 | end 512 | end 513 | rescue SystemCallError 514 | raise unless options[:force] 515 | end 516 | end 517 | end 518 | module_function :mv 519 | 520 | alias move mv 521 | module_function :move 522 | 523 | OPT_TABLE['mv'] = 524 | OPT_TABLE['move'] = [:force, :noop, :verbose, :secure] 525 | 526 | def rename_cannot_overwrite_file? #:nodoc: 527 | /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM 528 | end 529 | private_module_function :rename_cannot_overwrite_file? 530 | 531 | # 532 | # Options: force noop verbose 533 | # 534 | # Remove file(s) specified in +list+. This method cannot remove directories. 535 | # All StandardErrors are ignored when the :force option is set. 536 | # 537 | # FileUtils.rm %w( junk.txt dust.txt ) 538 | # FileUtils.rm Dir.glob('*.so') 539 | # FileUtils.rm 'NotExistFile', :force => true # never raises exception 540 | # 541 | def rm(list, options = {}) 542 | fu_check_options options, OPT_TABLE['rm'] 543 | list = fu_list(list) 544 | fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] 545 | return if options[:noop] 546 | 547 | list.each do |path| 548 | remove_file path, options[:force] 549 | end 550 | end 551 | module_function :rm 552 | 553 | alias remove rm 554 | module_function :remove 555 | 556 | OPT_TABLE['rm'] = 557 | OPT_TABLE['remove'] = [:force, :noop, :verbose] 558 | 559 | # 560 | # Options: noop verbose 561 | # 562 | # Equivalent to 563 | # 564 | # #rm(list, :force => true) 565 | # 566 | def rm_f(list, options = {}) 567 | fu_check_options options, OPT_TABLE['rm_f'] 568 | options = options.dup 569 | options[:force] = true 570 | rm list, options 571 | end 572 | module_function :rm_f 573 | 574 | alias safe_unlink rm_f 575 | module_function :safe_unlink 576 | 577 | OPT_TABLE['rm_f'] = 578 | OPT_TABLE['safe_unlink'] = [:noop, :verbose] 579 | 580 | # 581 | # Options: force noop verbose secure 582 | # 583 | # remove files +list+[0] +list+[1]... If +list+[n] is a directory, 584 | # removes its all contents recursively. This method ignores 585 | # StandardError when :force option is set. 586 | # 587 | # FileUtils.rm_r Dir.glob('/tmp/*') 588 | # FileUtils.rm_r '/', :force => true # :-) 589 | # 590 | # WARNING: This method causes local vulnerability 591 | # if one of parent directories or removing directory tree are world 592 | # writable (including /tmp, whose permission is 1777), and the current 593 | # process has strong privilege such as Unix super user (root), and the 594 | # system has symbolic link. For secure removing, read the documentation 595 | # of #remove_entry_secure carefully, and set :secure option to true. 596 | # Default is :secure=>false. 597 | # 598 | # NOTE: This method calls #remove_entry_secure if :secure option is set. 599 | # See also #remove_entry_secure. 600 | # 601 | def rm_r(list, options = {}) 602 | fu_check_options options, OPT_TABLE['rm_r'] 603 | # options[:secure] = true unless options.key?(:secure) 604 | list = fu_list(list) 605 | fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] 606 | return if options[:noop] 607 | list.each do |path| 608 | if options[:secure] 609 | remove_entry_secure path, options[:force] 610 | else 611 | remove_entry path, options[:force] 612 | end 613 | end 614 | end 615 | module_function :rm_r 616 | 617 | OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure] 618 | 619 | # 620 | # Options: noop verbose secure 621 | # 622 | # Equivalent to 623 | # 624 | # #rm_r(list, :force => true) 625 | # 626 | # WARNING: This method causes local vulnerability. 627 | # Read the documentation of #rm_r first. 628 | # 629 | def rm_rf(list, options = {}) 630 | fu_check_options options, OPT_TABLE['rm_rf'] 631 | options = options.dup 632 | options[:force] = true 633 | rm_r list, options 634 | end 635 | module_function :rm_rf 636 | 637 | alias rmtree rm_rf 638 | module_function :rmtree 639 | 640 | OPT_TABLE['rm_rf'] = 641 | OPT_TABLE['rmtree'] = [:noop, :verbose, :secure] 642 | 643 | # 644 | # This method removes a file system entry +path+. +path+ shall be a 645 | # regular file, a directory, or something. If +path+ is a directory, 646 | # remove it recursively. This method is required to avoid TOCTTOU 647 | # (time-of-check-to-time-of-use) local security vulnerability of #rm_r. 648 | # #rm_r causes security hole when: 649 | # 650 | # * Parent directory is world writable (including /tmp). 651 | # * Removing directory tree includes world writable directory. 652 | # * The system has symbolic link. 653 | # 654 | # To avoid this security hole, this method applies special preprocess. 655 | # If +path+ is a directory, this method chown(2) and chmod(2) all 656 | # removing directories. This requires the current process is the 657 | # owner of the removing whole directory tree, or is the super user (root). 658 | # 659 | # WARNING: You must ensure that *ALL* parent directories are not 660 | # world writable. Otherwise this method does not work. 661 | # Only exception is temporary directory like /tmp and /var/tmp, 662 | # whose permission is 1777. 663 | # 664 | # WARNING: Only the owner of the removing directory tree, or Unix super 665 | # user (root) should invoke this method. Otherwise this method does not 666 | # work. 667 | # 668 | # For details of this security vulnerability, see Perl's case: 669 | # 670 | # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 671 | # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 672 | # 673 | # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. 674 | # 675 | def remove_entry_secure(path, force = false) 676 | unless fu_have_symlink? 677 | remove_entry path, force 678 | return 679 | end 680 | fullpath = File.expand_path(path) 681 | st = File.lstat(fullpath) 682 | unless st.directory? 683 | File.unlink fullpath 684 | return 685 | end 686 | # is a directory. 687 | parent_st = File.stat(File.dirname(fullpath)) 688 | unless parent_st.world_writable? 689 | remove_entry path, force 690 | return 691 | end 692 | unless parent_st.sticky? 693 | raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})" 694 | end 695 | # freeze tree root 696 | euid = Process.euid 697 | File.open(fullpath + '/.') {|f| 698 | unless fu_stat_identical_entry?(st, f.stat) 699 | # symlink (TOC-to-TOU attack?) 700 | File.unlink fullpath 701 | return 702 | end 703 | f.chown euid, -1 704 | f.chmod 0700 705 | } 706 | # ---- tree root is frozen ---- 707 | root = Entry_.new(path) 708 | root.preorder_traverse do |ent| 709 | if ent.directory? 710 | ent.chown euid, -1 711 | ent.chmod 0700 712 | end 713 | end 714 | root.postorder_traverse do |ent| 715 | begin 716 | ent.remove 717 | rescue 718 | raise unless force 719 | end 720 | end 721 | rescue 722 | raise unless force 723 | end 724 | module_function :remove_entry_secure 725 | 726 | def fu_have_symlink? #:nodoc 727 | File.symlink nil, nil 728 | rescue NotImplementedError 729 | return false 730 | rescue 731 | return true 732 | end 733 | private_module_function :fu_have_symlink? 734 | 735 | def fu_stat_identical_entry?(a, b) #:nodoc: 736 | a.dev == b.dev and a.ino == b.ino 737 | end 738 | private_module_function :fu_stat_identical_entry? 739 | 740 | # 741 | # This method removes a file system entry +path+. 742 | # +path+ might be a regular file, a directory, or something. 743 | # If +path+ is a directory, remove it recursively. 744 | # 745 | # See also #remove_entry_secure. 746 | # 747 | def remove_entry(path, force = false) 748 | Entry_.new(path).postorder_traverse do |ent| 749 | begin 750 | ent.remove 751 | rescue 752 | raise unless force 753 | end 754 | end 755 | rescue 756 | raise unless force 757 | end 758 | module_function :remove_entry 759 | 760 | # 761 | # Removes a file +path+. 762 | # This method ignores StandardError if +force+ is true. 763 | # 764 | def remove_file(path, force = false) 765 | Entry_.new(path).remove_file 766 | rescue 767 | raise unless force 768 | end 769 | module_function :remove_file 770 | 771 | # 772 | # Removes a directory +dir+ and its contents recursively. 773 | # This method ignores StandardError if +force+ is true. 774 | # 775 | def remove_dir(path, force = false) 776 | remove_entry path, force # FIXME?? check if it is a directory 777 | end 778 | module_function :remove_dir 779 | 780 | # 781 | # Returns true if the contents of a file A and a file B are identical. 782 | # 783 | # FileUtils.compare_file('somefile', 'somefile') #=> true 784 | # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false 785 | # 786 | def compare_file(a, b) 787 | return false unless File.size(a) == File.size(b) 788 | File.open(a, 'rb') {|fa| 789 | File.open(b, 'rb') {|fb| 790 | return compare_stream(fa, fb) 791 | } 792 | } 793 | end 794 | module_function :compare_file 795 | 796 | alias identical? compare_file 797 | alias cmp compare_file 798 | module_function :identical? 799 | module_function :cmp 800 | 801 | # 802 | # Returns true if the contents of a stream +a+ and +b+ are identical. 803 | # 804 | def compare_stream(a, b) 805 | bsize = fu_stream_blksize(a, b) 806 | sa = sb = nil 807 | while sa == sb 808 | sa = a.read(bsize) 809 | sb = b.read(bsize) 810 | unless sa and sb 811 | if sa.nil? and sb.nil? 812 | return true 813 | end 814 | end 815 | end 816 | false 817 | end 818 | module_function :compare_stream 819 | 820 | # 821 | # Options: mode preserve noop verbose 822 | # 823 | # If +src+ is not same as +dest+, copies it and changes the permission 824 | # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. 825 | # This method removes destination before copy. 826 | # 827 | # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true 828 | # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true 829 | # 830 | def install(src, dest, options = {}) 831 | fu_check_options options, OPT_TABLE['install'] 832 | fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 833 | return if options[:noop] 834 | fu_each_src_dest(src, dest) do |s, d| 835 | unless File.exist?(d) and compare_file(s, d) 836 | remove_file d, true 837 | st = File.stat(s) if options[:preserve] 838 | copy_file s, d 839 | File.utime st.atime, st.mtime, d if options[:preserve] 840 | File.chmod options[:mode], d if options[:mode] 841 | end 842 | end 843 | end 844 | module_function :install 845 | 846 | OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose] 847 | 848 | # 849 | # Options: noop verbose 850 | # 851 | # Changes permission bits on the named files (in +list+) to the bit pattern 852 | # represented by +mode+. 853 | # 854 | # FileUtils.chmod 0755, 'somecommand' 855 | # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) 856 | # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true 857 | # 858 | def chmod(mode, list, options = {}) 859 | fu_check_options options, OPT_TABLE['chmod'] 860 | list = fu_list(list) 861 | fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose] 862 | return if options[:noop] 863 | list.each do |path| 864 | Entry_.new(path).chmod mode 865 | end 866 | end 867 | module_function :chmod 868 | 869 | OPT_TABLE['chmod'] = [:noop, :verbose] 870 | 871 | # 872 | # Options: noop verbose force 873 | # 874 | # Changes permission bits on the named files (in +list+) 875 | # to the bit pattern represented by +mode+. 876 | # 877 | # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" 878 | # 879 | def chmod_R(mode, list, options = {}) 880 | fu_check_options options, OPT_TABLE['chmod_R'] 881 | list = fu_list(list) 882 | fu_output_message sprintf('chmod -R%s %o %s', 883 | (options[:force] ? 'f' : ''), 884 | mode, list.join(' ')) if options[:verbose] 885 | return if options[:noop] 886 | list.each do |root| 887 | Entry_.new(root).traverse do |ent| 888 | begin 889 | ent.chmod mode 890 | rescue 891 | raise unless options[:force] 892 | end 893 | end 894 | end 895 | end 896 | module_function :chmod_R 897 | 898 | OPT_TABLE['chmod_R'] = [:noop, :verbose, :force] 899 | 900 | # 901 | # Options: noop verbose 902 | # 903 | # Changes owner and group on the named files (in +list+) 904 | # to the user +user+ and the group +group+. +user+ and +group+ 905 | # may be an ID (Integer/String) or a name (String). 906 | # If +user+ or +group+ is nil, this method does not change 907 | # the attribute. 908 | # 909 | # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' 910 | # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true 911 | # 912 | def chown(user, group, list, options = {}) 913 | fu_check_options options, OPT_TABLE['chown'] 914 | list = fu_list(list) 915 | fu_output_message sprintf('chown %s%s', 916 | [user,group].compact.join(':') + ' ', 917 | list.join(' ')) if options[:verbose] 918 | return if options[:noop] 919 | uid = fu_get_uid(user) 920 | gid = fu_get_gid(group) 921 | list.each do |path| 922 | Entry_.new(path).chown uid, gid 923 | end 924 | end 925 | module_function :chown 926 | 927 | OPT_TABLE['chown'] = [:noop, :verbose] 928 | 929 | # 930 | # Options: noop verbose force 931 | # 932 | # Changes owner and group on the named files (in +list+) 933 | # to the user +user+ and the group +group+ recursively. 934 | # +user+ and +group+ may be an ID (Integer/String) or 935 | # a name (String). If +user+ or +group+ is nil, this 936 | # method does not change the attribute. 937 | # 938 | # FileUtils.chown_R 'www', 'www', '/var/www/htdocs' 939 | # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true 940 | # 941 | def chown_R(user, group, list, options = {}) 942 | fu_check_options options, OPT_TABLE['chown_R'] 943 | list = fu_list(list) 944 | fu_output_message sprintf('chown -R%s %s%s', 945 | (options[:force] ? 'f' : ''), 946 | [user,group].compact.join(':') + ' ', 947 | list.join(' ')) if options[:verbose] 948 | return if options[:noop] 949 | uid = fu_get_uid(user) 950 | gid = fu_get_gid(group) 951 | return unless uid or gid 952 | list.each do |root| 953 | Entry_.new(root).traverse do |ent| 954 | begin 955 | ent.chown uid, gid 956 | rescue 957 | raise unless options[:force] 958 | end 959 | end 960 | end 961 | end 962 | module_function :chown_R 963 | 964 | OPT_TABLE['chown_R'] = [:noop, :verbose, :force] 965 | 966 | begin 967 | require 'etc' 968 | 969 | def fu_get_uid(user) #:nodoc: 970 | return nil unless user 971 | user = user.to_s 972 | if /\A\d+\z/ =~ user 973 | then user.to_i 974 | else Etc.getpwnam(user).uid 975 | end 976 | end 977 | private_module_function :fu_get_uid 978 | 979 | def fu_get_gid(group) #:nodoc: 980 | return nil unless group 981 | if /\A\d+\z/ =~ group 982 | then group.to_i 983 | else Etc.getgrnam(group).gid 984 | end 985 | end 986 | private_module_function :fu_get_gid 987 | 988 | rescue LoadError 989 | # need Win32 support??? 990 | 991 | def fu_get_uid(user) #:nodoc: 992 | user # FIXME 993 | end 994 | private_module_function :fu_get_uid 995 | 996 | def fu_get_gid(group) #:nodoc: 997 | group # FIXME 998 | end 999 | private_module_function :fu_get_gid 1000 | end 1001 | 1002 | # 1003 | # Options: noop verbose 1004 | # 1005 | # Updates modification time (mtime) and access time (atime) of file(s) in 1006 | # +list+. Files are created if they don't exist. 1007 | # 1008 | # FileUtils.touch 'timestamp' 1009 | # FileUtils.touch Dir.glob('*.c'); system 'make' 1010 | # 1011 | def touch(list, options = {}) 1012 | fu_check_options options, OPT_TABLE['touch'] 1013 | list = fu_list(list) 1014 | created = nocreate = options[:nocreate] 1015 | t = options[:mtime] 1016 | if options[:verbose] 1017 | fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}" 1018 | end 1019 | return if options[:noop] 1020 | list.each do |path| 1021 | created = nocreate 1022 | begin 1023 | File.utime(t, t, path) 1024 | rescue Errno::ENOENT 1025 | raise if created 1026 | File.open(path, 'a') { 1027 | ; 1028 | } 1029 | created = true 1030 | retry if t 1031 | end 1032 | end 1033 | end 1034 | module_function :touch 1035 | 1036 | OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate] 1037 | 1038 | private 1039 | 1040 | module StreamUtils_ 1041 | private 1042 | 1043 | def fu_windows? 1044 | /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM 1045 | end 1046 | 1047 | def fu_copy_stream0(src, dest, blksize) #:nodoc: 1048 | # FIXME: readpartial? 1049 | while s = src.read(blksize) 1050 | dest.write s 1051 | end 1052 | end 1053 | 1054 | def fu_stream_blksize(*streams) 1055 | streams.each do |s| 1056 | next unless s.respond_to?(:stat) 1057 | size = fu_blksize(s.stat) 1058 | return size if size 1059 | end 1060 | fu_default_blksize() 1061 | end 1062 | 1063 | def fu_blksize(st) 1064 | s = st.blksize 1065 | return nil unless s 1066 | return nil if s == 0 1067 | s 1068 | end 1069 | 1070 | def fu_default_blksize 1071 | 1024 1072 | end 1073 | end 1074 | 1075 | include StreamUtils_ 1076 | extend StreamUtils_ 1077 | 1078 | class Entry_ #:nodoc: internal use only 1079 | include StreamUtils_ 1080 | 1081 | def initialize(a, b = nil, deref = false) 1082 | @prefix = @rel = @path = nil 1083 | if b 1084 | @prefix = a 1085 | @rel = b 1086 | else 1087 | @path = a 1088 | end 1089 | @deref = deref 1090 | @stat = nil 1091 | @lstat = nil 1092 | end 1093 | 1094 | def inspect 1095 | "\#<#{self.class} #{path()}>" 1096 | end 1097 | 1098 | def path 1099 | if @path 1100 | File.path(@path) 1101 | else 1102 | join(@prefix, @rel) 1103 | end 1104 | end 1105 | 1106 | def prefix 1107 | @prefix || @path 1108 | end 1109 | 1110 | def rel 1111 | @rel 1112 | end 1113 | 1114 | def dereference? 1115 | @deref 1116 | end 1117 | 1118 | def exist? 1119 | lstat! ? true : false 1120 | end 1121 | 1122 | def file? 1123 | s = lstat! 1124 | s and s.file? 1125 | end 1126 | 1127 | def directory? 1128 | s = lstat! 1129 | s and s.directory? 1130 | end 1131 | 1132 | def symlink? 1133 | s = lstat! 1134 | s and s.symlink? 1135 | end 1136 | 1137 | def chardev? 1138 | s = lstat! 1139 | s and s.chardev? 1140 | end 1141 | 1142 | def blockdev? 1143 | s = lstat! 1144 | s and s.blockdev? 1145 | end 1146 | 1147 | def socket? 1148 | s = lstat! 1149 | s and s.socket? 1150 | end 1151 | 1152 | def pipe? 1153 | s = lstat! 1154 | s and s.pipe? 1155 | end 1156 | 1157 | S_IF_DOOR = 0xD000 1158 | 1159 | def door? 1160 | s = lstat! 1161 | s and (s.mode & 0xF000 == S_IF_DOOR) 1162 | end 1163 | 1164 | def entries 1165 | Dir.entries(path())\ 1166 | .reject {|n| n == '.' or n == '..' }\ 1167 | .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) } 1168 | end 1169 | 1170 | def stat 1171 | return @stat if @stat 1172 | if lstat() and lstat().symlink? 1173 | @stat = File.stat(path()) 1174 | else 1175 | @stat = lstat() 1176 | end 1177 | @stat 1178 | end 1179 | 1180 | def stat! 1181 | return @stat if @stat 1182 | if lstat! and lstat!.symlink? 1183 | @stat = File.stat(path()) 1184 | else 1185 | @stat = lstat! 1186 | end 1187 | @stat 1188 | rescue SystemCallError 1189 | nil 1190 | end 1191 | 1192 | def lstat 1193 | if dereference? 1194 | @lstat ||= File.stat(path()) 1195 | else 1196 | @lstat ||= File.lstat(path()) 1197 | end 1198 | end 1199 | 1200 | def lstat! 1201 | lstat() 1202 | rescue SystemCallError 1203 | nil 1204 | end 1205 | 1206 | def chmod(mode) 1207 | if symlink? 1208 | File.lchmod mode, path() if have_lchmod? 1209 | else 1210 | File.chmod mode, path() 1211 | end 1212 | end 1213 | 1214 | def chown(uid, gid) 1215 | if symlink? 1216 | File.lchown uid, gid, path() if have_lchown? 1217 | else 1218 | File.chown uid, gid, path() 1219 | end 1220 | end 1221 | 1222 | def copy(dest) 1223 | case 1224 | when file? 1225 | copy_file dest 1226 | when directory? 1227 | if !File.exist?(dest) and /^#{Regexp.quote(path)}/ =~ File.dirname(dest) 1228 | raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest] 1229 | end 1230 | begin 1231 | Dir.mkdir dest 1232 | rescue 1233 | raise unless File.directory?(dest) 1234 | end 1235 | when symlink? 1236 | File.symlink File.readlink(path()), dest 1237 | when chardev? 1238 | raise "cannot handle device file" unless File.respond_to?(:mknod) 1239 | mknod dest, ?c, 0666, lstat().rdev 1240 | when blockdev? 1241 | raise "cannot handle device file" unless File.respond_to?(:mknod) 1242 | mknod dest, ?b, 0666, lstat().rdev 1243 | when socket? 1244 | raise "cannot handle socket" unless File.respond_to?(:mknod) 1245 | mknod dest, nil, lstat().mode, 0 1246 | when pipe? 1247 | raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) 1248 | mkfifo dest, 0666 1249 | when door? 1250 | raise "cannot handle door: #{path()}" 1251 | else 1252 | raise "unknown file type: #{path()}" 1253 | end 1254 | end 1255 | 1256 | def copy_file(dest) 1257 | st = stat() 1258 | File.open(path(), 'rb') {|r| 1259 | File.open(dest, 'wb', st.mode) {|w| 1260 | fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize()) 1261 | } 1262 | } 1263 | end 1264 | 1265 | def copy_metadata(path) 1266 | st = lstat() 1267 | File.utime st.atime, st.mtime, path 1268 | begin 1269 | File.chown st.uid, st.gid, path 1270 | rescue Errno::EPERM 1271 | # clear setuid/setgid 1272 | File.chmod st.mode & 01777, path 1273 | else 1274 | File.chmod st.mode, path 1275 | end 1276 | end 1277 | 1278 | def remove 1279 | if directory? 1280 | remove_dir1 1281 | else 1282 | remove_file 1283 | end 1284 | end 1285 | 1286 | def remove_dir1 1287 | platform_support { 1288 | Dir.rmdir path().sub(%r, '') 1289 | } 1290 | end 1291 | 1292 | def remove_file 1293 | platform_support { 1294 | File.unlink path 1295 | } 1296 | end 1297 | 1298 | def platform_support 1299 | return yield unless fu_windows? 1300 | first_time_p = true 1301 | begin 1302 | yield 1303 | rescue Errno::ENOENT 1304 | raise 1305 | rescue => err 1306 | if first_time_p 1307 | first_time_p = false 1308 | begin 1309 | File.chmod 0700, path() # Windows does not have symlink 1310 | retry 1311 | rescue SystemCallError 1312 | end 1313 | end 1314 | raise err 1315 | end 1316 | end 1317 | 1318 | def preorder_traverse 1319 | stack = [self] 1320 | while ent = stack.pop 1321 | yield ent 1322 | stack.concat ent.entries.reverse if ent.directory? 1323 | end 1324 | end 1325 | 1326 | alias traverse preorder_traverse 1327 | 1328 | def postorder_traverse 1329 | if directory? 1330 | entries().each do |ent| 1331 | ent.postorder_traverse do |e| 1332 | yield e 1333 | end 1334 | end 1335 | end 1336 | yield self 1337 | end 1338 | 1339 | private 1340 | 1341 | $fileutils_rb_have_lchmod = nil 1342 | 1343 | def have_lchmod? 1344 | # This is not MT-safe, but it does not matter. 1345 | if $fileutils_rb_have_lchmod == nil 1346 | $fileutils_rb_have_lchmod = check_have_lchmod? 1347 | end 1348 | $fileutils_rb_have_lchmod 1349 | end 1350 | 1351 | def check_have_lchmod? 1352 | return false unless File.respond_to?(:lchmod) 1353 | File.lchmod 0 1354 | return true 1355 | rescue NotImplementedError 1356 | return false 1357 | end 1358 | 1359 | $fileutils_rb_have_lchown = nil 1360 | 1361 | def have_lchown? 1362 | # This is not MT-safe, but it does not matter. 1363 | if $fileutils_rb_have_lchown == nil 1364 | $fileutils_rb_have_lchown = check_have_lchown? 1365 | end 1366 | $fileutils_rb_have_lchown 1367 | end 1368 | 1369 | def check_have_lchown? 1370 | return false unless File.respond_to?(:lchown) 1371 | File.lchown nil, nil 1372 | return true 1373 | rescue NotImplementedError 1374 | return false 1375 | end 1376 | 1377 | def join(dir, base) 1378 | return File.path(dir) if not base or base == '.' 1379 | return File.path(base) if not dir or dir == '.' 1380 | File.join(dir, base) 1381 | end 1382 | end # class Entry_ 1383 | 1384 | def fu_list(arg) #:nodoc: 1385 | [arg].flatten.map {|path| File.path(path) } 1386 | end 1387 | private_module_function :fu_list 1388 | 1389 | def fu_each_src_dest(src, dest) #:nodoc: 1390 | fu_each_src_dest0(src, dest) do |s, d| 1391 | raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d) 1392 | yield s, d 1393 | end 1394 | end 1395 | private_module_function :fu_each_src_dest 1396 | 1397 | def fu_each_src_dest0(src, dest) #:nodoc: 1398 | if tmp = Array.try_convert(src) 1399 | tmp.each do |s| 1400 | s = File.path(s) 1401 | yield s, File.join(dest, File.basename(s)) 1402 | end 1403 | else 1404 | src = File.path(src) 1405 | if File.directory?(dest) 1406 | yield src, File.join(dest, File.basename(src)) 1407 | else 1408 | yield src, File.path(dest) 1409 | end 1410 | end 1411 | end 1412 | private_module_function :fu_each_src_dest0 1413 | 1414 | def fu_same?(a, b) #:nodoc: 1415 | if fu_have_st_ino? 1416 | st1 = File.stat(a) 1417 | st2 = File.stat(b) 1418 | st1.dev == st2.dev and st1.ino == st2.ino 1419 | else 1420 | File.expand_path(a) == File.expand_path(b) 1421 | end 1422 | rescue Errno::ENOENT 1423 | return false 1424 | end 1425 | private_module_function :fu_same? 1426 | 1427 | def fu_have_st_ino? #:nodoc: 1428 | not fu_windows? 1429 | end 1430 | private_module_function :fu_have_st_ino? 1431 | 1432 | def fu_check_options(options, optdecl) #:nodoc: 1433 | h = options.dup 1434 | optdecl.each do |opt| 1435 | h.delete opt 1436 | end 1437 | raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? 1438 | end 1439 | private_module_function :fu_check_options 1440 | 1441 | def fu_update_option(args, new) #:nodoc: 1442 | if tmp = Hash.try_convert(args.last) 1443 | args[-1] = tmp.dup.update(new) 1444 | else 1445 | args.push new 1446 | end 1447 | args 1448 | end 1449 | private_module_function :fu_update_option 1450 | 1451 | @fileutils_output = $stderr 1452 | @fileutils_label = '' 1453 | 1454 | def fu_output_message(msg) #:nodoc: 1455 | @fileutils_output ||= $stderr 1456 | @fileutils_label ||= '' 1457 | @fileutils_output.puts @fileutils_label + msg 1458 | end 1459 | private_module_function :fu_output_message 1460 | 1461 | # 1462 | # Returns an Array of method names which have any options. 1463 | # 1464 | # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] 1465 | # 1466 | def FileUtils.commands 1467 | OPT_TABLE.keys 1468 | end 1469 | 1470 | # 1471 | # Returns an Array of option names. 1472 | # 1473 | # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] 1474 | # 1475 | def FileUtils.options 1476 | OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } 1477 | end 1478 | 1479 | # 1480 | # Returns true if the method +mid+ have an option +opt+. 1481 | # 1482 | # p FileUtils.have_option?(:cp, :noop) #=> true 1483 | # p FileUtils.have_option?(:rm, :force) #=> true 1484 | # p FileUtils.have_option?(:rm, :perserve) #=> false 1485 | # 1486 | def FileUtils.have_option?(mid, opt) 1487 | li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" 1488 | li.include?(opt) 1489 | end 1490 | 1491 | # 1492 | # Returns an Array of option names of the method +mid+. 1493 | # 1494 | # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] 1495 | # 1496 | def FileUtils.options_of(mid) 1497 | OPT_TABLE[mid.to_s].map {|sym| sym.to_s } 1498 | end 1499 | 1500 | # 1501 | # Returns an Array of method names which have the option +opt+. 1502 | # 1503 | # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] 1504 | # 1505 | def FileUtils.collect_method(opt) 1506 | OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } 1507 | end 1508 | 1509 | METHODS = singleton_methods() - [:private_module_function, 1510 | :commands, :options, :have_option?, :options_of, :collect_method] 1511 | 1512 | # 1513 | # This module has all methods of FileUtils module, but it outputs messages 1514 | # before acting. This equates to passing the :verbose flag to 1515 | # methods in FileUtils. 1516 | # 1517 | module Verbose 1518 | include FileUtils 1519 | @fileutils_output = $stderr 1520 | @fileutils_label = '' 1521 | ::FileUtils.collect_method(:verbose).each do |name| 1522 | module_eval(<<-EOS, __FILE__, __LINE__ + 1) 1523 | def #{name}(*args) 1524 | super(*fu_update_option(args, :verbose => true)) 1525 | end 1526 | private :#{name} 1527 | EOS 1528 | end 1529 | extend self 1530 | class << self 1531 | ::FileUtils::METHODS.each do |m| 1532 | public m 1533 | end 1534 | end 1535 | end 1536 | 1537 | # 1538 | # This module has all methods of FileUtils module, but never changes 1539 | # files/directories. This equates to passing the :noop flag 1540 | # to methods in FileUtils. 1541 | # 1542 | module NoWrite 1543 | include FileUtils 1544 | @fileutils_output = $stderr 1545 | @fileutils_label = '' 1546 | ::FileUtils.collect_method(:noop).each do |name| 1547 | module_eval(<<-EOS, __FILE__, __LINE__ + 1) 1548 | def #{name}(*args) 1549 | super(*fu_update_option(args, :noop => true)) 1550 | end 1551 | private :#{name} 1552 | EOS 1553 | end 1554 | extend self 1555 | class << self 1556 | ::FileUtils::METHODS.each do |m| 1557 | public m 1558 | end 1559 | end 1560 | end 1561 | 1562 | # 1563 | # This module has all methods of FileUtils module, but never changes 1564 | # files/directories, with printing message before acting. 1565 | # This equates to passing the :noop and :verbose flag 1566 | # to methods in FileUtils. 1567 | # 1568 | module DryRun 1569 | include FileUtils 1570 | @fileutils_output = $stderr 1571 | @fileutils_label = '' 1572 | ::FileUtils.collect_method(:noop).each do |name| 1573 | module_eval(<<-EOS, __FILE__, __LINE__ + 1) 1574 | def #{name}(*args) 1575 | super(*fu_update_option(args, :noop => true, :verbose => true)) 1576 | end 1577 | private :#{name} 1578 | EOS 1579 | end 1580 | extend self 1581 | class << self 1582 | ::FileUtils::METHODS.each do |m| 1583 | public m 1584 | end 1585 | end 1586 | end 1587 | 1588 | end 1589 | -------------------------------------------------------------------------------- /lib/fileutils2.rb: -------------------------------------------------------------------------------- 1 | # 2 | # = fileutils.rb 3 | # 4 | # Copyright (c) 2000-2007 Minero Aoki 5 | # 6 | # This program is free software. 7 | # You can distribute/modify this program under the same terms of ruby. 8 | # 9 | # == module FileUtils 10 | # 11 | # Namespace for several file utility methods for copying, moving, removing, etc. 12 | # 13 | # === Module Functions 14 | # 15 | # cd(dir, options) 16 | # cd(dir, options) {|dir| .... } 17 | # pwd() 18 | # mkdir(dir, options) 19 | # mkdir(list, options) 20 | # mkdir_p(dir, options) 21 | # mkdir_p(list, options) 22 | # rmdir(dir, options) 23 | # rmdir(list, options) 24 | # ln(old, new, options) 25 | # ln(list, destdir, options) 26 | # ln_s(old, new, options) 27 | # ln_s(list, destdir, options) 28 | # ln_sf(src, dest, options) 29 | # cp(src, dest, options) 30 | # cp(list, dir, options) 31 | # cp_r(src, dest, options) 32 | # cp_r(list, dir, options) 33 | # mv(src, dest, options) 34 | # mv(list, dir, options) 35 | # rm(list, options) 36 | # rm_r(list, options) 37 | # rm_rf(list, options) 38 | # install(src, dest, mode = , options) 39 | # chmod(mode, list, options) 40 | # chmod_R(mode, list, options) 41 | # chown(user, group, list, options) 42 | # chown_R(user, group, list, options) 43 | # touch(list, options) 44 | # 45 | # The options parameter is a hash of options, taken from the list 46 | # :force, :noop, :preserve, and :verbose. 47 | # :noop means that no changes are made. The other two are obvious. 48 | # Each method documents the options that it honours. 49 | # 50 | # All methods that have the concept of a "source" file or directory can take 51 | # either one file or a list of files in that argument. See the method 52 | # documentation for examples. 53 | # 54 | # There are some `low level' methods, which do not accept any option: 55 | # 56 | # copy_entry(src, dest, preserve = false, dereference = false) 57 | # copy_file(src, dest, preserve = false, dereference = true) 58 | # copy_stream(srcstream, deststream) 59 | # remove_entry(path, force = false) 60 | # remove_entry_secure(path, force = false) 61 | # remove_file(path, force = false) 62 | # compare_file(path_a, path_b) 63 | # compare_stream(stream_a, stream_b) 64 | # uptodate?(file, cmp_list) 65 | # 66 | # == module FileUtils::Verbose 67 | # 68 | # This module has all methods of FileUtils module, but it outputs messages 69 | # before acting. This equates to passing the :verbose flag to methods 70 | # in FileUtils. 71 | # 72 | # == module FileUtils::NoWrite 73 | # 74 | # This module has all methods of FileUtils module, but never changes 75 | # files/directories. This equates to passing the :noop flag to methods 76 | # in FileUtils. 77 | # 78 | # == module FileUtils::DryRun 79 | # 80 | # This module has all methods of FileUtils module, but never changes 81 | # files/directories. This equates to passing the :noop and 82 | # :verbose flags to methods in FileUtils. 83 | # 84 | module FileUtils2 85 | @fileutils_output = $stderr 86 | @fileutils_label = '' 87 | extend self 88 | 89 | # 90 | # To overcome Ruby's "Module Inclusion Problem", whenever a module 91 | # is included into FileUtils, then the sub-modules re-include 92 | # FileUtils to ensure inclusion of the new module as well. 93 | # 94 | def self.include(mod) 95 | super mod 96 | extend self 97 | [Verbose, NoWrite, DryRun].each do |base| 98 | base.send(:include, self) #FileUtils) 99 | base.extend(base) # extend self 100 | end 101 | end 102 | 103 | # 104 | # This module has all methods of FileUtils module, but it outputs messages 105 | # before acting. This equates to passing the :verbose flag to 106 | # methods in FileUtils. 107 | # 108 | module Verbose 109 | include FileUtils2 110 | @fileutils_output = $stderr 111 | @fileutils_label = '' 112 | extend self 113 | end 114 | 115 | # 116 | # This module has all methods of FileUtils module, but never changes 117 | # files/directories. This equates to passing the :noop flag 118 | # to methods in FileUtils. 119 | # 120 | module NoWrite 121 | include FileUtils2 122 | @fileutils_output = $stderr 123 | @fileutils_label = '' 124 | extend self 125 | end 126 | 127 | # 128 | # This module has all methods of FileUtils module, but never changes 129 | # files/directories, with printing message before acting. 130 | # This equates to passing the :noop and :verbose flag 131 | # to methods in FileUtils. 132 | # 133 | module DryRun 134 | include FileUtils2 135 | @fileutils_output = $stderr 136 | @fileutils_label = '' 137 | extend self 138 | end 139 | 140 | # This hash table holds command options. 141 | OPT_TABLE = {} #:nodoc: internal use only 142 | 143 | # 144 | def self.define_command(name, *options) 145 | OPT_TABLE[name.to_s] = options 146 | 147 | if options.include?(:verbose) 148 | Verbose.module_eval(<<-EOS, __FILE__, __LINE__ + 1) 149 | def #{name}(*args) 150 | super(*fu_update_option(args, :verbose => true)) 151 | end 152 | EOS 153 | end 154 | if options.include?(:noop) 155 | NoWrite.module_eval(<<-EOS, __FILE__, __LINE__ + 1) 156 | def #{name}(*args) 157 | super(*fu_update_option(args, :noop => true)) 158 | end 159 | EOS 160 | DryRun.module_eval(<<-EOS, __FILE__, __LINE__ + 1) 161 | def #{name}(*args) 162 | super(*fu_update_option(args, :noop => true, :verbose => true)) 163 | end 164 | EOS 165 | else 166 | NoWrite.module_eval(<<-EOS, __FILE__, __LINE__ + 1) 167 | def #{name}(*); end 168 | EOS 169 | DryRun.module_eval(<<-EOS, __FILE__, __LINE__ + 1) 170 | def #{name}(*); end 171 | EOS 172 | end 173 | 174 | [self, Verbose, DryRun, NoWrite].each do |mod| 175 | mod.module_eval(<<-EOS, __FILE__, __LINE__ + 1) 176 | private :#{name} 177 | class << self; public :#{name}; end 178 | EOS 179 | end 180 | end 181 | 182 | class << self 183 | private :define_command 184 | end 185 | 186 | public 187 | 188 | # 189 | # Options: (none) 190 | # 191 | # Returns the name of the current directory. 192 | # 193 | def pwd 194 | Dir.pwd 195 | end 196 | 197 | alias getwd pwd 198 | 199 | define_command('pwd') 200 | define_command('getwd') 201 | 202 | # 203 | # Options: verbose 204 | # 205 | # Changes the current directory to the directory +dir+. 206 | # 207 | # If this method is called with block, resumes to the old 208 | # working directory after the block execution finished. 209 | # 210 | # FileUtils.cd('/', :verbose => true) # chdir and report it 211 | # 212 | # FileUtils.cd('/') do # chdir 213 | # [...] # do something 214 | # end # return to original directory 215 | # 216 | def cd(dir, options = {}, &block) # :yield: dir 217 | fu_check_options options, OPT_TABLE['cd'] 218 | fu_output_message "cd #{dir}" if options[:verbose] 219 | Dir.chdir(dir, &block) 220 | fu_output_message 'cd -' if options[:verbose] and block 221 | end 222 | 223 | alias chdir cd 224 | 225 | define_command('cd', :verbose) 226 | define_command('chdir', :verbose) 227 | 228 | # 229 | # Options: (none) 230 | # 231 | # Returns true if +newer+ is newer than all +old_list+. 232 | # Non-existent files are older than any file. 233 | # 234 | # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ 235 | # system 'make hello.o' 236 | # 237 | def uptodate?(new, old_list) 238 | return false unless File.exist?(new) 239 | new_time = File.mtime(new) 240 | old_list.each do |old| 241 | if File.exist?(old) 242 | return false unless new_time > File.mtime(old) 243 | end 244 | end 245 | true 246 | end 247 | 248 | define_command('uptodate?') 249 | 250 | # 251 | # Options: mode noop verbose 252 | # 253 | # Creates one or more directories. 254 | # 255 | # FileUtils.mkdir 'test' 256 | # FileUtils.mkdir %w( tmp data ) 257 | # FileUtils.mkdir 'notexist', :noop => true # Does not really create. 258 | # FileUtils.mkdir 'tmp', :mode => 0700 259 | # 260 | def mkdir(list, options = {}) 261 | fu_check_options options, OPT_TABLE['mkdir'] 262 | list = fu_list(list) 263 | fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] 264 | return if options[:noop] 265 | 266 | list.each do |dir| 267 | fu_mkdir dir, options[:mode] 268 | end 269 | end 270 | 271 | define_command('mkdir', :mode, :noop, :verbose) 272 | 273 | # 274 | # Options: mode noop verbose 275 | # 276 | # Creates a directory and all its parent directories. 277 | # For example, 278 | # 279 | # FileUtils.mkdir_p '/usr/local/lib/ruby' 280 | # 281 | # causes to make following directories, if it does not exist. 282 | # * /usr 283 | # * /usr/local 284 | # * /usr/local/lib 285 | # * /usr/local/lib/ruby 286 | # 287 | # You can pass several directories at a time in a list. 288 | # 289 | def mkdir_p(list, options = {}) 290 | fu_check_options options, OPT_TABLE['mkdir_p'] 291 | list = fu_list(list) 292 | fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] 293 | return *list if options[:noop] 294 | 295 | list.map {|path| path.chomp(?/) }.each do |path| 296 | # optimize for the most common case 297 | begin 298 | fu_mkdir path, options[:mode] 299 | next 300 | rescue SystemCallError 301 | next if File.directory?(path) 302 | end 303 | 304 | stack = [] 305 | until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" 306 | stack.push path 307 | path = File.dirname(path) 308 | end 309 | stack.reverse_each do |dir| 310 | begin 311 | fu_mkdir dir, options[:mode] 312 | rescue SystemCallError 313 | raise unless File.directory?(dir) 314 | end 315 | end 316 | end 317 | 318 | return *list 319 | end 320 | 321 | alias mkpath mkdir_p 322 | alias makedirs mkdir_p 323 | 324 | define_command('mkdir_p', :mode, :noop, :verbose) 325 | define_command('mkpath', :mode, :noop, :verbose) 326 | define_command('makedirs', :mode, :noop, :verbose) 327 | 328 | private 329 | 330 | def fu_mkdir(path, mode) #:nodoc: 331 | path = path.chomp(?/) 332 | if mode 333 | Dir.mkdir path, mode 334 | File.chmod mode, path 335 | else 336 | Dir.mkdir path 337 | end 338 | end 339 | 340 | public 341 | 342 | # 343 | # Options: noop, verbose 344 | # 345 | # Removes one or more directories. 346 | # 347 | # FileUtils.rmdir 'somedir' 348 | # FileUtils.rmdir %w(somedir anydir otherdir) 349 | # # Does not really remove directory; outputs message. 350 | # FileUtils.rmdir 'somedir', :verbose => true, :noop => true 351 | # 352 | def rmdir(list, options = {}) 353 | fu_check_options options, OPT_TABLE['rmdir'] 354 | list = fu_list(list) 355 | parents = options[:parents] 356 | fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if options[:verbose] 357 | return if options[:noop] 358 | list.each do |dir| 359 | begin 360 | Dir.rmdir(dir = dir.chomp(?/)) 361 | if parents 362 | until (parent = File.dirname(dir)) == '.' or parent == dir 363 | Dir.rmdir(dir) 364 | end 365 | end 366 | rescue Errno::ENOTEMPTY, Errno::ENOENT 367 | end 368 | end 369 | end 370 | 371 | define_command('rmdir', :parents, :noop, :verbose) 372 | 373 | # 374 | # Options: force noop verbose 375 | # 376 | # ln(old, new, options = {}) 377 | # 378 | # Creates a hard link +new+ which points to +old+. 379 | # If +new+ already exists and it is a directory, creates a link +new/old+. 380 | # If +new+ already exists and it is not a directory, raises Errno::EEXIST. 381 | # But if :force option is set, overwrite +new+. 382 | # 383 | # FileUtils.ln 'gcc', 'cc', :verbose => true 384 | # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' 385 | # 386 | # ln(list, destdir, options = {}) 387 | # 388 | # Creates several hard links in a directory, with each one pointing to the 389 | # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. 390 | # 391 | # include FileUtils 392 | # cd '/sbin' 393 | # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. 394 | # 395 | def ln(src, dest, options = {}) 396 | fu_check_options options, OPT_TABLE['ln'] 397 | fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 398 | return if options[:noop] 399 | fu_each_src_dest0(src, dest) do |s,d| 400 | remove_file d, true if options[:force] 401 | File.link s, d 402 | end 403 | end 404 | 405 | alias link ln 406 | 407 | define_command('ln', :force, :noop, :verbose) 408 | define_command('link', :force, :noop, :verbose) 409 | 410 | # 411 | # Options: force noop verbose 412 | # 413 | # ln_s(old, new, options = {}) 414 | # 415 | # Creates a symbolic link +new+ which points to +old+. If +new+ already 416 | # exists and it is a directory, creates a symbolic link +new/old+. If +new+ 417 | # already exists and it is not a directory, raises Errno::EEXIST. But if 418 | # :force option is set, overwrite +new+. 419 | # 420 | # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' 421 | # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true 422 | # 423 | # ln_s(list, destdir, options = {}) 424 | # 425 | # Creates several symbolic links in a directory, with each one pointing to the 426 | # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. 427 | # 428 | # If +destdir+ is not a directory, raises Errno::ENOTDIR. 429 | # 430 | # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin' 431 | # 432 | def ln_s(src, dest, options = {}) 433 | fu_check_options options, OPT_TABLE['ln_s'] 434 | fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 435 | return if options[:noop] 436 | fu_each_src_dest0(src, dest) do |s,d| 437 | remove_file d, true if options[:force] 438 | File.symlink s, d 439 | end 440 | end 441 | 442 | alias symlink ln_s 443 | 444 | define_command('ln_s', :force, :noop, :verbose) 445 | define_command('symlink', :force, :noop, :verbose) 446 | 447 | # 448 | # Options: noop verbose 449 | # 450 | # Same as 451 | # #ln_s(src, dest, :force => true) 452 | # 453 | def ln_sf(src, dest, options = {}) 454 | fu_check_options options, OPT_TABLE['ln_sf'] 455 | options = options.dup 456 | options[:force] = true 457 | ln_s src, dest, options 458 | end 459 | 460 | define_command('ln_sf', :noop, :verbose) 461 | 462 | # 463 | # Options: preserve noop verbose 464 | # 465 | # Copies a file content +src+ to +dest+. If +dest+ is a directory, 466 | # copies +src+ to +dest/src+. 467 | # 468 | # If +src+ is a list of files, then +dest+ must be a directory. 469 | # 470 | # FileUtils.cp 'eval.c', 'eval.c.org' 471 | # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' 472 | # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true 473 | # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink 474 | # 475 | def cp(src, dest, options = {}) 476 | fu_check_options options, OPT_TABLE['cp'] 477 | fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 478 | return if options[:noop] 479 | fu_each_src_dest(src, dest) do |s, d| 480 | copy_file s, d, options[:preserve] 481 | end 482 | end 483 | 484 | alias copy cp 485 | 486 | define_command('cp', :preserve, :noop, :verbose) 487 | define_command('copy', :preserve, :noop, :verbose) 488 | 489 | # 490 | # Options: preserve noop verbose dereference_root remove_destination 491 | # 492 | # Copies +src+ to +dest+. If +src+ is a directory, this method copies 493 | # all its contents recursively. If +dest+ is a directory, copies 494 | # +src+ to +dest/src+. 495 | # 496 | # +src+ can be a list of files. 497 | # 498 | # # Installing ruby library "mylib" under the site_ruby 499 | # FileUtils.rm_r site_ruby + '/mylib', :force 500 | # FileUtils.cp_r 'lib/', site_ruby + '/mylib' 501 | # 502 | # # Examples of copying several files to target directory. 503 | # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' 504 | # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true 505 | # 506 | # # If you want to copy all contents of a directory instead of the 507 | # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, 508 | # # use following code. 509 | # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, 510 | # # but this doesn't. 511 | # 512 | def cp_r(src, dest, options = {}) 513 | fu_check_options options, OPT_TABLE['cp_r'] 514 | fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 515 | return if options[:noop] 516 | options = options.dup 517 | options[:dereference_root] = true unless options.key?(:dereference_root) 518 | fu_each_src_dest(src, dest) do |s, d| 519 | copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination] 520 | end 521 | end 522 | 523 | define_command('cp_r', :preserve, :noop, :verbose, :dereference_root, :remove_destination) 524 | 525 | # 526 | # Copies a file system entry +src+ to +dest+. 527 | # If +src+ is a directory, this method copies its contents recursively. 528 | # This method preserves file types, c.f. symlink, directory... 529 | # (FIFO, device files and etc. are not supported yet) 530 | # 531 | # Both of +src+ and +dest+ must be a path name. 532 | # +src+ must exist, +dest+ must not exist. 533 | # 534 | # If +preserve+ is true, this method preserves owner, group, permissions 535 | # and modified time. 536 | # 537 | # If +dereference_root+ is true, this method dereference tree root. 538 | # 539 | # If +remove_destination+ is true, this method removes each destination file before copy. 540 | # 541 | def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) 542 | Entry_.new(src, nil, dereference_root).wrap_traverse(proc do |ent| 543 | destent = Entry_.new(dest, ent.rel, false) 544 | File.unlink destent.path if remove_destination && File.file?(destent.path) 545 | ent.copy destent.path 546 | end, proc do |ent| 547 | destent = Entry_.new(dest, ent.rel, false) 548 | ent.copy_metadata destent.path if preserve 549 | end) 550 | end 551 | 552 | define_command(:copy_entry) 553 | 554 | # 555 | # Copies file contents of +src+ to +dest+. 556 | # Both of +src+ and +dest+ must be a path name. 557 | # 558 | def copy_file(src, dest, preserve = false, dereference = true) 559 | ent = Entry_.new(src, nil, dereference) 560 | ent.copy_file dest 561 | ent.copy_metadata dest if preserve 562 | end 563 | 564 | define_command(:copy_file) 565 | 566 | # 567 | # Copies stream +src+ to +dest+. 568 | # +src+ must respond to #read(n) and 569 | # +dest+ must respond to #write(str). 570 | # 571 | def copy_stream(src, dest) 572 | IO.copy_stream(src, dest) 573 | end 574 | 575 | define_command(:copy_stream) 576 | 577 | # 578 | # Options: force noop verbose 579 | # 580 | # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different 581 | # disk partition, the file is copied then the original file is removed. 582 | # 583 | # FileUtils.mv 'badname.rb', 'goodname.rb' 584 | # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error 585 | # 586 | # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/' 587 | # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true 588 | # 589 | def mv(src, dest, options = {}) 590 | fu_check_options options, OPT_TABLE['mv'] 591 | fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 592 | return if options[:noop] 593 | fu_each_src_dest(src, dest) do |s, d| 594 | destent = Entry_.new(d, nil, true) 595 | begin 596 | if destent.exist? 597 | if destent.directory? 598 | raise Errno::EEXIST, dest 599 | else 600 | destent.remove_file if rename_cannot_overwrite_file? 601 | end 602 | end 603 | begin 604 | File.rename s, d 605 | rescue Errno::EXDEV 606 | copy_entry s, d, true 607 | if options[:secure] 608 | remove_entry_secure s, options[:force] 609 | else 610 | remove_entry s, options[:force] 611 | end 612 | end 613 | rescue SystemCallError 614 | raise unless options[:force] 615 | end 616 | end 617 | end 618 | 619 | alias move mv 620 | 621 | define_command('mv', :force, :noop, :verbose, :secure) 622 | define_command('move', :force, :noop, :verbose, :secure) 623 | 624 | private 625 | 626 | def rename_cannot_overwrite_file? #:nodoc: 627 | /cygwin|mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM 628 | end 629 | 630 | public 631 | 632 | # 633 | # Options: force noop verbose 634 | # 635 | # Remove file(s) specified in +list+. This method cannot remove directories. 636 | # All StandardErrors are ignored when the :force option is set. 637 | # 638 | # FileUtils.rm %w( junk.txt dust.txt ) 639 | # FileUtils.rm Dir.glob('*.so') 640 | # FileUtils.rm 'NotExistFile', :force => true # never raises exception 641 | # 642 | def rm(list, options = {}) 643 | fu_check_options options, OPT_TABLE['rm'] 644 | list = fu_list(list) 645 | fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] 646 | return if options[:noop] 647 | 648 | list.each do |path| 649 | remove_file path, options[:force] 650 | end 651 | end 652 | 653 | alias remove rm 654 | 655 | define_command('rm', :force, :noop, :verbose) 656 | define_command('remove', :force, :noop, :verbose) 657 | 658 | # 659 | # Options: noop verbose 660 | # 661 | # Equivalent to 662 | # 663 | # #rm(list, :force => true) 664 | # 665 | def rm_f(list, options = {}) 666 | fu_check_options options, OPT_TABLE['rm_f'] 667 | options = options.dup 668 | options[:force] = true 669 | rm list, options 670 | end 671 | 672 | alias safe_unlink rm_f 673 | 674 | define_command('rm_f', :noop, :verbose) 675 | define_command('safe_unlink', :noop, :verbose) 676 | 677 | # 678 | # Options: force noop verbose secure 679 | # 680 | # remove files +list+[0] +list+[1]... If +list+[n] is a directory, 681 | # removes its all contents recursively. This method ignores 682 | # StandardError when :force option is set. 683 | # 684 | # FileUtils.rm_r Dir.glob('/tmp/*') 685 | # FileUtils.rm_r '/', :force => true # :-) 686 | # 687 | # WARNING: This method causes local vulnerability 688 | # if one of parent directories or removing directory tree are world 689 | # writable (including /tmp, whose permission is 1777), and the current 690 | # process has strong privilege such as Unix super user (root), and the 691 | # system has symbolic link. For secure removing, read the documentation 692 | # of #remove_entry_secure carefully, and set :secure option to true. 693 | # Default is :secure=>false. 694 | # 695 | # NOTE: This method calls #remove_entry_secure if :secure option is set. 696 | # See also #remove_entry_secure. 697 | # 698 | def rm_r(list, options = {}) 699 | fu_check_options options, OPT_TABLE['rm_r'] 700 | # options[:secure] = true unless options.key?(:secure) 701 | list = fu_list(list) 702 | fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] 703 | return if options[:noop] 704 | list.each do |path| 705 | if options[:secure] 706 | remove_entry_secure path, options[:force] 707 | else 708 | remove_entry path, options[:force] 709 | end 710 | end 711 | end 712 | 713 | define_command('rm_r', :force, :noop, :verbose, :secure) 714 | 715 | # 716 | # Options: noop verbose secure 717 | # 718 | # Equivalent to 719 | # 720 | # #rm_r(list, :force => true) 721 | # 722 | # WARNING: This method causes local vulnerability. 723 | # Read the documentation of #rm_r first. 724 | # 725 | def rm_rf(list, options = {}) 726 | fu_check_options options, OPT_TABLE['rm_rf'] 727 | options = options.dup 728 | options[:force] = true 729 | rm_r list, options 730 | end 731 | 732 | alias rmtree rm_rf 733 | 734 | define_command('rm_rf', :noop, :verbose, :secure) 735 | define_command('rmtree', :noop, :verbose, :secure) 736 | 737 | # 738 | # This method removes a file system entry +path+. +path+ shall be a 739 | # regular file, a directory, or something. If +path+ is a directory, 740 | # remove it recursively. This method is required to avoid TOCTTOU 741 | # (time-of-check-to-time-of-use) local security vulnerability of #rm_r. 742 | # #rm_r causes security hole when: 743 | # 744 | # * Parent directory is world writable (including /tmp). 745 | # * Removing directory tree includes world writable directory. 746 | # * The system has symbolic link. 747 | # 748 | # To avoid this security hole, this method applies special preprocess. 749 | # If +path+ is a directory, this method chown(2) and chmod(2) all 750 | # removing directories. This requires the current process is the 751 | # owner of the removing whole directory tree, or is the super user (root). 752 | # 753 | # WARNING: You must ensure that *ALL* parent directories cannot be 754 | # moved by other untrusted users. For example, parent directories 755 | # should not be owned by untrusted users, and should not be world 756 | # writable except when the sticky bit set. 757 | # 758 | # WARNING: Only the owner of the removing directory tree, or Unix super 759 | # user (root) should invoke this method. Otherwise this method does not 760 | # work. 761 | # 762 | # For details of this security vulnerability, see Perl's case: 763 | # 764 | # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 765 | # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 766 | # 767 | # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. 768 | # 769 | def remove_entry_secure(path, force = false) 770 | unless fu_have_symlink? 771 | remove_entry path, force 772 | return 773 | end 774 | fullpath = File.expand_path(path) 775 | st = File.lstat(fullpath) 776 | unless st.directory? 777 | File.unlink fullpath 778 | return 779 | end 780 | # is a directory. 781 | parent_st = File.stat(File.dirname(fullpath)) 782 | unless parent_st.world_writable? 783 | remove_entry path, force 784 | return 785 | end 786 | unless parent_st.sticky? 787 | raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})" 788 | end 789 | # freeze tree root 790 | euid = Process.euid 791 | File.open(fullpath + '/.') {|f| 792 | unless fu_stat_identical_entry?(st, f.stat) 793 | # symlink (TOC-to-TOU attack?) 794 | File.unlink fullpath 795 | return 796 | end 797 | f.chown euid, -1 798 | f.chmod 0700 799 | unless fu_stat_identical_entry?(st, File.lstat(fullpath)) 800 | # TOC-to-TOU attack? 801 | File.unlink fullpath 802 | return 803 | end 804 | } 805 | # ---- tree root is frozen ---- 806 | root = Entry_.new(path) 807 | root.preorder_traverse do |ent| 808 | if ent.directory? 809 | ent.chown euid, -1 810 | ent.chmod 0700 811 | end 812 | end 813 | root.postorder_traverse do |ent| 814 | begin 815 | ent.remove 816 | rescue 817 | raise unless force 818 | end 819 | end 820 | rescue 821 | raise unless force 822 | end 823 | 824 | define_command(:remove_entry_secure) 825 | 826 | private 827 | 828 | def fu_have_symlink? #:nodoc: 829 | File.symlink nil, nil 830 | rescue NotImplementedError 831 | return false 832 | rescue TypeError 833 | return true 834 | end 835 | 836 | def fu_stat_identical_entry?(a, b) #:nodoc: 837 | a.dev == b.dev and a.ino == b.ino 838 | end 839 | 840 | public 841 | 842 | # 843 | # This method removes a file system entry +path+. 844 | # +path+ might be a regular file, a directory, or something. 845 | # If +path+ is a directory, remove it recursively. 846 | # 847 | # See also #remove_entry_secure. 848 | # 849 | def remove_entry(path, force = false) 850 | Entry_.new(path).postorder_traverse do |ent| 851 | begin 852 | ent.remove 853 | rescue 854 | raise unless force 855 | end 856 | end 857 | rescue 858 | raise unless force 859 | end 860 | 861 | define_command(:remove_entry) 862 | 863 | # 864 | # Removes a file +path+. 865 | # This method ignores StandardError if +force+ is true. 866 | # 867 | def remove_file(path, force = false) 868 | Entry_.new(path).remove_file 869 | rescue 870 | raise unless force 871 | end 872 | 873 | define_command(:remove_file) 874 | 875 | # 876 | # Removes a directory +dir+ and its contents recursively. 877 | # This method ignores StandardError if +force+ is true. 878 | # 879 | def remove_dir(path, force = false) 880 | remove_entry path, force # FIXME?? check if it is a directory 881 | end 882 | 883 | define_command(:remove_dir) 884 | 885 | # 886 | # Returns true if the contents of a file A and a file B are identical. 887 | # 888 | # FileUtils.compare_file('somefile', 'somefile') #=> true 889 | # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false 890 | # 891 | def compare_file(a, b) 892 | return false unless File.size(a) == File.size(b) 893 | File.open(a, 'rb') {|fa| 894 | File.open(b, 'rb') {|fb| 895 | return compare_stream(fa, fb) 896 | } 897 | } 898 | end 899 | 900 | alias identical? compare_file 901 | alias cmp compare_file 902 | 903 | define_command(:compare_file) 904 | define_command(:identical?) 905 | define_command(:cmp) 906 | 907 | # 908 | # Returns true if the contents of a stream +a+ and +b+ are identical. 909 | # 910 | def compare_stream(a, b) 911 | bsize = fu_stream_blksize(a, b) 912 | sa = "" 913 | sb = "" 914 | begin 915 | a.read(bsize, sa) 916 | b.read(bsize, sb) 917 | return true if sa.empty? && sb.empty? 918 | end while sa == sb 919 | false 920 | end 921 | 922 | define_command(:compare_stream) 923 | 924 | # 925 | # Options: mode preserve noop verbose 926 | # 927 | # If +src+ is not same as +dest+, copies it and changes the permission 928 | # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. 929 | # This method removes destination before copy. 930 | # 931 | # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true 932 | # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true 933 | # 934 | def install(src, dest, options = {}) 935 | fu_check_options options, OPT_TABLE['install'] 936 | fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 937 | return if options[:noop] 938 | fu_each_src_dest(src, dest) do |s, d, st| 939 | unless File.exist?(d) and compare_file(s, d) 940 | remove_file d, true 941 | copy_file s, d 942 | File.utime st.atime, st.mtime, d if options[:preserve] 943 | File.chmod options[:mode], d if options[:mode] 944 | end 945 | end 946 | end 947 | 948 | define_command('install', :mode, :preserve, :noop, :verbose) 949 | 950 | private 951 | 952 | def user_mask(target) #:nodoc: 953 | mask = 0 954 | target.each_byte do |byte_chr| 955 | case byte_chr.chr 956 | when "u" 957 | mask |= 04700 958 | when "g" 959 | mask |= 02070 960 | when "o" 961 | mask |= 01007 962 | when "a" 963 | mask |= 07777 964 | end 965 | end 966 | mask 967 | end 968 | 969 | def mode_mask(mode, path) #:nodoc: 970 | mask = 0 971 | mode.each_byte do |byte_chr| 972 | case byte_chr.chr 973 | when "r" 974 | mask |= 0444 975 | when "w" 976 | mask |= 0222 977 | when "x" 978 | mask |= 0111 979 | when "X" 980 | mask |= 0111 if FileTest::directory? path 981 | when "s" 982 | mask |= 06000 983 | when "t" 984 | mask |= 01000 985 | end 986 | end 987 | mask 988 | end 989 | 990 | def symbolic_modes_to_i(modes, path) #:nodoc: 991 | current_mode = (File.stat(path).mode & 07777) 992 | modes.split(/,/).inject(0) do |mode, mode_sym| 993 | mode_sym = "a#{mode_sym}" if mode_sym =~ %r!^[+-=]! 994 | target, mode = mode_sym.split %r![+-=]! 995 | user_mask = user_mask(target) 996 | mode_mask = mode_mask(mode ? mode : "", path) 997 | 998 | case mode_sym 999 | when /=/ 1000 | current_mode &= ~(user_mask) 1001 | current_mode |= user_mask & mode_mask 1002 | when /\+/ 1003 | current_mode |= user_mask & mode_mask 1004 | when /-/ 1005 | current_mode &= ~(user_mask & mode_mask) 1006 | end 1007 | end 1008 | end 1009 | 1010 | def fu_mode(mode, path) #:nodoc: 1011 | mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode 1012 | end 1013 | 1014 | def mode_to_s(mode) #:nodoc: 1015 | mode.is_a?(String) ? mode : "%o" % mode 1016 | end 1017 | 1018 | public 1019 | 1020 | # 1021 | # Options: noop verbose 1022 | # 1023 | # Changes permission bits on the named files (in +list+) to the bit pattern 1024 | # represented by +mode+. 1025 | # 1026 | # +mode+ is the symbolic and absolute mode can be used. 1027 | # 1028 | # Absolute mode is 1029 | # FileUtils.chmod 0755, 'somecommand' 1030 | # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) 1031 | # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true 1032 | # 1033 | # Symbolic mode is 1034 | # FileUtils.chmod "u=wrx,go=rx", 'somecommand' 1035 | # FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) 1036 | # FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true 1037 | # 1038 | # "a" :: is user, group, other mask. 1039 | # "u" :: is user's mask. 1040 | # "g" :: is group's mask. 1041 | # "o" :: is other's mask. 1042 | # "w" :: is write permission. 1043 | # "r" :: is read permission. 1044 | # "x" :: is execute permission. 1045 | # "X" :: 1046 | # is execute permission for directories only, must be used in conjunction with "+" 1047 | # "s" :: is uid, gid. 1048 | # "t" :: is sticky bit. 1049 | # "+" :: is added to a class given the specified mode. 1050 | # "-" :: Is removed from a given class given mode. 1051 | # "=" :: Is the exact nature of the class will be given a specified mode. 1052 | 1053 | def chmod(mode, list, options = {}) 1054 | fu_check_options options, OPT_TABLE['chmod'] 1055 | list = fu_list(list) 1056 | fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if options[:verbose] 1057 | return if options[:noop] 1058 | list.each do |path| 1059 | Entry_.new(path).chmod(fu_mode(mode, path)) 1060 | end 1061 | end 1062 | 1063 | define_command('chmod', :noop, :verbose) 1064 | 1065 | # 1066 | # Options: noop verbose force 1067 | # 1068 | # Changes permission bits on the named files (in +list+) 1069 | # to the bit pattern represented by +mode+. 1070 | # 1071 | # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" 1072 | # FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" 1073 | # 1074 | def chmod_R(mode, list, options = {}) 1075 | fu_check_options options, OPT_TABLE['chmod_R'] 1076 | list = fu_list(list) 1077 | fu_output_message sprintf('chmod -R%s %s %s', 1078 | (options[:force] ? 'f' : ''), 1079 | mode_to_s(mode), list.join(' ')) if options[:verbose] 1080 | return if options[:noop] 1081 | list.each do |root| 1082 | Entry_.new(root).traverse do |ent| 1083 | begin 1084 | ent.chmod(fu_mode(mode, ent.path)) 1085 | rescue 1086 | raise unless options[:force] 1087 | end 1088 | end 1089 | end 1090 | end 1091 | 1092 | define_command('chmod_R', :noop, :verbose, :force) 1093 | 1094 | # 1095 | # Options: noop verbose 1096 | # 1097 | # Changes owner and group on the named files (in +list+) 1098 | # to the user +user+ and the group +group+. +user+ and +group+ 1099 | # may be an ID (Integer/String) or a name (String). 1100 | # If +user+ or +group+ is nil, this method does not change 1101 | # the attribute. 1102 | # 1103 | # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' 1104 | # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true 1105 | # 1106 | def chown(user, group, list, options = {}) 1107 | fu_check_options options, OPT_TABLE['chown'] 1108 | list = fu_list(list) 1109 | fu_output_message sprintf('chown %s%s', 1110 | [user,group].compact.join(':') + ' ', 1111 | list.join(' ')) if options[:verbose] 1112 | return if options[:noop] 1113 | uid = fu_get_uid(user) 1114 | gid = fu_get_gid(group) 1115 | list.each do |path| 1116 | Entry_.new(path).chown uid, gid 1117 | end 1118 | end 1119 | 1120 | define_command('chown', :noop, :verbose) 1121 | 1122 | # 1123 | # Options: noop verbose force 1124 | # 1125 | # Changes owner and group on the named files (in +list+) 1126 | # to the user +user+ and the group +group+ recursively. 1127 | # +user+ and +group+ may be an ID (Integer/String) or 1128 | # a name (String). If +user+ or +group+ is nil, this 1129 | # method does not change the attribute. 1130 | # 1131 | # FileUtils.chown_R 'www', 'www', '/var/www/htdocs' 1132 | # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true 1133 | # 1134 | def chown_R(user, group, list, options = {}) 1135 | fu_check_options options, OPT_TABLE['chown_R'] 1136 | list = fu_list(list) 1137 | fu_output_message sprintf('chown -R%s %s%s', 1138 | (options[:force] ? 'f' : ''), 1139 | [user,group].compact.join(':') + ' ', 1140 | list.join(' ')) if options[:verbose] 1141 | return if options[:noop] 1142 | uid = fu_get_uid(user) 1143 | gid = fu_get_gid(group) 1144 | return unless uid or gid 1145 | list.each do |root| 1146 | Entry_.new(root).traverse do |ent| 1147 | begin 1148 | ent.chown uid, gid 1149 | rescue 1150 | raise unless options[:force] 1151 | end 1152 | end 1153 | end 1154 | end 1155 | 1156 | define_command('chown_R', :noop, :verbose, :force) 1157 | 1158 | private 1159 | 1160 | begin 1161 | require 'etc' 1162 | 1163 | def fu_get_uid(user) #:nodoc: 1164 | return nil unless user 1165 | case user 1166 | when Integer 1167 | user 1168 | when /\A\d+\z/ 1169 | user.to_i 1170 | else 1171 | Etc.getpwnam(user).uid 1172 | end 1173 | end 1174 | 1175 | def fu_get_gid(group) #:nodoc: 1176 | return nil unless group 1177 | case group 1178 | when Integer 1179 | group 1180 | when /\A\d+\z/ 1181 | group.to_i 1182 | else 1183 | Etc.getgrnam(group).gid 1184 | end 1185 | end 1186 | 1187 | rescue LoadError 1188 | # need Win32 support??? 1189 | 1190 | def fu_get_uid(user) #:nodoc: 1191 | user # FIXME 1192 | end 1193 | 1194 | def fu_get_gid(group) #:nodoc: 1195 | group # FIXME 1196 | end 1197 | end 1198 | 1199 | public 1200 | 1201 | # 1202 | # Options: noop verbose 1203 | # 1204 | # Updates modification time (mtime) and access time (atime) of file(s) in 1205 | # +list+. Files are created if they don't exist. 1206 | # 1207 | # FileUtils.touch 'timestamp' 1208 | # FileUtils.touch Dir.glob('*.c'); system 'make' 1209 | # 1210 | def touch(list, options = {}) 1211 | fu_check_options options, OPT_TABLE['touch'] 1212 | list = fu_list(list) 1213 | created = nocreate = options[:nocreate] 1214 | t = options[:mtime] 1215 | if options[:verbose] 1216 | fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}" 1217 | end 1218 | return if options[:noop] 1219 | list.each do |path| 1220 | created = nocreate 1221 | begin 1222 | File.utime(t, t, path) 1223 | rescue Errno::ENOENT 1224 | raise if created 1225 | File.open(path, 'a') { 1226 | ; 1227 | } 1228 | created = true 1229 | retry if t 1230 | end 1231 | end 1232 | end 1233 | 1234 | define_command('touch', :noop, :verbose, :mtime, :nocreate) 1235 | 1236 | private 1237 | 1238 | module StreamUtils_ 1239 | private 1240 | 1241 | def fu_windows? 1242 | /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM 1243 | end 1244 | 1245 | def fu_copy_stream0(src, dest, blksize = nil) #:nodoc: 1246 | IO.copy_stream(src, dest) 1247 | end 1248 | 1249 | def fu_stream_blksize(*streams) 1250 | streams.each do |s| 1251 | next unless s.respond_to?(:stat) 1252 | size = fu_blksize(s.stat) 1253 | return size if size 1254 | end 1255 | fu_default_blksize() 1256 | end 1257 | 1258 | def fu_blksize(st) 1259 | s = st.blksize 1260 | return nil unless s 1261 | return nil if s == 0 1262 | s 1263 | end 1264 | 1265 | def fu_default_blksize 1266 | 1024 1267 | end 1268 | end 1269 | 1270 | include StreamUtils_ 1271 | 1272 | class Entry_ #:nodoc: internal use only 1273 | include StreamUtils_ 1274 | 1275 | def initialize(a, b = nil, deref = false) 1276 | @prefix = @rel = @path = nil 1277 | if b 1278 | @prefix = a 1279 | @rel = b 1280 | else 1281 | @path = a 1282 | end 1283 | @deref = deref 1284 | @stat = nil 1285 | @lstat = nil 1286 | end 1287 | 1288 | def inspect 1289 | "\#<#{self.class} #{path()}>" 1290 | end 1291 | 1292 | def path 1293 | if @path 1294 | File.path(@path) 1295 | else 1296 | join(@prefix, @rel) 1297 | end 1298 | end 1299 | 1300 | def prefix 1301 | @prefix || @path 1302 | end 1303 | 1304 | def rel 1305 | @rel 1306 | end 1307 | 1308 | def dereference? 1309 | @deref 1310 | end 1311 | 1312 | def exist? 1313 | lstat! ? true : false 1314 | end 1315 | 1316 | def file? 1317 | s = lstat! 1318 | s and s.file? 1319 | end 1320 | 1321 | def directory? 1322 | s = lstat! 1323 | s and s.directory? 1324 | end 1325 | 1326 | def symlink? 1327 | s = lstat! 1328 | s and s.symlink? 1329 | end 1330 | 1331 | def chardev? 1332 | s = lstat! 1333 | s and s.chardev? 1334 | end 1335 | 1336 | def blockdev? 1337 | s = lstat! 1338 | s and s.blockdev? 1339 | end 1340 | 1341 | def socket? 1342 | s = lstat! 1343 | s and s.socket? 1344 | end 1345 | 1346 | def pipe? 1347 | s = lstat! 1348 | s and s.pipe? 1349 | end 1350 | 1351 | S_IF_DOOR = 0xD000 1352 | 1353 | def door? 1354 | s = lstat! 1355 | s and (s.mode & 0xF000 == S_IF_DOOR) 1356 | end 1357 | 1358 | def entries 1359 | opts = {} 1360 | opts[:encoding] = ::Encoding::UTF_8 if fu_windows? 1361 | Dir.entries(path(), opts)\ 1362 | .reject {|n| n == '.' or n == '..' }\ 1363 | .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) } 1364 | end 1365 | 1366 | def stat 1367 | return @stat if @stat 1368 | if lstat() and lstat().symlink? 1369 | @stat = File.stat(path()) 1370 | else 1371 | @stat = lstat() 1372 | end 1373 | @stat 1374 | end 1375 | 1376 | def stat! 1377 | return @stat if @stat 1378 | if lstat! and lstat!.symlink? 1379 | @stat = File.stat(path()) 1380 | else 1381 | @stat = lstat! 1382 | end 1383 | @stat 1384 | rescue SystemCallError 1385 | nil 1386 | end 1387 | 1388 | def lstat 1389 | if dereference? 1390 | @lstat ||= File.stat(path()) 1391 | else 1392 | @lstat ||= File.lstat(path()) 1393 | end 1394 | end 1395 | 1396 | def lstat! 1397 | lstat() 1398 | rescue SystemCallError 1399 | nil 1400 | end 1401 | 1402 | def chmod(mode) 1403 | if symlink? 1404 | File.lchmod mode, path() if have_lchmod? 1405 | else 1406 | File.chmod mode, path() 1407 | end 1408 | end 1409 | 1410 | def chown(uid, gid) 1411 | if symlink? 1412 | File.lchown uid, gid, path() if have_lchown? 1413 | else 1414 | File.chown uid, gid, path() 1415 | end 1416 | end 1417 | 1418 | def copy(dest) 1419 | case 1420 | when file? 1421 | copy_file dest 1422 | when directory? 1423 | if !File.exist?(dest) and descendant_diretory?(dest, path) 1424 | raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest] 1425 | end 1426 | begin 1427 | Dir.mkdir dest 1428 | rescue 1429 | raise unless File.directory?(dest) 1430 | end 1431 | when symlink? 1432 | File.symlink File.readlink(path()), dest 1433 | when chardev? 1434 | raise "cannot handle device file" unless File.respond_to?(:mknod) 1435 | mknod dest, ?c, 0666, lstat().rdev 1436 | when blockdev? 1437 | raise "cannot handle device file" unless File.respond_to?(:mknod) 1438 | mknod dest, ?b, 0666, lstat().rdev 1439 | when socket? 1440 | raise "cannot handle socket" unless File.respond_to?(:mknod) 1441 | mknod dest, nil, lstat().mode, 0 1442 | when pipe? 1443 | raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) 1444 | mkfifo dest, 0666 1445 | when door? 1446 | raise "cannot handle door: #{path()}" 1447 | else 1448 | raise "unknown file type: #{path()}" 1449 | end 1450 | end 1451 | 1452 | def copy_file(dest) 1453 | File.open(path()) do |s| 1454 | File.open(dest, 'wb', s.stat.mode) do |f| 1455 | IO.copy_stream(s, f) 1456 | end 1457 | end 1458 | end 1459 | 1460 | def copy_metadata(path) 1461 | st = lstat() 1462 | if !st.symlink? 1463 | File.utime st.atime, st.mtime, path 1464 | end 1465 | begin 1466 | if st.symlink? 1467 | begin 1468 | File.lchown st.uid, st.gid, path 1469 | rescue NotImplementedError 1470 | end 1471 | else 1472 | File.chown st.uid, st.gid, path 1473 | end 1474 | rescue Errno::EPERM 1475 | # clear setuid/setgid 1476 | if st.symlink? 1477 | begin 1478 | File.lchmod st.mode & 01777, path 1479 | rescue NotImplementedError 1480 | end 1481 | else 1482 | File.chmod st.mode & 01777, path 1483 | end 1484 | else 1485 | if st.symlink? 1486 | begin 1487 | File.lchmod st.mode, path 1488 | rescue NotImplementedError 1489 | end 1490 | else 1491 | File.chmod st.mode, path 1492 | end 1493 | end 1494 | end 1495 | 1496 | def remove 1497 | if directory? 1498 | remove_dir1 1499 | else 1500 | remove_file 1501 | end 1502 | end 1503 | 1504 | def remove_dir1 1505 | platform_support { 1506 | Dir.rmdir path().chomp(?/) 1507 | } 1508 | end 1509 | 1510 | def remove_file 1511 | platform_support { 1512 | File.unlink path 1513 | } 1514 | end 1515 | 1516 | def platform_support 1517 | return yield unless fu_windows? 1518 | first_time_p = true 1519 | begin 1520 | yield 1521 | rescue Errno::ENOENT 1522 | raise 1523 | rescue => err 1524 | if first_time_p 1525 | first_time_p = false 1526 | begin 1527 | File.chmod 0700, path() # Windows does not have symlink 1528 | retry 1529 | rescue SystemCallError 1530 | end 1531 | end 1532 | raise err 1533 | end 1534 | end 1535 | 1536 | def preorder_traverse 1537 | stack = [self] 1538 | while ent = stack.pop 1539 | yield ent 1540 | stack.concat ent.entries.reverse if ent.directory? 1541 | end 1542 | end 1543 | 1544 | alias traverse preorder_traverse 1545 | 1546 | def postorder_traverse 1547 | if directory? 1548 | entries().each do |ent| 1549 | ent.postorder_traverse do |e| 1550 | yield e 1551 | end 1552 | end 1553 | end 1554 | yield self 1555 | end 1556 | 1557 | def wrap_traverse(pre, post) 1558 | pre.call self 1559 | if directory? 1560 | entries.each do |ent| 1561 | ent.wrap_traverse pre, post 1562 | end 1563 | end 1564 | post.call self 1565 | end 1566 | 1567 | private 1568 | 1569 | $fileutils_rb_have_lchmod = nil 1570 | 1571 | def have_lchmod? 1572 | # This is not MT-safe, but it does not matter. 1573 | if $fileutils_rb_have_lchmod == nil 1574 | $fileutils_rb_have_lchmod = check_have_lchmod? 1575 | end 1576 | $fileutils_rb_have_lchmod 1577 | end 1578 | 1579 | def check_have_lchmod? 1580 | return false unless File.respond_to?(:lchmod) 1581 | File.lchmod 0 1582 | return true 1583 | rescue NotImplementedError 1584 | return false 1585 | end 1586 | 1587 | $fileutils_rb_have_lchown = nil 1588 | 1589 | def have_lchown? 1590 | # This is not MT-safe, but it does not matter. 1591 | if $fileutils_rb_have_lchown == nil 1592 | $fileutils_rb_have_lchown = check_have_lchown? 1593 | end 1594 | $fileutils_rb_have_lchown 1595 | end 1596 | 1597 | def check_have_lchown? 1598 | return false unless File.respond_to?(:lchown) 1599 | File.lchown nil, nil 1600 | return true 1601 | rescue NotImplementedError 1602 | return false 1603 | end 1604 | 1605 | def join(dir, base) 1606 | return File.path(dir) if not base or base == '.' 1607 | return File.path(base) if not dir or dir == '.' 1608 | File.join(dir, base) 1609 | end 1610 | 1611 | if File::ALT_SEPARATOR 1612 | DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)".freeze 1613 | else 1614 | DIRECTORY_TERM = "(?=/|\\z)".freeze 1615 | end 1616 | SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : "" 1617 | 1618 | def descendant_diretory?(descendant, ascendant) 1619 | /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant) 1620 | end 1621 | end # class Entry_ 1622 | 1623 | private 1624 | 1625 | def fu_list(arg) #:nodoc: 1626 | [arg].flatten.map {|path| File.path(path) } 1627 | end 1628 | 1629 | def fu_each_src_dest(src, dest) #:nodoc: 1630 | fu_each_src_dest0(src, dest) do |s, d| 1631 | raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d) 1632 | yield s, d, File.stat(s) 1633 | end 1634 | end 1635 | 1636 | def fu_each_src_dest0(src, dest) #:nodoc: 1637 | if tmp = Array.try_convert(src) 1638 | tmp.each do |s| 1639 | s = File.path(s) 1640 | yield s, File.join(dest, File.basename(s)) 1641 | end 1642 | else 1643 | src = File.path(src) 1644 | if File.directory?(dest) 1645 | yield src, File.join(dest, File.basename(src)) 1646 | else 1647 | yield src, File.path(dest) 1648 | end 1649 | end 1650 | end 1651 | 1652 | def fu_same?(a, b) #:nodoc: 1653 | File.identical?(a, b) 1654 | end 1655 | 1656 | def fu_check_options(options, optdecl) #:nodoc: 1657 | h = options.dup 1658 | optdecl.each do |opt| 1659 | h.delete opt 1660 | end 1661 | raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? 1662 | end 1663 | 1664 | def fu_update_option(args, new) #:nodoc: 1665 | if tmp = Hash.try_convert(args.last) 1666 | args[-1] = tmp.dup.update(new) 1667 | else 1668 | args.push new 1669 | end 1670 | args 1671 | end 1672 | 1673 | def fu_output_message(msg) #:nodoc: 1674 | @fileutils_output ||= $stderr 1675 | @fileutils_label ||= '' 1676 | @fileutils_output.puts @fileutils_label + msg 1677 | end 1678 | 1679 | # 1680 | # Returns an Array of method names which have any options. 1681 | # 1682 | # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] 1683 | # 1684 | def self.commands 1685 | OPT_TABLE.keys 1686 | end 1687 | 1688 | # 1689 | # Returns an Array of option names. 1690 | # 1691 | # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] 1692 | # 1693 | def self.options 1694 | OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } 1695 | end 1696 | 1697 | # 1698 | # Returns true if the method +mid+ have an option +opt+. 1699 | # 1700 | # p FileUtils.have_option?(:cp, :noop) #=> true 1701 | # p FileUtils.have_option?(:rm, :force) #=> true 1702 | # p FileUtils.have_option?(:rm, :perserve) #=> false 1703 | # 1704 | def self.have_option?(mid, opt) 1705 | li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" 1706 | li.include?(opt) 1707 | end 1708 | 1709 | # 1710 | # Returns an Array of option names of the method +mid+. 1711 | # 1712 | # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] 1713 | # 1714 | def self.options_of(mid) 1715 | OPT_TABLE[mid.to_s].map {|sym| sym.to_s } 1716 | end 1717 | 1718 | # 1719 | # Returns an Array of method names which have the option +opt+. 1720 | # 1721 | # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] 1722 | # 1723 | def self.collect_method(opt) 1724 | OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } 1725 | end 1726 | 1727 | # LOW_METHODS 1728 | # 1729 | # :pwd, :getwd, :cd, :chdir, 1730 | # :uptodate?, :copy_entry, :copy_file, :copy_stream, :remove_entry_secure, 1731 | # :remove_entry, :remove_file, :remove_dir, :compare_file, :identical?, 1732 | # :cmp, :compare_stream 1733 | # 1734 | # DEPRECATED - Only here for backward compatibility. 1735 | LOW_METHODS = (commands - collect_method(:noop)).map(&:to_sym) 1736 | 1737 | 1738 | # METHODS 1739 | # 1740 | # :pwd, :getwd, :cd, :chdir, :uptodate?, :mkdir, :mkdir_p, :mkpath, :makedirs, 1741 | # :rmdir, :ln, :link, :ln_s, :symlink, :ln_sf, :cp, :copy, :cp_r, :copy_entry, 1742 | # :copy_file, :copy_stream, :mv, :move, :rm, :remove, :rm_f, :safe_unlink, 1743 | # :rm_r, :rm_rf, :rmtree, :remove_entry_secure, :remove_entry, :remove_file, 1744 | # :remove_dir, :compare_file, :identical?, :cmp, :compare_stream, :install, 1745 | # :chmod, :chmod_R, :chown, :chown_R, :touch 1746 | # 1747 | # DEPRECATED - Only here for backward compatibility. 1748 | METHODS = commands.map(&:to_sym) 1749 | 1750 | end 1751 | --------------------------------------------------------------------------------