fileutils 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/fileutils.rb +1329 -376
  4. metadata +3 -3
data/lib/fileutils.rb CHANGED
@@ -6,103 +6,181 @@ rescue LoadError
6
6
  # for make mjit-headers
7
7
  end
8
8
 
9
+ # Namespace for file utility methods for copying, moving, removing, etc.
9
10
  #
10
- # = fileutils.rb
11
+ # == What's Here
11
12
  #
12
- # Copyright (c) 2000-2007 Minero Aoki
13
+ # First, what’s elsewhere. \Module \FileUtils:
13
14
  #
14
- # This program is free software.
15
- # You can distribute/modify this program under the same terms of ruby.
15
+ # - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html].
16
+ # - Supplements {class File}[https://docs.ruby-lang.org/en/master/File.html]
17
+ # (but is not included or extended there).
16
18
  #
17
- # == module FileUtils
19
+ # Here, module \FileUtils provides methods that are useful for:
18
20
  #
19
- # Namespace for several file utility methods for copying, moving, removing, etc.
21
+ # - {Creating}[rdoc-ref:FileUtils@Creating].
22
+ # - {Deleting}[rdoc-ref:FileUtils@Deleting].
23
+ # - {Querying}[rdoc-ref:FileUtils@Querying].
24
+ # - {Setting}[rdoc-ref:FileUtils@Setting].
25
+ # - {Comparing}[rdoc-ref:FileUtils@Comparing].
26
+ # - {Copying}[rdoc-ref:FileUtils@Copying].
27
+ # - {Moving}[rdoc-ref:FileUtils@Moving].
28
+ # - {Options}[rdoc-ref:FileUtils@Options].
20
29
  #
21
- # === Module Functions
30
+ # === Creating
22
31
  #
23
- # require 'fileutils'
32
+ # - ::mkdir: Creates directories.
33
+ # - ::mkdir_p, ::makedirs, ::mkpath: Creates directories,
34
+ # also creating ancestor directories as needed.
35
+ # - ::link_entry: Creates a hard link.
36
+ # - ::ln, ::link: Creates hard links.
37
+ # - ::ln_s, ::symlink: Creates symbolic links.
38
+ # - ::ln_sf: Creates symbolic links, overwriting if necessary.
39
+ # - ::ln_sr: Creates symbolic links relative to targets
24
40
  #
25
- # FileUtils.cd(dir, **options)
26
- # FileUtils.cd(dir, **options) {|dir| block }
27
- # FileUtils.pwd()
28
- # FileUtils.mkdir(dir, **options)
29
- # FileUtils.mkdir(list, **options)
30
- # FileUtils.mkdir_p(dir, **options)
31
- # FileUtils.mkdir_p(list, **options)
32
- # FileUtils.rmdir(dir, **options)
33
- # FileUtils.rmdir(list, **options)
34
- # FileUtils.ln(target, link, **options)
35
- # FileUtils.ln(targets, dir, **options)
36
- # FileUtils.ln_s(target, link, **options)
37
- # FileUtils.ln_s(targets, dir, **options)
38
- # FileUtils.ln_sf(target, link, **options)
39
- # FileUtils.cp(src, dest, **options)
40
- # FileUtils.cp(list, dir, **options)
41
- # FileUtils.cp_r(src, dest, **options)
42
- # FileUtils.cp_r(list, dir, **options)
43
- # FileUtils.mv(src, dest, **options)
44
- # FileUtils.mv(list, dir, **options)
45
- # FileUtils.rm(list, **options)
46
- # FileUtils.rm_r(list, **options)
47
- # FileUtils.rm_rf(list, **options)
48
- # FileUtils.install(src, dest, **options)
49
- # FileUtils.chmod(mode, list, **options)
50
- # FileUtils.chmod_R(mode, list, **options)
51
- # FileUtils.chown(user, group, list, **options)
52
- # FileUtils.chown_R(user, group, list, **options)
53
- # FileUtils.touch(list, **options)
41
+ # === Deleting
54
42
  #
55
- # Possible <tt>options</tt> are:
43
+ # - ::remove_dir: Removes a directory and its descendants.
44
+ # - ::remove_entry: Removes an entry, including its descendants if it is a directory.
45
+ # - ::remove_entry_secure: Like ::remove_entry, but removes securely.
46
+ # - ::remove_file: Removes a file entry.
47
+ # - ::rm, ::remove: Removes entries.
48
+ # - ::rm_f, ::safe_unlink: Like ::rm, but removes forcibly.
49
+ # - ::rm_r: Removes entries and their descendants.
50
+ # - ::rm_rf, ::rmtree: Like ::rm_r, but removes forcibly.
51
+ # - ::rmdir: Removes directories.
56
52
  #
57
- # <tt>:force</tt> :: forced operation (rewrite files if exist, remove
58
- # directories if not empty, etc.);
59
- # <tt>:verbose</tt> :: print command to be run, in bash syntax, before
60
- # performing it;
61
- # <tt>:preserve</tt> :: preserve object's group, user and modification
62
- # time on copying;
63
- # <tt>:noop</tt> :: no changes are made (usable in combination with
64
- # <tt>:verbose</tt> which will print the command to run)
53
+ # === Querying
65
54
  #
66
- # Each method documents the options that it honours. See also ::commands,
67
- # ::options and ::options_of methods to introspect which command have which
68
- # options.
55
+ # - ::pwd, ::getwd: Returns the path to the working directory.
56
+ # - ::uptodate?: Returns whether a given entry is newer than given other entries.
69
57
  #
70
- # All methods that have the concept of a "source" file or directory can take
71
- # either one file or a list of files in that argument. See the method
72
- # documentation for examples.
58
+ # === Setting
73
59
  #
74
- # There are some `low level' methods, which do not accept keyword arguments:
60
+ # - ::cd, ::chdir: Sets the working directory.
61
+ # - ::chmod: Sets permissions for an entry.
62
+ # - ::chmod_R: Sets permissions for an entry and its descendants.
63
+ # - ::chown: Sets the owner and group for entries.
64
+ # - ::chown_R: Sets the owner and group for entries and their descendants.
65
+ # - ::touch: Sets modification and access times for entries,
66
+ # creating if necessary.
75
67
  #
76
- # FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
77
- # FileUtils.copy_file(src, dest, preserve = false, dereference = true)
78
- # FileUtils.copy_stream(srcstream, deststream)
79
- # FileUtils.remove_entry(path, force = false)
80
- # FileUtils.remove_entry_secure(path, force = false)
81
- # FileUtils.remove_file(path, force = false)
82
- # FileUtils.compare_file(path_a, path_b)
83
- # FileUtils.compare_stream(stream_a, stream_b)
84
- # FileUtils.uptodate?(file, cmp_list)
68
+ # === Comparing
85
69
  #
86
- # == module FileUtils::Verbose
70
+ # - ::compare_file, ::cmp, ::identical?: Returns whether two entries are identical.
71
+ # - ::compare_stream: Returns whether two streams are identical.
87
72
  #
88
- # This module has all methods of FileUtils module, but it outputs messages
89
- # before acting. This equates to passing the <tt>:verbose</tt> flag to methods
90
- # in FileUtils.
73
+ # === Copying
91
74
  #
92
- # == module FileUtils::NoWrite
75
+ # - ::copy_entry: Recursively copies an entry.
76
+ # - ::copy_file: Copies an entry.
77
+ # - ::copy_stream: Copies a stream.
78
+ # - ::cp, ::copy: Copies files.
79
+ # - ::cp_lr: Recursively creates hard links.
80
+ # - ::cp_r: Recursively copies files, retaining mode, owner, and group.
81
+ # - ::install: Recursively copies files, optionally setting mode,
82
+ # owner, and group.
93
83
  #
94
- # This module has all methods of FileUtils module, but never changes
95
- # files/directories. This equates to passing the <tt>:noop</tt> flag to methods
96
- # in FileUtils.
84
+ # === Moving
97
85
  #
98
- # == module FileUtils::DryRun
86
+ # - ::mv, ::move: Moves entries.
99
87
  #
100
- # This module has all methods of FileUtils module, but never changes
101
- # files/directories. This equates to passing the <tt>:noop</tt> and
102
- # <tt>:verbose</tt> flags to methods in FileUtils.
88
+ # === Options
89
+ #
90
+ # - ::collect_method: Returns the names of methods that accept a given option.
91
+ # - ::commands: Returns the names of methods that accept options.
92
+ # - ::have_option?: Returns whether a given method accepts a given option.
93
+ # - ::options: Returns all option names.
94
+ # - ::options_of: Returns the names of the options for a given method.
95
+ #
96
+ # == Path Arguments
97
+ #
98
+ # Some methods in \FileUtils accept _path_ arguments,
99
+ # which are interpreted as paths to filesystem entries:
100
+ #
101
+ # - If the argument is a string, that value is the path.
102
+ # - If the argument has method +:to_path+, it is converted via that method.
103
+ # - If the argument has method +:to_str+, it is converted via that method.
104
+ #
105
+ # == About the Examples
106
+ #
107
+ # Some examples here involve trees of file entries.
108
+ # For these, we sometimes display trees using the
109
+ # {tree command-line utility}[https://en.wikipedia.org/wiki/Tree_(command)],
110
+ # which is a recursive directory-listing utility that produces
111
+ # a depth-indented listing of files and directories.
112
+ #
113
+ # We use a helper method to launch the command and control the format:
114
+ #
115
+ # def tree(dirpath = '.')
116
+ # command = "tree --noreport --charset=ascii #{dirpath}"
117
+ # system(command)
118
+ # end
119
+ #
120
+ # To illustrate:
121
+ #
122
+ # tree('src0')
123
+ # # => src0
124
+ # # |-- sub0
125
+ # # | |-- src0.txt
126
+ # # | `-- src1.txt
127
+ # # `-- sub1
128
+ # # |-- src2.txt
129
+ # # `-- src3.txt
130
+ #
131
+ # == Avoiding the TOCTTOU Vulnerability
132
+ #
133
+ # For certain methods that recursively remove entries,
134
+ # there is a potential vulnerability called the
135
+ # {Time-of-check to time-of-use}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use],
136
+ # or TOCTTOU, vulnerability that can exist when:
137
+ #
138
+ # - An ancestor directory of the entry at the target path is world writable;
139
+ # such directories include <tt>/tmp</tt>.
140
+ # - The directory tree at the target path includes:
141
+ #
142
+ # - A world-writable descendant directory.
143
+ # - A symbolic link.
144
+ #
145
+ # To avoid that vulnerability, you can use this method to remove entries:
146
+ #
147
+ # - FileUtils.remove_entry_secure: removes recursively
148
+ # if the target path points to a directory.
149
+ #
150
+ # Also available are these methods,
151
+ # each of which calls \FileUtils.remove_entry_secure:
152
+ #
153
+ # - FileUtils.rm_r with keyword argument <tt>secure: true</tt>.
154
+ # - FileUtils.rm_rf with keyword argument <tt>secure: true</tt>.
155
+ #
156
+ # Finally, this method for moving entries calls \FileUtils.remove_entry_secure
157
+ # if the source and destination are on different file systems
158
+ # (which means that the "move" is really a copy and remove):
159
+ #
160
+ # - FileUtils.mv with keyword argument <tt>secure: true</tt>.
161
+ #
162
+ # \Method \FileUtils.remove_entry_secure removes securely
163
+ # by applying a special pre-process:
164
+ #
165
+ # - If the target path points to a directory, this method uses methods
166
+ # {File#chown}[https://docs.ruby-lang.org/en/master/File.html#method-i-chown]
167
+ # and {File#chmod}[https://docs.ruby-lang.org/en/master/File.html#method-i-chmod]
168
+ # in removing directories.
169
+ # - The owner of the target directory should be either the current process
170
+ # or the super user (root).
171
+ #
172
+ # WARNING: You must ensure that *ALL* parent directories cannot be
173
+ # moved by other untrusted users. For example, parent directories
174
+ # should not be owned by untrusted users, and should not be world
175
+ # writable except when the sticky bit is set.
176
+ #
177
+ # For details of this security vulnerability, see Perl cases:
178
+ #
179
+ # - {CVE-2005-0448}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448].
180
+ # - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452].
103
181
  #
