fileutils2 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.index ADDED
@@ -0,0 +1,57 @@
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'
@@ -0,0 +1,21 @@
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
+
@@ -0,0 +1,26 @@
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
+
@@ -0,0 +1,198 @@
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
+ ## Overriding FileUtils
124
+
125
+ You can use FileUtils2 in place of FileUtils simple by setting FileUtils
126
+ equal to FileUtils2.
127
+
128
+ ```ruby
129
+ require 'fileutils2'
130
+ FileUtils = FileUtils2
131
+ ```
132
+
133
+ It will issue a warning if FileUtils is already loaded, but it should work fine
134
+ in either case. In fact, it may be wise to first `require 'fileutils'` in anycase
135
+ to make sure it's not loaded later by some other script, which could cause some
136
+ unspecified results due to method clobbering. Of course there should plenty
137
+ of warnings in the output in that case, so you could just keep an eye out for
138
+ it instead.
139
+
140
+ For the sake of simply being overly thurough, included in the gem is a script
141
+ that takes care of most of this for you called, `override.rb`.
142
+
143
+ ```ruby
144
+ require 'fileutils2/override'
145
+ ```
146
+
147
+ It requires fileutils2.rb for you and sets `FileUtils = FileUtils2` while
148
+ supressing the usual warning. It doesn't preload the old fileutils.rb library
149
+ first though. That's your call.
150
+
151
+
152
+ ## JRuby and Rubinius Users
153
+
154
+ FileUtils2, as well as the original FileUtils library for that matter, produce
155
+ a few test failures (out of a 1000+) when run again JRuby or Rubinius. At this
156
+ point it is unclear exactly what the issues are. If you are involved in either
157
+ of these projects and can spare a little time to try and fix these issues, that
158
+ would be really great of you! Have a look at the
159
+ [Rubinius build](https://travis-ci.org/rubyworks/fileutils2/jobs/5634466)
160
+ and the [JRuby build](https://travis-ci.org/rubyworks/fileutils2/jobs/5634467)
161
+ for these test results.
162
+
163
+
164
+ ## Why a Gem?
165
+
166
+ You might be wondering why this is a Gem and not part of Ruby's standard library.
167
+ Unfortunately, due to to what I believe to be nothing more than "clique politics"
168
+ among some of the Ruby Core members, this code has been rejected.
169
+
170
+ Actually it was accepted, but after the discovery a bug (easily fixed) it was
171
+ reverted. Despite the code passing all tests, and the fact that this bug made it
172
+ clear that the tests themselves were missing something (that's a good thing to
173
+ discover!), the code was reverted to the old design. Sadly, I am certain there
174
+ was no other reason for it than the simple fact that the three main core members
175
+ from Seattle.rb begrudge me, and go out their way to undermine everything I do.
176
+ This behavior is fairly well documented in the archives of the ruby-talk mailing
177
+ list. I don't like to think that their personal opinions of me would influence
178
+ the design of the Ruby programming language, which should be of the utmost
179
+ professional character, but it is clearly not the case, as is evidenced by
180
+ the fact that they were not willing to discuss the design, let alone actually fix
181
+ it, but instead summarily declared themselves the new maintainers of the code,
182
+ reverted the code to the old design and pronounced the issue closed. Period.
183
+
184
+ * https://bugs.ruby-lang.org/issues/4970
185
+ * https://bugs.ruby-lang.org/issues/7958
186
+
187
+
188
+ ## Legal
189
+
190
+ Copyright (c) 2011 Rubyworks
191
+
192
+ Copyright (c) 2000 Minero Aoki
193
+
194
+ This program is distributed under the terms of the
195
+ [BSD-2-Clause](http://www.spdx.org/licenses/BSD-2-Clause) license.
196
+
197
+ See LICENSE.txt file for details.
198
+
@@ -0,0 +1,1750 @@
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 = <src's>, 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 <tt>options</tt> parameter is a hash of options, taken from the list
46
+ # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
47
+ # <tt>:noop</tt> 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 <tt>:verbose</tt> 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 <tt>:noop</tt> 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 <tt>:noop</tt> and
82
+ # <tt>:verbose</tt> 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 <tt>:verbose</tt> 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 <tt>:noop</tt> 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 <tt>:noop</tt> and <tt>:verbose</tt> 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
+ # <b><tt>ln(old, new, options = {})</tt></b>
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
+ # <b><tt>ln(list, destdir, options = {})</tt></b>
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
+ # <b><tt>ln_s(old, new, options = {})</tt></b>
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
+ # <b><tt>ln_s(list, destdir, options = {})</tt></b>
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