fileutils2 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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