104
182
  module FileUtils
105
- VERSION = "1.6.0"
183
+ VERSION = "1.7.0"
106
184
 
107
185
  def self.private_module_function(name) #:nodoc:
108
186
  module_function name
@@ -110,7 +188,13 @@ module FileUtils
110
188
  end
111
189
 
112
190
  #
113
- # Returns the name of the current directory.
191
+ # Returns a string containing the path to the current directory:
192
+ #
193
+ # FileUtils.pwd # => "/rdoc/fileutils"
194
+ #
195
+ # FileUtils.getwd is an alias for FileUtils.pwd.
196
+ #
197
+ # Related: FileUtils.cd.
114
198
  #
115
199
  def pwd
116
200
  Dir.pwd
@@ -120,19 +204,40 @@ module FileUtils
120
204
  alias getwd pwd
121
205
  module_function :getwd
122
206
 
207
+ # Changes the working directory to the given +dir+, which
208
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]:
209
+ #
210
+ # With no block given,
211
+ # changes the current directory to the directory at +dir+; returns zero:
212
+ #
213
+ # FileUtils.pwd # => "/rdoc/fileutils"
214
+ # FileUtils.cd('..')
215
+ # FileUtils.pwd # => "/rdoc"
216
+ # FileUtils.cd('fileutils')
217
+ #
218
+ # With a block given, changes the current directory to the directory
219
+ # at +dir+, calls the block with argument +dir+,
220
+ # and restores the original current directory; returns the block's value:
221
+ #
222
+ # FileUtils.pwd # => "/rdoc/fileutils"
223
+ # FileUtils.cd('..') { |arg| [arg, FileUtils.pwd] } # => ["..", "/rdoc"]
224
+ # FileUtils.pwd # => "/rdoc/fileutils"
225
+ #
226
+ # Keyword arguments:
123
227
  #
124
- # Changes the current directory to the directory +dir+.
228
+ # - <tt>verbose: true</tt> - prints an equivalent command:
125
229
  #
126
- # If this method is called with block, resumes to the previous
127
- # working directory after the block execution has finished.
230
+ # FileUtils.cd('..')
231
+ # FileUtils.cd('fileutils')
128
232
  #
129
- # FileUtils.cd('/') # change directory
233
+ # Output:
130
234
  #
131
- # FileUtils.cd('/', verbose: true) # change directory and report it
235
+ # cd ..
236
+ # cd fileutils
132
237
  #
133
- # FileUtils.cd('/') do # change directory
134
- # # ... # do something
135
- # end # return to original directory
238
+ # FileUtils.chdir is an alias for FileUtils.cd.
239
+ #
240
+ # Related: FileUtils.pwd.
136
241
  #
137
242
  def cd(dir, verbose: nil, &block) # :yield: dir
138
243
  fu_output_message "cd #{dir}" if verbose
@@ -146,11 +251,19 @@ module FileUtils
146
251
  module_function :chdir
147
252
 
148
253
  #
149
- # Returns true if +new+ is newer than all +old_list+.
150
- # Non-existent files are older than any file.
254
+ # Returns +true+ if the file at path +new+
255
+ # is newer than all the files at paths in array +old_list+;
256
+ # +false+ otherwise.
257
+ #
258
+ # Argument +new+ and the elements of +old_list+
259
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]:
260
+ #
261
+ # FileUtils.uptodate?('Rakefile', ['Gemfile', 'README.md']) # => true
262
+ # FileUtils.uptodate?('Gemfile', ['Rakefile', 'README.md']) # => false
151
263
  #
152
- # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
153
- # system 'make hello.o'
264
+ # A non-existent file is considered to be infinitely old.
265
+ #
266
+ # Related: FileUtils.touch.
154
267
  #
155
268
  def uptodate?(new, old_list)
156
269
  return false unless File.exist?(new)
@@ -170,12 +283,39 @@ module FileUtils
170
283
  private_module_function :remove_trailing_slash
171
284
 
172
285
  #
173
- # Creates one or more directories.
286
+ # Creates directories at the paths in the given +list+
287
+ # (a single path or an array of paths);
288
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise.
289
+ #
290
+ # Argument +list+ or its elements
291
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
292
+ #
293
+ # With no keyword arguments, creates a directory at each +path+ in +list+
294
+ # by calling: <tt>Dir.mkdir(path, mode)</tt>;
295
+ # see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]:
296
+ #
297
+ # FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"]
298
+ # FileUtils.mkdir('tmp4') # => ["tmp4"]
299
+ #
300
+ # Keyword arguments:
301
+ #
302
+ # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>;
303
+ # see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
304
+ # - <tt>noop: true</tt> - does not create directories.
305
+ # - <tt>verbose: true</tt> - prints an equivalent command:
174
306
  #
175
- # FileUtils.mkdir 'test'
176
- # FileUtils.mkdir %w(tmp data)
177
- # FileUtils.mkdir 'notexist', noop: true # Does not really create.
178
- # FileUtils.mkdir 'tmp', mode: 0700
307
+ # FileUtils.mkdir(%w[tmp0 tmp1], verbose: true)
308
+ # FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true)
309
+ #
310
+ # Output:
311
+ #
312
+ # mkdir tmp0 tmp1
313
+ # mkdir -m 700 tmp2 tmp3
314
+ #
315
+ # Raises an exception if any path points to an existing
316
+ # file or directory, or if for any reason a directory cannot be created.
317
+ #
318
+ # Related: FileUtils.mkdir_p.
179
319
  #
180
320
  def mkdir(list, mode: nil, noop: nil, verbose: nil)
181
321
  list = fu_list(list)
@@ -189,19 +329,42 @@ module FileUtils
189
329
  module_function :mkdir
190
330
 
191
331
  #
192
- # Creates a directory and all its parent directories.
193
- # For example,
332
+ # Creates directories at the paths in the given +list+
333
+ # (a single path or an array of paths),
334
+ # also creating ancestor directories as needed;
335
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise.
336
+ #
337
+ # Argument +list+ or its elements
338
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
339
+ #
340
+ # With no keyword arguments, creates a directory at each +path+ in +list+,
341
+ # along with any needed ancestor directories,
342
+ # by calling: <tt>Dir.mkdir(path, mode)</tt>;
343
+ # see {Dir.mkdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-mkdir]:
344
+ #
345
+ # FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]
346
+ # FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"]
347
+ #
348
+ # Keyword arguments:
194
349
  #
195
- # FileUtils.mkdir_p '/usr/local/lib/ruby'
350
+ # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>;
351
+ # see {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
352
+ # - <tt>noop: true</tt> - does not create directories.
353
+ # - <tt>verbose: true</tt> - prints an equivalent command:
196
354
  #
197
- # causes to make following directories, if they do not exist.
355
+ # FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true)
356
+ # FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true)
198
357
  #
199
- # * /usr
200
- # * /usr/local
201
- # * /usr/local/lib
202
- # * /usr/local/lib/ruby
358
+ # Output:
203
359
  #
204
- # You can pass several directories at a time in a list.
360
+ # mkdir -p tmp0 tmp1
361
+ # mkdir -p -m 700 tmp2 tmp3
362
+ #
363
+ # Raises an exception if for any reason a directory cannot be created.
364
+ #
365
+ # FileUtils.mkpath and FileUtils.makedirs are aliases for FileUtils.mkdir_p.
366
+ #
367
+ # Related: FileUtils.mkdir.
205
368
  #
206
369
  def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
207
370
  list = fu_list(list)
@@ -212,7 +375,7 @@ module FileUtils
212
375
  path = remove_trailing_slash(item)
213
376
 
214
377
  stack = []
215
- until File.directory?(path)
378
+ until File.directory?(path) || File.dirname(path) == path
216
379
  stack.push path
217
380
  path = File.dirname(path)
218
381
  end
@@ -246,12 +409,39 @@ module FileUtils
246
409
  private_module_function :fu_mkdir
247
410
 
248
411
  #
249
- # Removes one or more directories.
412
+ # Removes directories at the paths in the given +list+
413
+ # (a single path or an array of paths);
414
+ # returns +list+, if it is an array, <tt>[list]</tt> otherwise.
415
+ #
416
+ # Argument +list+ or its elements
417
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
418
+ #
419
+ # With no keyword arguments, removes the directory at each +path+ in +list+,
420
+ # by calling: <tt>Dir.rmdir(path)</tt>;
421
+ # see {Dir.rmdir}[https://docs.ruby-lang.org/en/master/Dir.html#method-c-rmdir]:
422
+ #
423
+ # FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]
424
+ # FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"]
425
+ #
426
+ # Keyword arguments:
427
+ #
428
+ # - <tt>parents: true</tt> - removes successive ancestor directories
429
+ # if empty.
430
+ # - <tt>noop: true</tt> - does not remove directories.
431
+ # - <tt>verbose: true</tt> - prints an equivalent command:
250
432
  #
251
- # FileUtils.rmdir 'somedir'
252
- # FileUtils.rmdir %w(somedir anydir otherdir)
253
- # # Does not really remove directory; outputs message.
254
- # FileUtils.rmdir 'somedir', verbose: true, noop: true
433
+ # FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3], parents: true, verbose: true)
434
+ # FileUtils.rmdir('tmp4/tmp5', parents: true, verbose: true)
435
+ #
436
+ # Output:
437
+ #
438
+ # rmdir -p tmp0/tmp1 tmp2/tmp3
439
+ # rmdir -p tmp4/tmp5
440
+ #
441
+ # Raises an exception if a directory does not exist
442
+ # or if for any reason a directory cannot be removed.
443
+ #
444
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
255
445
  #
256
446
  def rmdir(list, parents: nil, noop: nil, verbose: nil)
257
447
  list = fu_list(list)
@@ -272,26 +462,62 @@ module FileUtils
272
462
  end
273
463
  module_function :rmdir
274
464
 
465
+ # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link].
466
+ #
467
+ # Arguments +src+ (a single path or an array of paths)
468
+ # and +dest+ (a single path)
469
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
470
+ #
471
+ # When +src+ is the path to an existing file
472
+ # and +dest+ is the path to a non-existent file,
473
+ # creates a hard link at +dest+ pointing to +src+; returns zero:
474
+ #
475
+ # Dir.children('tmp0/') # => ["t.txt"]
476
+ # Dir.children('tmp1/') # => []
477
+ # FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk') # => 0
478
+ # Dir.children('tmp1/') # => ["t.lnk"]
479
+ #
480
+ # When +src+ is the path to an existing file
481
+ # and +dest+ is the path to an existing directory,
482
+ # creates a hard link at <tt>dest/src</tt> pointing to +src+; returns zero:
275
483
  #
276
- # :call-seq:
277
- # FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
278
- # FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
279
- # FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
484
+ # Dir.children('tmp2') # => ["t.dat"]
485
+ # Dir.children('tmp3') # => []
486
+ # FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0
487
+ # Dir.children('tmp3') # => ["t.dat"]
280
488
  #
281
- # In the first form, creates a hard link +link+ which points to +target+.
282
- # If +link+ already exists, raises Errno::EEXIST.
283
- # But if the +force+ option is set, overwrites +link+.
489
+ # When +src+ is an array of paths to existing files
490
+ # and +dest+ is the path to an existing directory,
491
+ # then for each path +target+ in +src+,
492
+ # creates a hard link at <tt>dest/target</tt> pointing to +target+;
493
+ # returns +src+:
284
494
  #
285
- # FileUtils.ln 'gcc', 'cc', verbose: true
286
- # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
495
+ # Dir.children('tmp4/') # => []
496
+ # FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/') # => ["tmp0/t.txt", "tmp2/t.dat"]
497
+ # Dir.children('tmp4/') # => ["t.dat", "t.txt"]
287
498
  #
288
- # In the second form, creates a link +dir/target+ pointing to +target+.
289
- # In the third form, creates several hard links in the directory +dir+,
290
- # pointing to each item in +targets+.
291
- # If +dir+ is not a directory, raises Errno::ENOTDIR.
499
+ # Keyword arguments:
292
500
  #
293
- # FileUtils.cd '/sbin'
294
- # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
501
+ # - <tt>force: true</tt> - overwrites +dest+ if it exists.
502
+ # - <tt>noop: true</tt> - does not create links.
503
+ # - <tt>verbose: true</tt> - prints an equivalent command:
504
+ #
505
+ # FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk', verbose: true)
506
+ # FileUtils.ln('tmp2/t.dat', 'tmp3', verbose: true)
507
+ # FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/', verbose: true)
508
+ #
509
+ # Output:
510
+ #
511
+ # ln tmp0/t.txt tmp1/t.lnk
512
+ # ln tmp2/t.dat tmp3
513
+ # ln tmp0/t.txt tmp2/t.dat tmp4/
514
+ #
515
+ # Raises an exception if +dest+ is the path to an existing file
516
+ # and keyword argument +force+ is not +true+.
517
+ #
518
+ # FileUtils#link is an alias for FileUtils#ln.
519
+ #
520
+ # Related: FileUtils.link_entry (has different options).
295
521
  #
296
522
  def ln(src, dest, force: nil, noop: nil, verbose: nil)
297
523
  fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -306,28 +532,103 @@ module FileUtils
306
532
  alias link ln
307
533
  module_function :link
308
534
 
309
- #
310
- # Hard link +src+ to +dest+. If +src+ is a directory, this method links
311
- # all its contents recursively. If +dest+ is a directory, links
312
- # +src+ to +dest/src+.
313
- #
314
- # +src+ can be a list of files.
315
- #
316
- # If +dereference_root+ is true, this method dereference tree root.
317
- #
318
- # If +remove_destination+ is true, this method removes each destination file before copy.
319
- #
320
- # FileUtils.rm_r site_ruby + '/mylib', force: true
321
- # FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
322
- #
323
- # # Examples of linking several files to target directory.
324
- # FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
325
- # FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true
326
- #
327
- # # If you want to link all contents of a directory instead of the
328
- # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
329
- # # use the following code.
330
- # FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't.
535
+ # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link].
536
+ #
537
+ # Arguments +src+ (a single path or an array of paths)
538
+ # and +dest+ (a single path)
539
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
540
+ #
541
+ # If +src+ is the path to a directory and +dest+ does not exist,
542
+ # creates links +dest+ and descendents pointing to +src+ and its descendents:
543
+ #
544
+ # tree('src0')
545
+ # # => src0
546
+ # # |-- sub0
547
+ # # | |-- src0.txt
548
+ # # | `-- src1.txt
549
+ # # `-- sub1
550
+ # # |-- src2.txt
551
+ # # `-- src3.txt
552
+ # File.exist?('dest0') # => false
553
+ # FileUtils.cp_lr('src0', 'dest0')
554
+ # tree('dest0')
555
+ # # => dest0
556
+ # # |-- sub0
557
+ # # | |-- src0.txt
558
+ # # | `-- src1.txt
559
+ # # `-- sub1
560
+ # # |-- src2.txt
561
+ # # `-- src3.txt
562
+ #
563
+ # If +src+ and +dest+ are both paths to directories,
564
+ # creates links <tt>dest/src</tt> and descendents
565
+ # pointing to +src+ and its descendents:
566
+ #
567
+ # tree('src1')
568
+ # # => src1
569
+ # # |-- sub0
570
+ # # | |-- src0.txt
571
+ # # | `-- src1.txt
572
+ # # `-- sub1
573
+ # # |-- src2.txt
574
+ # # `-- src3.txt
575
+ # FileUtils.mkdir('dest1')
576
+ # FileUtils.cp_lr('src1', 'dest1')
577
+ # tree('dest1')
578
+ # # => dest1
579
+ # # `-- src1
580
+ # # |-- sub0
581
+ # # | |-- src0.txt
582
+ # # | `-- src1.txt
583
+ # # `-- sub1
584
+ # # |-- src2.txt
585
+ # # `-- src3.txt
586
+ #
587
+ # If +src+ is an array of paths to entries and +dest+ is the path to a directory,
588
+ # for each path +filepath+ in +src+, creates a link at <tt>dest/filepath</tt>
589
+ # pointing to that path:
590
+ #
591
+ # tree('src2')
592
+ # # => src2
593
+ # # |-- sub0
594
+ # # | |-- src0.txt
595
+ # # | `-- src1.txt
596
+ # # `-- sub1
597
+ # # |-- src2.txt
598
+ # # `-- src3.txt
599
+ # FileUtils.mkdir('dest2')
600
+ # FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2')
601
+ # tree('dest2')
602
+ # # => dest2
603
+ # # |-- sub0
604
+ # # | |-- src0.txt
605
+ # # | `-- src1.txt
606
+ # # `-- sub1
607
+ # # |-- src2.txt
608
+ # # `-- src3.txt
609
+ #
610
+ # Keyword arguments:
611
+ #
612
+ # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link,
613
+ # does not dereference it.
614
+ # - <tt>noop: true</tt> - does not create links.
615
+ # - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
616
+ # - <tt>verbose: true</tt> - prints an equivalent command:
617
+ #
618
+ # FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true)
619
+ # FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true)
620
+ # FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true)
621
+ #
622
+ # Output:
623
+ #
624
+ # cp -lr src0 dest0
625
+ # cp -lr src1 dest1
626
+ # cp -lr src2/sub0 src2/sub1 dest2
627
+ #
628
+ # Raises an exception if +dest+ is the path to an existing file or directory
629
+ # and keyword argument <tt>remove_destination: true</tt> is not given.
630
+ #
631
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
331
632
  #
332
633
  def cp_lr(src, dest, noop: nil, verbose: nil,
333
634
  dereference_root: true, remove_destination: false)
@@ -339,27 +640,81 @@ module FileUtils
339
640
  end
340
641
  module_function :cp_lr
341
642
 
643
+ # Creates {symbolic links}[https://en.wikipedia.org/wiki/Symbolic_link].
644
+ #
645
+ # Arguments +src+ (a single path or an array of paths)
646
+ # and +dest+ (a single path)
647
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
648
+ #
649
+ # If +src+ is the path to an existing file:
650
+ #
651
+ # - When +dest+ is the path to a non-existent file,
652
+ # creates a symbolic link at +dest+ pointing to +src+:
653
+ #
654
+ # FileUtils.touch('src0.txt')
655
+ # File.exist?('dest0.txt') # => false
656
+ # FileUtils.ln_s('src0.txt', 'dest0.txt')
657
+ # File.symlink?('dest0.txt') # => true
342
658
  #
343
- # :call-seq:
344
- # FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
345
- # FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
346
- # FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
659
+ # - When +dest+ is the path to an existing file,
660
+ # creates a symbolic link at +dest+ pointing to +src+
661
+ # if and only if keyword argument <tt>force: true</tt> is given
662
+ # (raises an exception otherwise):
347
663
  #
348
- # In the first form, creates a symbolic link +link+ which points to +target+.
349
- # If +link+ already exists, raises Errno::EEXIST.
350
- # But if the <tt>force</tt> option is set, overwrites +link+.
664
+ # FileUtils.touch('src1.txt')
665
+ # FileUtils.touch('dest1.txt')
666
+ # FileUtils.ln_s('src1.txt', 'dest1.txt', force: true)
667
+ # FileTest.symlink?('dest1.txt') # => true
351
668
  #
352
- # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
353
- # FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
669
+ # FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST.
354
670
  #
355
- # In the second form, creates a link +dir/target+ pointing to +target+.
356
- # In the third form, creates several symbolic links in the directory +dir+,
357
- # pointing to each item in +targets+.
358
- # If +dir+ is not a directory, raises Errno::ENOTDIR.
671
+ # If +dest+ is the path to a directory,
672
+ # creates a symbolic link at <tt>dest/src</tt> pointing to +src+:
359
673
  #
360
- # FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
674
+ # FileUtils.touch('src2.txt')
675
+ # FileUtils.mkdir('destdir2')
676
+ # FileUtils.ln_s('src2.txt', 'destdir2')
677
+ # File.symlink?('destdir2/src2.txt') # => true
361
678
  #
362
- def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
679
+ # If +src+ is an array of paths to existing files and +dest+ is a directory,
680
+ # for each child +child+ in +src+ creates a symbolic link <tt>dest/child</tt>
681
+ # pointing to +child+:
682
+ #
683
+ # FileUtils.mkdir('srcdir3')
684
+ # FileUtils.touch('srcdir3/src0.txt')
685
+ # FileUtils.touch('srcdir3/src1.txt')
686
+ # FileUtils.mkdir('destdir3')
687
+ # FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3')
688
+ # File.symlink?('destdir3/src0.txt') # => true
689
+ # File.symlink?('destdir3/src1.txt') # => true
690
+ #
691
+ # Keyword arguments:
692
+ #
693
+ # - <tt>force: true</tt> - overwrites +dest+ if it exists.
694
+ # - <tt>relative: false</tt> - create links relative to +dest+.
695
+ # - <tt>noop: true</tt> - does not create links.
696
+ # - <tt>verbose: true</tt> - prints an equivalent command:
697
+ #
698
+ # FileUtils.ln_s('src0.txt', 'dest0.txt', noop: true, verbose: true)
699
+ # FileUtils.ln_s('src1.txt', 'destdir1', noop: true, verbose: true)
700
+ # FileUtils.ln_s('src2.txt', 'dest2.txt', force: true, noop: true, verbose: true)
701
+ # FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3', noop: true, verbose: true)
702
+ #
703
+ # Output:
704
+ #
705
+ # ln -s src0.txt dest0.txt
706
+ # ln -s src1.txt destdir1
707
+ # ln -sf src2.txt dest2.txt
708
+ # ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3
709
+ #
710
+ # FileUtils.symlink is an alias for FileUtils.ln_s.
711
+ #
712
+ # Related: FileUtils.ln_sf.
713
+ #
714
+ def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil)
715
+ if relative
716
+ return ln_sr(src, dest, force: force, noop: noop, verbose: verbose)
717
+ end
363
718
  fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
364
719
  return if noop
365
720
  fu_each_src_dest0(src, dest) do |s,d|
@@ -372,29 +727,95 @@ module FileUtils
372
727
  alias symlink ln_s
373
728
  module_function :symlink
374
729
 
375
- #
376
- # :call-seq:
377
- # FileUtils.ln_sf(*args)
378
- #
379
- # Same as
380
- #
381
- # FileUtils.ln_s(*args, force: true)
730
+ # Like FileUtils.ln_s, but always with keyword argument <tt>force: true</tt> given.
382
731
  #
383
732
  def ln_sf(src, dest, noop: nil, verbose: nil)
384
733
  ln_s src, dest, force: true, noop: noop, verbose: verbose
385
734
  end
386
735
  module_function :ln_sf
387
736
 
737
+ # Like FileUtils.ln_s, but create links relative to +dest+.
388
738
  #
389
- # Hard links a file system entry +src+ to +dest+.
390
- # If +src+ is a directory, this method links its contents recursively.
739
+ def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
740
+ options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}"
741
+ dest = File.path(dest)
742
+ srcs = Array(src)
743
+ link = proc do |s, target_dir_p = true|
744
+ s = File.path(s)
745
+ if target_dir_p
746
+ d = File.join(destdirs = dest, File.basename(s))
747
+ else
748
+ destdirs = File.dirname(d = dest)
749
+ end
750
+ destdirs = fu_split_path(File.realpath(destdirs))
751
+ if fu_starting_path?(s)
752
+ srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
753
+ base = fu_relative_components_from(srcdirs, destdirs)
754
+ s = File.join(*base)
755
+ else
756
+ srcdirs = fu_clean_components(*fu_split_path(s))
757
+ base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs)
758
+ while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last)
759
+ srcdirs.shift
760
+ base.pop
761
+ end
762
+ s = File.join(*base, *srcdirs)
763
+ end
764
+ fu_output_message "ln -s#{options} #{s} #{d}" if verbose
765
+ next if noop
766
+ remove_file d, true if force
767
+ File.symlink s, d
768
+ end
769
+ case srcs.size
770
+ when 0
771
+ when 1
772
+ link[srcs[0], target_directory && File.directory?(dest)]
773
+ else
774
+ srcs.each(&link)
775
+ end
776
+ end
777
+ module_function :ln_sr
778
+
779
+ # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+.
780
+ #
781
+ # Arguments +src+ and +dest+
782
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
783
+ #
784
+ # If +src+ is the path to a file and +dest+ does not exist,
785
+ # creates a hard link at +dest+ pointing to +src+:
786
+ #
787
+ # FileUtils.touch('src0.txt')
788
+ # File.exist?('dest0.txt') # => false
789
+ # FileUtils.link_entry('src0.txt', 'dest0.txt')
790
+ # File.file?('dest0.txt') # => true
391
791
  #
392
- # Both of +src+ and +dest+ must be a path name.
393
- # +src+ must exist, +dest+ must not exist.
792
+ # If +src+ is the path to a directory and +dest+ does not exist,
793
+ # recursively creates hard links at +dest+ pointing to paths in +src+:
394
794
  #
395
- # If +dereference_root+ is true, this method dereferences the tree root.
795
+ # FileUtils.mkdir_p(['src1/dir0', 'src1/dir1'])
796
+ # src_file_paths = [
797
+ # 'src1/dir0/t0.txt',
798
+ # 'src1/dir0/t1.txt',
799
+ # 'src1/dir1/t2.txt',
800
+ # 'src1/dir1/t3.txt',
801
+ # ]
802
+ # FileUtils.touch(src_file_paths)
803
+ # File.directory?('dest1') # => true
804
+ # FileUtils.link_entry('src1', 'dest1')
805
+ # File.file?('dest1/dir0/t0.txt') # => true
806
+ # File.file?('dest1/dir0/t1.txt') # => true
807
+ # File.file?('dest1/dir1/t2.txt') # => true
808
+ # File.file?('dest1/dir1/t3.txt') # => true
396
809
  #
397
- # If +remove_destination+ is true, this method removes each destination file before copy.
810
+ # Keyword arguments:
811
+ #
812
+ # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link.
813
+ # - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
814
+ #
815
+ # Raises an exception if +dest+ is the path to an existing file or directory
816
+ # and keyword argument <tt>remove_destination: true</tt> is not given.
817
+ #
818
+ # Related: FileUtils.ln (has different options).
398
819
  #
399
820
  def link_entry(src, dest, dereference_root = false, remove_destination = false)
400
821
  Entry_.new(src, nil, dereference_root).traverse do |ent|
@@ -405,16 +826,59 @@ module FileUtils
405
826
  end
406
827
  module_function :link_entry
407
828
 
829
+ # Copies files.
830
+ #
831
+ # Arguments +src+ (a single path or an array of paths)
832
+ # and +dest+ (a single path)
833
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
408
834
  #
409
- # Copies a file content +src+ to +dest+. If +dest+ is a directory,
410
- # copies +src+ to +dest/src+.
835
+ # If +src+ is the path to a file and +dest+ is not the path to a directory,
836
+ # copies +src+ to +dest+:
411
837
  #
412
- # If +src+ is a list of files, then +dest+ must be a directory.
838
+ # FileUtils.touch('src0.txt')
839
+ # File.exist?('dest0.txt') # => false
840
+ # FileUtils.cp('src0.txt', 'dest0.txt')
841
+ # File.file?('dest0.txt') # => true
413
842
  #
414
- # FileUtils.cp 'eval.c', 'eval.c.org'
415
- # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
416
- # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true
417
- # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
843
+ # If +src+ is the path to a file and +dest+ is the path to a directory,
844
+ # copies +src+ to <tt>dest/src</tt>:
845
+ #
846
+ # FileUtils.touch('src1.txt')
847
+ # FileUtils.mkdir('dest1')
848
+ # FileUtils.cp('src1.txt', 'dest1')
849
+ # File.file?('dest1/src1.txt') # => true
850
+ #
851
+ # If +src+ is an array of paths to files and +dest+ is the path to a directory,
852
+ # copies from each +src+ to +dest+:
853
+ #
854
+ # src_file_paths = ['src2.txt', 'src2.dat']
855
+ # FileUtils.touch(src_file_paths)
856
+ # FileUtils.mkdir('dest2')
857
+ # FileUtils.cp(src_file_paths, 'dest2')
858
+ # File.file?('dest2/src2.txt') # => true
859
+ # File.file?('dest2/src2.dat') # => true
860
+ #
861
+ # Keyword arguments:
862
+ #
863
+ # - <tt>preserve: true</tt> - preserves file times.
864
+ # - <tt>noop: true</tt> - does not copy files.
865
+ # - <tt>verbose: true</tt> - prints an equivalent command:
866
+ #
867
+ # FileUtils.cp('src0.txt', 'dest0.txt', noop: true, verbose: true)
868
+ # FileUtils.cp('src1.txt', 'dest1', noop: true, verbose: true)
869
+ # FileUtils.cp(src_file_paths, 'dest2', noop: true, verbose: true)
870
+ #
871
+ # Output:
872
+ #
873
+ # cp src0.txt dest0.txt
874
+ # cp src1.txt dest1
875
+ # cp src2.txt src2.dat dest2
876
+ #
877
+ # Raises an exception if +src+ is a directory.
878
+ #
879
+ # FileUtils.copy is an alias for FileUtils.cp.
880
+ #
881
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
418
882
  #
419
883
  def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
420
884
  fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -428,30 +892,105 @@ module FileUtils
428
892
  alias copy cp
429
893
  module_function :copy
430
894
 
431
- #
432
- # Copies +src+ to +dest+. If +src+ is a directory, this method copies
433
- # all its contents recursively. If +dest+ is a directory, copies
434
- # +src+ to +dest/src+.
435
- #
436
- # +src+ can be a list of files.
437
- #
438
- # If +dereference_root+ is true, this method dereference tree root.
439
- #
440
- # If +remove_destination+ is true, this method removes each destination file before copy.
441
- #
442
- # # Installing Ruby library "mylib" under the site_ruby
443
- # FileUtils.rm_r site_ruby + '/mylib', force: true
444
- # FileUtils.cp_r 'lib/', site_ruby + '/mylib'
445
- #
446
- # # Examples of copying several files to target directory.
447
- # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
448
- # FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true
449
- #
450
- # # If you want to copy all contents of a directory instead of the
451
- # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
452
- # # use following code.
453
- # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
454
- # # but this doesn't.
895
+ # Recursively copies files.
896
+ #
897
+ # Arguments +src+ (a single path or an array of paths)
898
+ # and +dest+ (a single path)
899
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
900
+ #
901
+ # The mode, owner, and group are retained in the copy;
902
+ # to change those, use FileUtils.install instead.
903
+ #
904
+ # If +src+ is the path to a file and +dest+ is not the path to a directory,
905
+ # copies +src+ to +dest+:
906
+ #
907
+ # FileUtils.touch('src0.txt')
908
+ # File.exist?('dest0.txt') # => false
909
+ # FileUtils.cp_r('src0.txt', 'dest0.txt')
910
+ # File.file?('dest0.txt') # => true
911
+ #
912
+ # If +src+ is the path to a file and +dest+ is the path to a directory,
913
+ # copies +src+ to <tt>dest/src</tt>:
914
+ #
915
+ # FileUtils.touch('src1.txt')
916
+ # FileUtils.mkdir('dest1')
917
+ # FileUtils.cp_r('src1.txt', 'dest1')
918
+ # File.file?('dest1/src1.txt') # => true
919
+ #
920
+ # If +src+ is the path to a directory and +dest+ does not exist,
921
+ # recursively copies +src+ to +dest+:
922
+ #
923
+ # tree('src2')
924
+ # # => src2
925
+ # # |-- dir0
926
+ # # | |-- src0.txt
927
+ # # | `-- src1.txt
928
+ # # `-- dir1
929
+ # # |-- src2.txt
930
+ # # `-- src3.txt
931
+ # FileUtils.exist?('dest2') # => false
932
+ # FileUtils.cp_r('src2', 'dest2')
933
+ # tree('dest2')
934
+ # # => dest2
935
+ # # |-- dir0
936
+ # # | |-- src0.txt
937
+ # # | `-- src1.txt
938
+ # # `-- dir1
939
+ # # |-- src2.txt
940
+ # # `-- src3.txt
941
+ #
942
+ # If +src+ and +dest+ are paths to directories,
943
+ # recursively copies +src+ to <tt>dest/src</tt>:
944
+ #
945
+ # tree('src3')
946
+ # # => src3
947
+ # # |-- dir0
948
+ # # | |-- src0.txt
949
+ # # | `-- src1.txt
950
+ # # `-- dir1
951
+ # # |-- src2.txt
952
+ # # `-- src3.txt
953
+ # FileUtils.mkdir('dest3')
954
+ # FileUtils.cp_r('src3', 'dest3')
955
+ # tree('dest3')
956
+ # # => dest3
957
+ # # `-- src3
958
+ # # |-- dir0
959
+ # # | |-- src0.txt
960
+ # # | `-- src1.txt
961
+ # # `-- dir1
962
+ # # |-- src2.txt
963
+ # # `-- src3.txt
964
+ #
965
+ # If +src+ is an array of paths and +dest+ is a directory,
966
+ # recursively copies from each path in +src+ to +dest+;
967
+ # the paths in +src+ may point to files and/or directories.
968
+ #
969
+ # Keyword arguments:
970
+ #
971
+ # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link,
972
+ # does not dereference it.
973
+ # - <tt>noop: true</tt> - does not copy files.
974
+ # - <tt>preserve: true</tt> - preserves file times.
975
+ # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
976
+ # - <tt>verbose: true</tt> - prints an equivalent command:
977
+ #
978
+ # FileUtils.cp_r('src0.txt', 'dest0.txt', noop: true, verbose: true)
979
+ # FileUtils.cp_r('src1.txt', 'dest1', noop: true, verbose: true)
980
+ # FileUtils.cp_r('src2', 'dest2', noop: true, verbose: true)
981
+ # FileUtils.cp_r('src3', 'dest3', noop: true, verbose: true)
982
+ #
983
+ # Output:
984
+ #
985
+ # cp -r src0.txt dest0.txt
986
+ # cp -r src1.txt dest1
987
+ # cp -r src2 dest2
988
+ # cp -r src3 dest3
989
+ #
990
+ # Raises an exception of +src+ is the path to a directory
991
+ # and +dest+ is the path to a file.
992
+ #
993
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
455
994
  #
456
995
  def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
457
996
  dereference_root: true, remove_destination: nil)
@@ -463,21 +1002,50 @@ module FileUtils
463
1002
  end
464
1003
  module_function :cp_r
465
1004
 
1005
+ # Recursively copies files from +src+ to +dest+.
1006
+ #
1007
+ # Arguments +src+ and +dest+
1008
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1009
+ #
1010
+ # If +src+ is the path to a file, copies +src+ to +dest+:
1011
+ #
1012
+ # FileUtils.touch('src0.txt')
1013
+ # File.exist?('dest0.txt') # => false
1014
+ # FileUtils.copy_entry('src0.txt', 'dest0.txt')
1015
+ # File.file?('dest0.txt') # => true
466
1016
  #
467
- # Copies a file system entry +src+ to +dest+.
468
- # If +src+ is a directory, this method copies its contents recursively.
469
- # This method preserves file types, c.f. symlink, directory...
470
- # (FIFO, device files and etc. are not supported yet)
1017
+ # If +src+ is a directory, recursively copies +src+ to +dest+:
471
1018
  #
472
- # Both of +src+ and +dest+ must be a path name.
473
- # +src+ must exist, +dest+ must not exist.
1019
+ # tree('src1')
1020
+ # # => src1
1021
+ # # |-- dir0
1022
+ # # | |-- src0.txt
1023
+ # # | `-- src1.txt
1024
+ # # `-- dir1
1025
+ # # |-- src2.txt
1026
+ # # `-- src3.txt
1027
+ # FileUtils.copy_entry('src1', 'dest1')
1028
+ # tree('dest1')
1029
+ # # => dest1
1030
+ # # |-- dir0
1031
+ # # | |-- src0.txt
1032
+ # # | `-- src1.txt
1033
+ # # `-- dir1
1034
+ # # |-- src2.txt
1035
+ # # `-- src3.txt
474
1036
  #
475
- # If +preserve+ is true, this method preserves owner, group, and
476
- # modified time. Permissions are copied regardless +preserve+.
1037
+ # The recursive copying preserves file types for regular files,
1038
+ # directories, and symbolic links;
1039
+ # other file types (FIFO streams, device files, etc.) are not supported.
477
1040
  #
478
- # If +dereference_root+ is true, this method dereference tree root.
1041
+ # Keyword arguments:
479
1042
  #
480
- # If +remove_destination+ is true, this method removes each destination file before copy.
1043
+ # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link,
1044
+ # follows the link.
1045
+ # - <tt>preserve: true</tt> - preserves file times.
1046
+ # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
1047
+ #
1048
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
481
1049
  #
482
1050
  def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
483
1051
  if dereference_root
@@ -495,9 +1063,25 @@ module FileUtils
495
1063
  end
496
1064
  module_function :copy_entry
497
1065
 
1066
+ # Copies file from +src+ to +dest+, which should not be directories.
1067
+ #
1068
+ # Arguments +src+ and +dest+
1069
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1070
+ #
1071
+ # Examples:
1072
+ #
1073
+ # FileUtils.touch('src0.txt')
1074
+ # FileUtils.copy_file('src0.txt', 'dest0.txt')
1075
+ # File.file?('dest0.txt') # => true
498
1076
  #
499
- # Copies file contents of +src+ to +dest+.
500
- # Both of +src+ and +dest+ must be a path name.
1077
+ # Keyword arguments:
1078
+ #
1079
+ # - <tt>dereference: false</tt> - if +src+ is a symbolic link,
1080
+ # does not follow the link.
1081
+ # - <tt>preserve: true</tt> - preserves file times.
1082
+ # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
1083
+ #
1084
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
501
1085
  #
502
1086
  def copy_file(src, dest, preserve = false, dereference = true)
503
1087
  ent = Entry_.new(src, nil, dereference)
@@ -506,25 +1090,81 @@ module FileUtils
506
1090
  end
507
1091
  module_function :copy_file
508
1092
 
1093
+ # Copies \IO stream +src+ to \IO stream +dest+ via
1094
+ # {IO.copy_stream}[https://docs.ruby-lang.org/en/master/IO.html#method-c-copy_stream].
509
1095
  #
510
- # Copies stream +src+ to +dest+.
511
- # +src+ must respond to #read(n) and
512
- # +dest+ must respond to #write(str).
1096
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
513
1097
  #
514
1098
  def copy_stream(src, dest)
515
1099
  IO.copy_stream(src, dest)
516
1100
  end
517
1101
  module_function :copy_stream
518
1102
 
519
- #
520
- # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
521
- # disk partition, the file is copied then the original file is removed.
522
- #
523
- # FileUtils.mv 'badname.rb', 'goodname.rb'
524
- # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error
525
- #
526
- # FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
527
- # FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true
1103
+ # Moves entries.
1104
+ #
1105
+ # Arguments +src+ (a single path or an array of paths)
1106
+ # and +dest+ (a single path)
1107
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1108
+ #
1109
+ # If +src+ and +dest+ are on different file systems,
1110
+ # first copies, then removes +src+.
1111
+ #
1112
+ # May cause a local vulnerability if not called with keyword argument
1113
+ # <tt>secure: true</tt>;
1114
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
1115
+ #
1116
+ # If +src+ is the path to a single file or directory and +dest+ does not exist,
1117
+ # moves +src+ to +dest+:
1118
+ #
1119
+ # tree('src0')
1120
+ # # => src0
1121
+ # # |-- src0.txt
1122
+ # # `-- src1.txt
1123
+ # File.exist?('dest0') # => false
1124
+ # FileUtils.mv('src0', 'dest0')
1125
+ # File.exist?('src0') # => false
1126
+ # tree('dest0')
1127
+ # # => dest0
1128
+ # # |-- src0.txt
1129
+ # # `-- src1.txt
1130
+ #
1131
+ # If +src+ is an array of paths to files and directories
1132
+ # and +dest+ is the path to a directory,
1133
+ # copies from each path in the array to +dest+:
1134
+ #
1135
+ # File.file?('src1.txt') # => true
1136
+ # tree('src1')
1137
+ # # => src1
1138
+ # # |-- src.dat
1139
+ # # `-- src.txt
1140
+ # Dir.empty?('dest1') # => true
1141
+ # FileUtils.mv(['src1.txt', 'src1'], 'dest1')
1142
+ # tree('dest1')
1143
+ # # => dest1
1144
+ # # |-- src1
1145
+ # # | |-- src.dat
1146
+ # # | `-- src.txt
1147
+ # # `-- src1.txt
1148
+ #
1149
+ # Keyword arguments:
1150
+ #
1151
+ # - <tt>force: true</tt> - if the move includes removing +src+
1152
+ # (that is, if +src+ and +dest+ are on different file systems),
1153
+ # ignores raised exceptions of StandardError and its descendants.
1154
+ # - <tt>noop: true</tt> - does not move files.
1155
+ # - <tt>secure: true</tt> - removes +src+ securely;
1156
+ # see details at FileUtils.remove_entry_secure.
1157
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1158
+ #
1159
+ # FileUtils.mv('src0', 'dest0', noop: true, verbose: true)
1160
+ # FileUtils.mv(['src1.txt', 'src1'], 'dest1', noop: true, verbose: true)
1161
+ #
1162
+ # Output:
1163
+ #
1164
+ # mv src0 dest0
1165
+ # mv src1.txt src1 dest1
1166
+ #
1167
+ # FileUtils.move is an alias for FileUtils.mv.
528
1168
  #
529
1169
  def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
530
1170
  fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -558,13 +1198,34 @@ module FileUtils
558
1198
  alias move mv
559
1199
  module_function :move
560
1200
 
1201
+ # Removes entries at the paths in the given +list+
1202
+ # (a single path or an array of paths)
1203
+ # returns +list+, if it is an array, <tt>[list]</tt> otherwise.
1204
+ #
1205
+ # Argument +list+ or its elements
1206
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1207
+ #
1208
+ # With no keyword arguments, removes files at the paths given in +list+:
1209
+ #
1210
+ # FileUtils.touch(['src0.txt', 'src0.dat'])
1211
+ # FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"]
561
1212
  #
562
- # Remove file(s) specified in +list+. This method cannot remove directories.
563
- # All StandardErrors are ignored when the :force option is set.
1213
+ # Keyword arguments:
564
1214
  #
565
- # FileUtils.rm %w( junk.txt dust.txt )
566
- # FileUtils.rm Dir.glob('*.so')
567
- # FileUtils.rm 'NotExistFile', force: true # never raises exception
1215
+ # - <tt>force: true</tt> - ignores raised exceptions of StandardError
1216
+ # and its descendants.
1217
+ # - <tt>noop: true</tt> - does not remove files; returns +nil+.
1218
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1219
+ #
1220
+ # FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true)
1221
+ #
1222
+ # Output:
1223
+ #
1224
+ # rm src0.dat src0.txt
1225
+ #
1226
+ # FileUtils.remove is an alias for FileUtils.rm.
1227
+ #
1228
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
568
1229
  #
569
1230
  def rm(list, force: nil, noop: nil, verbose: nil)
570
1231
  list = fu_list(list)
@@ -580,10 +1241,18 @@ module FileUtils
580
1241
  alias remove rm
581
1242
  module_function :remove
582
1243
 
1244
+ # Equivalent to:
1245
+ #
1246
+ # FileUtils.rm(list, force: true, **kwargs)
1247
+ #
1248
+ # Argument +list+ (a single path or an array of paths)
1249
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1250
+ #
1251
+ # See FileUtils.rm for keyword arguments.
583
1252
  #
584
- # Equivalent to
1253
+ # FileUtils.safe_unlink is an alias for FileUtils.rm_f.
585
1254
  #
586
- # FileUtils.rm(list, force: true)
1255
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
587
1256
  #
588
1257
  def rm_f(list, noop: nil, verbose: nil)
589
1258
  rm list, force: true, noop: noop, verbose: verbose
@@ -593,24 +1262,55 @@ module FileUtils
593
1262
  alias safe_unlink rm_f
594
1263
  module_function :safe_unlink
595
1264
 
1265
+ # Removes entries at the paths in the given +list+
1266
+ # (a single path or an array of paths);
1267
+ # returns +list+, if it is an array, <tt>[list]</tt> otherwise.
1268
+ #
1269
+ # Argument +list+ or its elements
1270
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1271
+ #
1272
+ # May cause a local vulnerability if not called with keyword argument
1273
+ # <tt>secure: true</tt>;
1274
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
1275
+ #
1276
+ # For each file path, removes the file at that path:
1277
+ #
1278
+ # FileUtils.touch(['src0.txt', 'src0.dat'])
1279
+ # FileUtils.rm_r(['src0.dat', 'src0.txt'])
1280
+ # File.exist?('src0.txt') # => false
1281
+ # File.exist?('src0.dat') # => false
1282
+ #
1283
+ # For each directory path, recursively removes files and directories:
1284
+ #
1285
+ # tree('src1')
1286
+ # # => src1
1287
+ # # |-- dir0
1288
+ # # | |-- src0.txt
1289
+ # # | `-- src1.txt
1290
+ # # `-- dir1
1291
+ # # |-- src2.txt
1292
+ # # `-- src3.txt
1293
+ # FileUtils.rm_r('src1')
1294
+ # File.exist?('src1') # => false
1295
+ #
1296
+ # Keyword arguments:
1297
+ #
1298
+ # - <tt>force: true</tt> - ignores raised exceptions of StandardError
1299
+ # and its descendants.
1300
+ # - <tt>noop: true</tt> - does not remove entries; returns +nil+.
1301
+ # - <tt>secure: true</tt> - removes +src+ securely;
1302
+ # see details at FileUtils.remove_entry_secure.
1303
+ # - <tt>verbose: true</tt> - prints an equivalent command:
596
1304
  #
597
- # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
598
- # removes its all contents recursively. This method ignores
599
- # StandardError when :force option is set.
1305
+ # FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true)
1306
+ # FileUtils.rm_r('src1', noop: true, verbose: true)
600
1307
  #
601
- # FileUtils.rm_r Dir.glob('/tmp/*')
602
- # FileUtils.rm_r 'some_dir', force: true
1308
+ # Output:
603
1309
  #
604
- # WARNING: This method causes local vulnerability
605
- # if one of parent directories or removing directory tree are world
606
- # writable (including /tmp, whose permission is 1777), and the current
607
- # process has strong privilege such as Unix super user (root), and the
608
- # system has symbolic link. For secure removing, read the documentation
609
- # of remove_entry_secure carefully, and set :secure option to true.
610
- # Default is <tt>secure: false</tt>.
1310
+ # rm -r src0.dat src0.txt
1311
+ # rm -r src1
611
1312
  #
612
- # NOTE: This method calls remove_entry_secure if :secure option is set.
613
- # See also remove_entry_secure.
1313
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
614
1314
  #
615
1315
  def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
616
1316
  list = fu_list(list)
@@ -626,13 +1326,22 @@ module FileUtils
626
1326
  end
627
1327
  module_function :rm_r
628
1328
 
1329
+ # Equivalent to:
629
1330
  #
630
- # Equivalent to
1331
+ # FileUtils.rm_r(list, force: true, **kwargs)
631
1332
  #
632
- # FileUtils.rm_r(list, force: true)
1333
+ # Argument +list+ or its elements
1334
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
633
1335
  #
634
- # WARNING: This method causes local vulnerability.
635
- # Read the documentation of rm_r first.
1336
+ # May cause a local vulnerability if not called with keyword argument
1337
+ # <tt>secure: true</tt>;
1338
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
1339
+ #
1340
+ # See FileUtils.rm_r for keyword arguments.
1341
+ #
1342
+ # FileUtils.rmtree is an alias for FileUtils.rm_rf.
1343
+ #
1344
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
636
1345
  #
637
1346
  def rm_rf(list, noop: nil, verbose: nil, secure: nil)
638
1347
  rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
@@ -642,37 +1351,20 @@ module FileUtils
642
1351
  alias rmtree rm_rf
643
1352
  module_function :rmtree
644
1353
 
1354
+ # Securely removes the entry given by +path+,
1355
+ # which should be the entry for a regular file, a symbolic link,
1356
+ # or a directory.
645
1357
  #
646
- # This method removes a file system entry +path+. +path+ shall be a
647
- # regular file, a directory, or something. If +path+ is a directory,
648
- # remove it recursively. This method is required to avoid TOCTTOU
649
- # (time-of-check-to-time-of-use) local security vulnerability of rm_r.
650
- # #rm_r causes security hole when:
651
- #
652
- # * Parent directory is world writable (including /tmp).
653
- # * Removing directory tree includes world writable directory.
654
- # * The system has symbolic link.
655
- #
656
- # To avoid this security hole, this method applies special preprocess.
657
- # If +path+ is a directory, this method chown(2) and chmod(2) all
658
- # removing directories. This requires the current process is the
659
- # owner of the removing whole directory tree, or is the super user (root).
1358
+ # Argument +path+
1359
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
660
1360
  #
661
- # WARNING: You must ensure that *ALL* parent directories cannot be
662
- # moved by other untrusted users. For example, parent directories
663
- # should not be owned by untrusted users, and should not be world
664
- # writable except when the sticky bit set.
1361
+ # Avoids a local vulnerability that can exist in certain circumstances;
1362
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
665
1363
  #
666
- # WARNING: Only the owner of the removing directory tree, or Unix super
667
- # user (root) should invoke this method. Otherwise this method does not
668
- # work.
1364
+ # Optional argument +force+ specifies whether to ignore
1365
+ # raised exceptions of StandardError and its descendants.
669
1366
  #
670
- # For details of this security vulnerability, see Perl's case:
671
- #
672
- # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
673
- # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
674
- #
675
- # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
1367
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
676
1368
  #
677
1369
  def remove_entry_secure(path, force = false)
678
1370
  unless fu_have_symlink?
@@ -760,12 +1452,17 @@ module FileUtils
760
1452
  end
761
1453
  private_module_function :fu_stat_identical_entry?
762
1454
 
1455
+ # Removes the entry given by +path+,
1456
+ # which should be the entry for a regular file, a symbolic link,
1457
+ # or a directory.
763
1458
  #
764
- # This method removes a file system entry +path+.
765
- # +path+ might be a regular file, a directory, or something.
766
- # If +path+ is a directory, remove it recursively.
1459
+ # Argument +path+
1460
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
767
1461
  #
768
- # See also remove_entry_secure.
1462
+ # Optional argument +force+ specifies whether to ignore
1463
+ # raised exceptions of StandardError and its descendants.
1464
+ #
1465
+ # Related: FileUtils.remove_entry_secure.
769
1466
  #
770
1467
  def remove_entry(path, force = false)
771
1468
  Entry_.new(path).postorder_traverse do |ent|
@@ -780,9 +1477,16 @@ module FileUtils
780
1477
  end
781
1478
  module_function :remove_entry
782
1479
 
1480
+ # Removes the file entry given by +path+,
1481
+ # which should be the entry for a regular file or a symbolic link.
1482
+ #
1483
+ # Argument +path+
1484
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
783
1485
  #
784
- # Removes a file +path+.
785
- # This method ignores StandardError if +force+ is true.
1486
+ # Optional argument +force+ specifies whether to ignore
1487
+ # raised exceptions of StandardError and its descendants.
1488
+ #
1489
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
786
1490
  #
787
1491
  def remove_file(path, force = false)
788
1492
  Entry_.new(path).remove_file
@@ -791,20 +1495,32 @@ module FileUtils
791
1495
  end
792
1496
  module_function :remove_file
793
1497
 
1498
+ # Recursively removes the directory entry given by +path+,
1499
+ # which should be the entry for a regular file, a symbolic link,
1500
+ # or a directory.
1501
+ #
1502
+ # Argument +path+
1503
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
794
1504
  #
795
- # Removes a directory +dir+ and its contents recursively.
796
- # This method ignores StandardError if +force+ is true.
1505
+ # Optional argument +force+ specifies whether to ignore
1506
+ # raised exceptions of StandardError and its descendants.
1507
+ #
1508
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
797
1509
  #
798
1510
  def remove_dir(path, force = false)
799
1511
  remove_entry path, force # FIXME?? check if it is a directory
800
1512
  end
801
1513
  module_function :remove_dir
802
1514
 
1515
+ # Returns +true+ if the contents of files +a+ and +b+ are identical,
1516
+ # +false+ otherwise.
1517
+ #
1518
+ # Arguments +a+ and +b+
1519
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
803
1520
  #
804
- # Returns true if the contents of a file +a+ and a file +b+ are identical.
1521
+ # FileUtils.identical? and FileUtils.cmp are aliases for FileUtils.compare_file.
805
1522
  #
806
- # FileUtils.compare_file('somefile', 'somefile') #=> true
807
- # FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
1523
+ # Related: FileUtils.compare_stream.
808
1524
  #
809
1525
  def compare_file(a, b)
810
1526
  return false unless File.size(a) == File.size(b)
@@ -821,8 +1537,13 @@ module FileUtils
821
1537
  module_function :identical?
822
1538
  module_function :cmp
823
1539
 
1540
+ # Returns +true+ if the contents of streams +a+ and +b+ are identical,
1541
+ # +false+ otherwise.
824
1542
  #
825
- # Returns true if the contents of a stream +a+ and +b+ are identical.
1543
+ # Arguments +a+ and +b+
1544
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
1545
+ #
1546
+ # Related: FileUtils.compare_file.
826
1547
  #
827
1548
  def compare_stream(a, b)
828
1549
  bsize = fu_stream_blksize(a, b)
@@ -839,13 +1560,69 @@ module FileUtils
839
1560
  end
840
1561
  module_function :compare_stream
841
1562
 
1563
+ # Copies a file entry.
1564
+ # See {install(1)}[https://man7.org/linux/man-pages/man1/install.1.html].
1565
+ #
1566
+ # Arguments +src+ (a single path or an array of paths)
1567
+ # and +dest+ (a single path)
1568
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments];
1569
+ #
1570
+ # If the entry at +dest+ does not exist, copies from +src+ to +dest+:
1571
+ #
1572
+ # File.read('src0.txt') # => "aaa\n"
1573
+ # File.exist?('dest0.txt') # => false
1574
+ # FileUtils.install('src0.txt', 'dest0.txt')
1575
+ # File.read('dest0.txt') # => "aaa\n"
842
1576
  #
843
- # If +src+ is not same as +dest+, copies it and changes the permission
844
- # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
845
- # This method removes destination before copy.
1577
+ # If +dest+ is a file entry, copies from +src+ to +dest+, overwriting:
846
1578
  #
847
- # FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true
848
- # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true
1579
+ # File.read('src1.txt') # => "aaa\n"
1580
+ # File.read('dest1.txt') # => "bbb\n"
1581
+ # FileUtils.install('src1.txt', 'dest1.txt')
1582
+ # File.read('dest1.txt') # => "aaa\n"
1583
+ #
1584
+ # If +dest+ is a directory entry, copies from +src+ to <tt>dest/src</tt>,
1585
+ # overwriting if necessary:
1586
+ #
1587
+ # File.read('src2.txt') # => "aaa\n"
1588
+ # File.read('dest2/src2.txt') # => "bbb\n"
1589
+ # FileUtils.install('src2.txt', 'dest2')
1590
+ # File.read('dest2/src2.txt') # => "aaa\n"
1591
+ #
1592
+ # If +src+ is an array of paths and +dest+ points to a directory,
1593
+ # copies each path +path+ in +src+ to <tt>dest/path</tt>:
1594
+ #
1595
+ # File.file?('src3.txt') # => true
1596
+ # File.file?('src3.dat') # => true
1597
+ # FileUtils.mkdir('dest3')
1598
+ # FileUtils.install(['src3.txt', 'src3.dat'], 'dest3')
1599
+ # File.file?('dest3/src3.txt') # => true
1600
+ # File.file?('dest3/src3.dat') # => true
1601
+ #
1602
+ # Keyword arguments:
1603
+ #
1604
+ # - <tt>group: <i>group</i></tt> - changes the group if not +nil+,
1605
+ # using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
1606
+ # - <tt>mode: <i>permissions</i></tt> - changes the permissions.
1607
+ # using {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
1608
+ # - <tt>noop: true</tt> - does not copy entries; returns +nil+.
1609
+ # - <tt>owner: <i>owner</i></tt> - changes the owner if not +nil+,
1610
+ # using {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
1611
+ # - <tt>preserve: true</tt> - preserve timestamps
1612
+ # using {File.utime}[https://docs.ruby-lang.org/en/master/File.html#method-c-utime].
1613
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1614
+ #
1615
+ # FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true)
1616
+ # FileUtils.install('src1.txt', 'dest1.txt', noop: true, verbose: true)
1617
+ # FileUtils.install('src2.txt', 'dest2', noop: true, verbose: true)
1618
+ #
1619
+ # Output:
1620
+ #
1621
+ # install -c src0.txt dest0.txt
1622
+ # install -c src1.txt dest1.txt
1623
+ # install -c src2.txt dest2
1624
+ #
1625
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
849
1626
  #
850
1627
  def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
851
1628
  noop: nil, verbose: nil)
@@ -963,37 +1740,78 @@ module FileUtils
963
1740
  end
964
1741
  private_module_function :mode_to_s
965
1742
 
1743
+ # Changes permissions on the entries at the paths given in +list+
1744
+ # (a single path or an array of paths)
1745
+ # to the permissions given by +mode+;
1746
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise:
1747
+ #
1748
+ # - Modifies each entry that is a regular file using
1749
+ # {File.chmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-chmod].
1750
+ # - Modifies each entry that is a symbolic link using
1751
+ # {File.lchmod}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchmod].
1752
+ #
1753
+ # Argument +list+ or its elements
1754
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1755
+ #
1756
+ # Argument +mode+ may be either an integer or a string:
1757
+ #
1758
+ # - \Integer +mode+: represents the permission bits to be set:
1759
+ #
1760
+ # FileUtils.chmod(0755, 'src0.txt')
1761
+ # FileUtils.chmod(0644, ['src0.txt', 'src0.dat'])
1762
+ #
1763
+ # - \String +mode+: represents the permissions to be set:
1764
+ #
1765
+ # The string is of the form <tt>[targets][[operator][perms[,perms]]</tt>, where:
1766
+ #
1767
+ # - +targets+ may be any combination of these letters:
1768
+ #
1769
+ # - <tt>'u'</tt>: permissions apply to the file's owner.
1770
+ # - <tt>'g'</tt>: permissions apply to users in the file's group.
1771
+ # - <tt>'o'</tt>: permissions apply to other users not in the file's group.
1772
+ # - <tt>'a'</tt> (the default): permissions apply to all users.
1773
+ #
1774
+ # - +operator+ may be one of these letters:
1775
+ #
1776
+ # - <tt>'+'</tt>: adds permissions.
1777
+ # - <tt>'-'</tt>: removes permissions.
1778
+ # - <tt>'='</tt>: sets (replaces) permissions.
1779
+ #
1780
+ # - +perms+ (may be repeated, with separating commas)
1781
+ # may be any combination of these letters:
1782
+ #
1783
+ # - <tt>'r'</tt>: Read.
1784
+ # - <tt>'w'</tt>: Write.
1785
+ # - <tt>'x'</tt>: Execute (search, for a directory).
1786
+ # - <tt>'X'</tt>: Search (for a directories only;
1787
+ # must be used with <tt>'+'</tt>)
1788
+ # - <tt>'s'</tt>: Uid or gid.
1789
+ # - <tt>'t'</tt>: Sticky bit.
1790
+ #
1791
+ # Examples:
1792
+ #
1793
+ # FileUtils.chmod('u=wrx,go=rx', 'src1.txt')
1794
+ # FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby')
1795
+ #
1796
+ # Keyword arguments:
1797
+ #
1798
+ # - <tt>noop: true</tt> - does not change permissions; returns +nil+.
1799
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1800
+ #
1801
+ # FileUtils.chmod(0755, 'src0.txt', noop: true, verbose: true)
1802
+ # FileUtils.chmod(0644, ['src0.txt', 'src0.dat'], noop: true, verbose: true)
1803
+ # FileUtils.chmod('u=wrx,go=rx', 'src1.txt', noop: true, verbose: true)
1804
+ # FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby', noop: true, verbose: true)
1805
+ #
1806
+ # Output:
1807
+ #
1808
+ # chmod 755 src0.txt
1809
+ # chmod 644 src0.txt src0.dat
1810
+ # chmod u=wrx,go=rx src1.txt
1811
+ # chmod u=wrx,go=rx /usr/bin/ruby
1812
+ #
1813
+ # Related: FileUtils.chmod_R.
966
1814
  #
967
- # Changes permission bits on the named files (in +list+) to the bit pattern
968
- # represented by +mode+.
969
- #
970
- # +mode+ is the symbolic and absolute mode can be used.
971
- #
972
- # Absolute mode is
973
- # FileUtils.chmod 0755, 'somecommand'
974
- # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
975
- # FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true
976
- #
977
- # Symbolic mode is
978
- # FileUtils.chmod "u=wrx,go=rx", 'somecommand'
979
- # FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
980
- # FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true
981
- #
982
- # "a" :: is user, group, other mask.
983
- # "u" :: is user's mask.
984
- # "g" :: is group's mask.
985
- # "o" :: is other's mask.
986
- # "w" :: is write permission.
987
- # "r" :: is read permission.
988
- # "x" :: is execute permission.
989
- # "X" ::
990
- # is execute permission for directories only, must be used in conjunction with "+"
991
- # "s" :: is uid, gid.
992
- # "t" :: is sticky bit.
993
- # "+" :: is added to a class given the specified mode.
994
- # "-" :: Is removed from a given class given mode.
995
- # "=" :: Is the exact nature of the class will be given a specified mode.
996
-
997
1815
  def chmod(mode, list, noop: nil, verbose: nil)
998
1816
  list = fu_list(list)
999
1817
  fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
@@ -1004,12 +1822,7 @@ module FileUtils
1004
1822
  end
1005
1823
  module_function :chmod
1006
1824
 
1007
- #
1008
- # Changes permission bits on the named files (in +list+)
1009
- # to the bit pattern represented by +mode+.
1010
- #
1011
- # FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
1012
- # FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
1825
+ # Like FileUtils.chmod, but changes permissions recursively.
1013
1826
  #
1014
1827
  def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
1015
1828
  list = fu_list(list)
@@ -1029,15 +1842,68 @@ module FileUtils
1029
1842
  end
1030
1843
  module_function :chmod_R
1031
1844
 
1845
+ # Changes the owner and group on the entries at the paths given in +list+
1846
+ # (a single path or an array of paths)
1847
+ # to the given +user+ and +group+;
1848
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise:
1849
+ #
1850
+ # - Modifies each entry that is a regular file using
1851
+ # {File.chown}[https://docs.ruby-lang.org/en/master/File.html#method-c-chown].
1852
+ # - Modifies each entry that is a symbolic link using
1853
+ # {File.lchown}[https://docs.ruby-lang.org/en/master/File.html#method-c-lchown].
1854
+ #
1855
+ # Argument +list+ or its elements
1856
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1857
+ #
1858
+ # User and group:
1859
+ #
1860
+ # - Argument +user+ may be a user name or a user id;
1861
+ # if +nil+ or +-1+, the user is not changed.
1862
+ # - Argument +group+ may be a group name or a group id;
1863
+ # if +nil+ or +-1+, the group is not changed.
1864
+ # - The user must be a member of the group.
1865
+ #
1866
+ # Examples:
1867
+ #
1868
+ # # One path.
1869
+ # # User and group as string names.
1870
+ # File.stat('src0.txt').uid # => 1004
1871
+ # File.stat('src0.txt').gid # => 1004
1872
+ # FileUtils.chown('user2', 'group1', 'src0.txt')
1873
+ # File.stat('src0.txt').uid # => 1006
1874
+ # File.stat('src0.txt').gid # => 1005
1875
+ #
1876
+ # # User and group as uid and gid.
1877
+ # FileUtils.chown(1004, 1004, 'src0.txt')
1878
+ # File.stat('src0.txt').uid # => 1004
1879
+ # File.stat('src0.txt').gid # => 1004
1032
1880
  #
1033
- # Changes owner and group on the named files (in +list+)
1034
- # to the user +user+ and the group +group+. +user+ and +group+
1035
- # may be an ID (Integer/String) or a name (String).
1036
- # If +user+ or +group+ is nil, this method does not change
1037
- # the attribute.
1881
+ # # Array of paths.
1882
+ # FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'])
1038
1883
  #
1039
- # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
1040
- # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true
1884
+ # # Directory (not recursive).
1885
+ # FileUtils.chown('user2', 'group1', '.')
1886
+ #
1887
+ # Keyword arguments:
1888
+ #
1889
+ # - <tt>noop: true</tt> - does not change permissions; returns +nil+.
1890
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1891
+ #
1892
+ # FileUtils.chown('user2', 'group1', 'src0.txt', noop: true, verbose: true)
1893
+ # FileUtils.chown(1004, 1004, 'src0.txt', noop: true, verbose: true)
1894
+ # FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'], noop: true, verbose: true)
1895
+ # FileUtils.chown('user2', 'group1', path, noop: true, verbose: true)
1896
+ # FileUtils.chown('user2', 'group1', '.', noop: true, verbose: true)
1897
+ #
1898
+ # Output:
1899
+ #
1900
+ # chown user2:group1 src0.txt
1901
+ # chown 1004:1004 src0.txt
1902
+ # chown 1006:1005 src0.txt src0.dat
1903
+ # chown user2:group1 src0.txt
1904
+ # chown user2:group1 .
1905
+ #
1906
+ # Related: FileUtils.chown_R.
1041
1907
  #
1042
1908
  def chown(user, group, list, noop: nil, verbose: nil)
1043
1909
  list = fu_list(list)
@@ -1053,15 +1919,7 @@ module FileUtils
1053
1919
  end
1054
1920
  module_function :chown
1055
1921
 
1056
- #
1057
- # Changes owner and group on the named files (in +list+)
1058
- # to the user +user+ and the group +group+ recursively.
1059
- # +user+ and +group+ may be an ID (Integer/String) or
1060
- # a name (String). If +user+ or +group+ is nil, this
1061
- # method does not change the attribute.
1062
- #
1063
- # FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
1064
- # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true
1922
+ # Like FileUtils.chown, but changes owner and group recursively.
1065
1923
  #
1066
1924
  def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
1067
1925
  list = fu_list(list)
@@ -1112,12 +1970,50 @@ module FileUtils
1112
1970
  end
1113
1971
  private_module_function :fu_get_gid
1114
1972
 
1973
+ # Updates modification times (mtime) and access times (atime)
1974
+ # of the entries given by the paths in +list+
1975
+ # (a single path or an array of paths);
1976
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise.
1115
1977
  #
1116
- # Updates modification time (mtime) and access time (atime) of file(s) in
1117
- # +list+. Files are created if they don't exist.
1978
+ # By default, creates an empty file for any path to a non-existent entry;
1979
+ # use keyword argument +nocreate+ to raise an exception instead.
1118
1980
  #
1119
- # FileUtils.touch 'timestamp'
1120
- # FileUtils.touch Dir.glob('*.c'); system 'make'
1981
+ # Argument +list+ or its elements
1982
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1983
+ #
1984
+ # Examples:
1985
+ #
1986
+ # # Single path.
1987
+ # f = File.new('src0.txt') # Existing file.
1988
+ # f.atime # => 2022-06-10 11:11:21.200277 -0700
1989
+ # f.mtime # => 2022-06-10 11:11:21.200277 -0700
1990
+ # FileUtils.touch('src0.txt')
1991
+ # f = File.new('src0.txt')
1992
+ # f.atime # => 2022-06-11 08:28:09.8185343 -0700
1993
+ # f.mtime # => 2022-06-11 08:28:09.8185343 -0700
1994
+ #
1995
+ # # Array of paths.
1996
+ # FileUtils.touch(['src0.txt', 'src0.dat'])
1997
+ #
1998
+ # Keyword arguments:
1999
+ #
2000
+ # - <tt>mtime: <i>time</i></tt> - sets the entry's mtime to the given time,
2001
+ # instead of the current time.
2002
+ # - <tt>nocreate: true</tt> - raises an exception if the entry does not exist.
2003
+ # - <tt>noop: true</tt> - does not touch entries; returns +nil+.
2004
+ # - <tt>verbose: true</tt> - prints an equivalent command:
2005
+ #
2006
+ # FileUtils.touch('src0.txt', noop: true, verbose: true)
2007
+ # FileUtils.touch(['src0.txt', 'src0.dat'], noop: true, verbose: true)
2008
+ # FileUtils.touch(path, noop: true, verbose: true)
2009
+ #
2010
+ # Output:
2011
+ #
2012
+ # touch src0.txt
2013
+ # touch src0.txt src0.dat
2014
+ # touch src0.txt
2015
+ #
2016
+ # Related: FileUtils.uptodate?.
1121
2017
  #
1122
2018
  def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
1123
2019
  list = fu_list(list)
@@ -1479,13 +2375,21 @@ module FileUtils
1479
2375
 
1480
2376
  def postorder_traverse
1481
2377
  if directory?
1482
- entries().each do |ent|
2378
+ begin
2379
+ children = entries()
2380
+ rescue Errno::EACCES
2381
+ # Failed to get the list of children.
2382
+ # Assuming there is no children, try to process the parent directory.
2383
+ yield self
2384
+ return
2385
+ end
2386
+
2387
+ children.each do |ent|
1483
2388
  ent.postorder_traverse do |e|
1484
2389
  yield e
1485
2390
  end
1486
2391
  end
1487
2392
  end
1488
- ensure
1489
2393
  yield self
1490
2394
  end
1491
2395
 
@@ -1579,15 +2483,15 @@ module FileUtils
1579
2483
  end
1580
2484
  private_module_function :fu_each_src_dest
1581
2485
 
1582
- def fu_each_src_dest0(src, dest) #:nodoc:
2486
+ def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
1583
2487
  if tmp = Array.try_convert(src)
1584
2488
  tmp.each do |s|
1585
2489
  s = File.path(s)
1586
- yield s, File.join(dest, File.basename(s))
2490
+ yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
1587
2491
  end
1588
2492
  else
1589
2493
  src = File.path(src)
1590
- if File.directory?(dest)
2494
+ if target_directory and File.directory?(dest)
1591
2495
  yield src, File.join(dest, File.basename(src))
1592
2496
  else
1593
2497
  yield src, File.path(dest)
@@ -1611,6 +2515,56 @@ module FileUtils
1611
2515
  end
1612
2516
  private_module_function :fu_output_message
1613
2517
 
2518
+ def fu_split_path(path)
2519
+ path = File.path(path)
2520
+ list = []
2521
+ until (parent, base = File.split(path); parent == path or parent == ".")
2522
+ list << base
2523
+ path = parent
2524
+ end
2525
+ list << path
2526
+ list.reverse!
2527
+ end
2528
+ private_module_function :fu_split_path
2529
+
2530
+ def fu_relative_components_from(target, base) #:nodoc:
2531
+ i = 0
2532
+ while target[i]&.== base[i]
2533
+ i += 1
2534
+ end
2535
+ Array.new(base.size-i, '..').concat(target[i..-1])
2536
+ end
2537
+ private_module_function :fu_relative_components_from
2538
+
2539
+ def fu_clean_components(*comp)
2540
+ comp.shift while comp.first == "."
2541
+ return comp if comp.empty?
2542
+ clean = [comp.shift]
2543
+ path = File.join(*clean, "") # ending with File::SEPARATOR
2544
+ while c = comp.shift
2545
+ if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path))
2546
+ clean.pop
2547
+ path.chomp!(%r((?<=\A|/)[^/]+/\z), "")
2548
+ else
2549
+ clean << c
2550
+ path << c << "/"
2551
+ end
2552
+ end
2553
+ clean
2554
+ end
2555
+ private_module_function :fu_clean_components
2556
+
2557
+ if fu_windows?
2558
+ def fu_starting_path?(path)
2559
+ path&.start_with?(%r(\w:|/))
2560
+ end
2561
+ else
2562
+ def fu_starting_path?(path)
2563
+ path&.start_with?("/")
2564
+ end
2565
+ end
2566
+ private_module_function :fu_starting_path?
2567
+
1614
2568
  # This hash table holds command options.
1615
2569
  OPT_TABLE = {} #:nodoc: internal use only
1616
2570
  (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
@@ -1620,50 +2574,49 @@ module FileUtils
1620
2574
 
1621
2575
  public
1622
2576
 
2577
+ # Returns an array of the string names of \FileUtils methods
2578
+ # that accept one or more keyword arguments:
1623
2579
  #
1624
- # Returns an Array of names of high-level methods that accept any keyword
1625
- # arguments.
1626
- #
1627
- # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
2580
+ # FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"]
1628
2581
  #
1629
2582
  def self.commands
1630
2583
  OPT_TABLE.keys
1631
2584
  end
1632
2585
 
2586
+ # Returns an array of the string keyword names:
1633
2587
  #
1634
- # Returns an Array of option names.
1635
- #
1636
- # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
2588
+ # FileUtils.options.take(3) # => ["noop", "verbose", "force"]
1637
2589
  #
1638
2590
  def self.options
1639
2591
  OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1640
2592
  end
1641
2593
 
2594
+ # Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise;
2595
+ # the arguments may be strings or symbols:
1642
2596
  #
1643
- # Returns true if the method +mid+ have an option +opt+.
1644
- #
1645
- # p FileUtils.have_option?(:cp, :noop) #=> true
1646
- # p FileUtils.have_option?(:rm, :force) #=> true
1647
- # p FileUtils.have_option?(:rm, :preserve) #=> false
2597
+ # FileUtils.have_option?(:chmod, :noop) # => true
2598
+ # FileUtils.have_option?('chmod', 'secure') # => false
1648
2599
  #
1649
2600
  def self.have_option?(mid, opt)
1650
2601
  li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1651
2602
  li.include?(opt)
1652
2603
  end
1653
2604
 
2605
+ # Returns an array of the string keyword name for method +mid+;
2606
+ # the argument may be a string or a symbol:
1654
2607
  #
1655
- # Returns an Array of option names of the method +mid+.
1656
- #
1657
- # p FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]
2608
+ # FileUtils.options_of(:rm) # => ["force", "noop", "verbose"]
2609
+ # FileUtils.options_of('mv') # => ["force", "noop", "verbose", "secure"]
1658
2610
  #
1659
2611
  def self.options_of(mid)
1660
2612
  OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1661
2613
  end
1662
2614
 
2615
+ # Returns an array of the string method names of the methods
2616
+ # that accept the given keyword option +opt+;
2617
+ # the argument must be a symbol:
1663
2618
  #
1664
- # Returns an Array of methods names which have the option +opt+.
1665
- #
1666
- # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
2619
+ # FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"]
1667
2620
  #
1668
2621
  def self.collect_method(opt)
1669
2622
  OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }