fileutils 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +1 -3
  4. data/fileutils.gemspec +1 -1
  5. data/lib/fileutils.rb +1332 -399
  6. metadata +4 -4
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.5.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:
227
+ #
228
+ # - <tt>verbose: true</tt> - prints an equivalent command:
123
229
  #
124
- # Changes the current directory to the directory +dir+.
230
+ # FileUtils.cd('..')
231
+ # FileUtils.cd('fileutils')
125
232
  #
126
- # If this method is called with block, resumes to the previous
127
- # working directory after the block execution has finished.
233
+ # Output:
128
234
  #
129
- # FileUtils.cd('/') # change directory
235
+ # cd ..
236
+ # cd fileutils
130
237
  #
131
- # FileUtils.cd('/', verbose: true) # change directory and report it
238
+ # FileUtils.chdir is an alias for FileUtils.cd.
132
239
  #
133
- # FileUtils.cd('/') do # change directory
134
- # # ... # do something
135
- # end # return to original directory
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.
151
257
  #
152
- # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
153
- # system 'make hello.o'
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
263
+ #
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:
306
+ #
307
+ # FileUtils.mkdir(%w[tmp0 tmp1], verbose: true)
308
+ # FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true)
174
309
  #
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
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:
349
+ #
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:
194
354
  #
195
- # FileUtils.mkdir_p '/usr/local/lib/ruby'
355
+ # FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true)
356
+ # FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true)
196
357
  #
197
- # causes to make following directories, if they do not exist.
358
+ # Output:
198
359
  #
199
- # * /usr
200
- # * /usr/local
201
- # * /usr/local/lib
202
- # * /usr/local/lib/ruby
360
+ # mkdir -p tmp0 tmp1
361
+ # mkdir -p -m 700 tmp2 tmp3
203
362
  #
204
- # You can pass several directories at a time in a list.
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)
@@ -211,21 +374,11 @@ module FileUtils
211
374
  list.each do |item|
212
375
  path = remove_trailing_slash(item)
213
376
 
214
- # optimize for the most common case
215
- begin
216
- fu_mkdir path, mode
217
- next
218
- rescue SystemCallError
219
- next if File.directory?(path)
220
- end
221
-
222
377
  stack = []
223
- until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
378
+ until File.directory?(path) || File.dirname(path) == path
224
379
  stack.push path
225
380
  path = File.dirname(path)
226
- break if File.directory?(path)
227
381
  end
228
- stack.pop if path == stack.last # root directory should exist
229
382
  stack.reverse_each do |dir|
230
383
  begin
231
384
  fu_mkdir dir, mode
@@ -256,12 +409,39 @@ module FileUtils
256
409
  private_module_function :fu_mkdir
257
410
 
258
411
  #
259
- # 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"]
260
425
  #
261
- # FileUtils.rmdir 'somedir'
262
- # FileUtils.rmdir %w(somedir anydir otherdir)
263
- # # Does not really remove directory; outputs message.
264
- # FileUtils.rmdir 'somedir', verbose: true, noop: true
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:
432
+ #
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].
265
445
  #
266
446
  def rmdir(list, parents: nil, noop: nil, verbose: nil)
267
447
  list = fu_list(list)
@@ -282,26 +462,62 @@ module FileUtils
282
462
  end
283
463
  module_function :rmdir
284
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:
285
474
  #
286
- # :call-seq:
287
- # FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
288
- # FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
289
- # FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
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"]
290
479
  #
291
- # In the first form, creates a hard link +link+ which points to +target+.
292
- # If +link+ already exists, raises Errno::EEXIST.
293
- # But if the +force+ option is set, overwrites +link+.
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:
294
483
  #
295
- # FileUtils.ln 'gcc', 'cc', verbose: true
296
- # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
484
+ # Dir.children('tmp2') # => ["t.dat"]
485
+ # Dir.children('tmp3') # => []
486
+ # FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0
487
+ # Dir.children('tmp3') # => ["t.dat"]
297
488
  #
298
- # In the second form, creates a link +dir/target+ pointing to +target+.
299
- # In the third form, creates several hard links in the directory +dir+,
300
- # pointing to each item in +targets+.
301
- # If +dir+ is not a directory, raises Errno::ENOTDIR.
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+:
302
494
  #
303
- # FileUtils.cd '/sbin'
304
- # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
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"]
498
+ #
499
+ # Keyword arguments:
500
+ #
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).
305
521
  #
306
522
  def ln(src, dest, force: nil, noop: nil, verbose: nil)
307
523
  fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -316,28 +532,103 @@ module FileUtils
316
532
  alias link ln
317
533
  module_function :link
318
534
 
319
- #
320
- # Hard link +src+ to +dest+. If +src+ is a directory, this method links
321
- # all its contents recursively. If +dest+ is a directory, links
322
- # +src+ to +dest/src+.
323
- #
324
- # +src+ can be a list of files.
325
- #
326
- # If +dereference_root+ is true, this method dereference tree root.
327
- #
328
- # If +remove_destination+ is true, this method removes each destination file before copy.
329
- #
330
- # FileUtils.rm_r site_ruby + '/mylib', force: true
331
- # FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
332
- #
333
- # # Examples of linking several files to target directory.
334
- # FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
335
- # FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true
336
- #
337
- # # If you want to link all contents of a directory instead of the
338
- # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
339
- # # use the following code.
340
- # 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].
341
632
  #
342
633
  def cp_lr(src, dest, noop: nil, verbose: nil,
343
634
  dereference_root: true, remove_destination: false)
@@ -349,27 +640,81 @@ module FileUtils
349
640
  end
350
641
  module_function :cp_lr
351
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:
352
650
  #
353
- # :call-seq:
354
- # FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
355
- # FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
356
- # FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
651
+ # - When +dest+ is the path to a non-existent file,
652
+ # creates a symbolic link at +dest+ pointing to +src+:
357
653
  #
358
- # In the first form, creates a symbolic link +link+ which points to +target+.
359
- # If +link+ already exists, raises Errno::EEXIST.
360
- # But if the <tt>force</tt> option is set, overwrites +link+.
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
361
658
  #
362
- # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
363
- # FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
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):
364
663
  #
365
- # In the second form, creates a link +dir/target+ pointing to +target+.
366
- # In the third form, creates several symbolic links in the directory +dir+,
367
- # pointing to each item in +targets+.
368
- # If +dir+ is not a directory, raises Errno::ENOTDIR.
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
369
668
  #
370
- # FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
669
+ # FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST.
371
670
  #
372
- def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
671
+ # If +dest+ is the path to a directory,
672
+ # creates a symbolic link at <tt>dest/src</tt> pointing to +src+:
673
+ #
674
+ # FileUtils.touch('src2.txt')
675
+ # FileUtils.mkdir('destdir2')
676
+ # FileUtils.ln_s('src2.txt', 'destdir2')
677
+ # File.symlink?('destdir2/src2.txt') # => true
678
+ #
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
373
718
  fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
374
719
  return if noop
375
720
  fu_each_src_dest0(src, dest) do |s,d|
@@ -382,29 +727,95 @@ module FileUtils
382
727
  alias symlink ln_s
383
728
  module_function :symlink
384
729
 
385
- #
386
- # :call-seq:
387
- # FileUtils.ln_sf(*args)
388
- #
389
- # Same as
390
- #
391
- # FileUtils.ln_s(*args, force: true)
730
+ # Like FileUtils.ln_s, but always with keyword argument <tt>force: true</tt> given.
392
731
  #
393
732
  def ln_sf(src, dest, noop: nil, verbose: nil)
394
733
  ln_s src, dest, force: true, noop: noop, verbose: verbose
395
734
  end
396
735
  module_function :ln_sf
397
736
 
737
+ # Like FileUtils.ln_s, but create links relative to +dest+.
398
738
  #
399
- # Hard links a file system entry +src+ to +dest+.
400
- # 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
401
791
  #
402
- # Both of +src+ and +dest+ must be a path name.
403
- # +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+:
404
794
  #
405
- # 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
406
809
  #
407
- # 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).
408
819
  #
409
820
  def link_entry(src, dest, dereference_root = false, remove_destination = false)
410
821
  Entry_.new(src, nil, dereference_root).traverse do |ent|
@@ -415,16 +826,59 @@ module FileUtils
415
826
  end
416
827
  module_function :link_entry
417
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].
418
834
  #
419
- # Copies a file content +src+ to +dest+. If +dest+ is a directory,
420
- # 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+:
421
837
  #
422
- # 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
423
842
  #
424
- # FileUtils.cp 'eval.c', 'eval.c.org'
425
- # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
426
- # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true
427
- # 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].
428
882
  #
429
883
  def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
430
884
  fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -438,30 +892,105 @@ module FileUtils
438
892
  alias copy cp
439
893
  module_function :copy
440
894
 
441
- #
442
- # Copies +src+ to +dest+. If +src+ is a directory, this method copies
443
- # all its contents recursively. If +dest+ is a directory, copies
444
- # +src+ to +dest/src+.
445
- #
446
- # +src+ can be a list of files.
447
- #
448
- # If +dereference_root+ is true, this method dereference tree root.
449
- #
450
- # If +remove_destination+ is true, this method removes each destination file before copy.
451
- #
452
- # # Installing Ruby library "mylib" under the site_ruby
453
- # FileUtils.rm_r site_ruby + '/mylib', force: true
454
- # FileUtils.cp_r 'lib/', site_ruby + '/mylib'
455
- #
456
- # # Examples of copying several files to target directory.
457
- # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
458
- # FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true
459
- #
460
- # # If you want to copy all contents of a directory instead of the
461
- # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
462
- # # use following code.
463
- # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
464
- # # 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].
465
994
  #
466
995
  def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
467
996
  dereference_root: true, remove_destination: nil)
@@ -473,21 +1002,50 @@ module FileUtils
473
1002
  end
474
1003
  module_function :cp_r
475
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
476
1016
  #
477
- # Copies a file system entry +src+ to +dest+.
478
- # If +src+ is a directory, this method copies its contents recursively.
479
- # This method preserves file types, c.f. symlink, directory...
480
- # (FIFO, device files and etc. are not supported yet)
1017
+ # If +src+ is a directory, recursively copies +src+ to +dest+:
481
1018
  #
482
- # Both of +src+ and +dest+ must be a path name.
483
- # +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
484
1036
  #
485
- # If +preserve+ is true, this method preserves owner, group, and
486
- # 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.
487
1040
  #
488
- # If +dereference_root+ is true, this method dereference tree root.
1041
+ # Keyword arguments:
489
1042
  #
490
- # 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].
491
1049
  #
492
1050
  def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
493
1051
  if dereference_root
@@ -505,9 +1063,25 @@ module FileUtils
505
1063
  end
506
1064
  module_function :copy_entry
507
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
508
1076
  #
509
- # Copies file contents of +src+ to +dest+.
510
- # 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].
511
1085
  #
512
1086
  def copy_file(src, dest, preserve = false, dereference = true)
513
1087
  ent = Entry_.new(src, nil, dereference)
@@ -516,25 +1090,81 @@ module FileUtils
516
1090
  end
517
1091
  module_function :copy_file
518
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].
519
1095
  #
520
- # Copies stream +src+ to +dest+.
521
- # +src+ must respond to #read(n) and
522
- # +dest+ must respond to #write(str).
1096
+ # Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
523
1097
  #
524
1098
  def copy_stream(src, dest)
525
1099
  IO.copy_stream(src, dest)
526
1100
  end
527
1101
  module_function :copy_stream
528
1102
 
529
- #
530
- # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
531
- # disk partition, the file is copied then the original file is removed.
532
- #
533
- # FileUtils.mv 'badname.rb', 'goodname.rb'
534
- # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error
535
- #
536
- # FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
537
- # 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.
538
1168
  #
539
1169
  def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
540
1170
  fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
@@ -568,13 +1198,34 @@ module FileUtils
568
1198
  alias move mv
569
1199
  module_function :move
570
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"]
571
1212
  #
572
- # Remove file(s) specified in +list+. This method cannot remove directories.
573
- # All StandardErrors are ignored when the :force option is set.
1213
+ # Keyword arguments:
574
1214
  #
575
- # FileUtils.rm %w( junk.txt dust.txt )
576
- # FileUtils.rm Dir.glob('*.so')
577
- # 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].
578
1229
  #
579
1230
  def rm(list, force: nil, noop: nil, verbose: nil)
580
1231
  list = fu_list(list)
@@ -590,10 +1241,18 @@ module FileUtils
590
1241
  alias remove rm
591
1242
  module_function :remove
592
1243
 
1244
+ # Equivalent to:
593
1245
  #
594
- # Equivalent to
1246
+ # FileUtils.rm(list, force: true, **kwargs)
595
1247
  #
596
- # FileUtils.rm(list, force: true)
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.
1252
+ #
1253
+ # FileUtils.safe_unlink is an alias for FileUtils.rm_f.
1254
+ #
1255
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
597
1256
  #
598
1257
  def rm_f(list, noop: nil, verbose: nil)
599
1258
  rm list, force: true, noop: noop, verbose: verbose
@@ -603,24 +1262,55 @@ module FileUtils
603
1262
  alias safe_unlink rm_f
604
1263
  module_function :safe_unlink
605
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].
606
1271
  #
607
- # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
608
- # removes its all contents recursively. This method ignores
609
- # StandardError when :force option is set.
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].
610
1275
  #
611
- # FileUtils.rm_r Dir.glob('/tmp/*')
612
- # FileUtils.rm_r 'some_dir', force: true
1276
+ # For each file path, removes the file at that path:
613
1277
  #
614
- # WARNING: This method causes local vulnerability
615
- # if one of parent directories or removing directory tree are world
616
- # writable (including /tmp, whose permission is 1777), and the current
617
- # process has strong privilege such as Unix super user (root), and the
618
- # system has symbolic link. For secure removing, read the documentation
619
- # of remove_entry_secure carefully, and set :secure option to true.
620
- # Default is <tt>secure: false</tt>.
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
621
1282
  #
622
- # NOTE: This method calls remove_entry_secure if :secure option is set.
623
- # See also remove_entry_secure.
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:
1304
+ #
1305
+ # FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true)
1306
+ # FileUtils.rm_r('src1', noop: true, verbose: true)
1307
+ #
1308
+ # Output:
1309
+ #
1310
+ # rm -r src0.dat src0.txt
1311
+ # rm -r src1
1312
+ #
1313
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
624
1314
  #
625
1315
  def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
626
1316
  list = fu_list(list)
@@ -636,13 +1326,22 @@ module FileUtils
636
1326
  end
637
1327
  module_function :rm_r
638
1328
 
1329
+ # Equivalent to:
1330
+ #
1331
+ # FileUtils.rm_r(list, force: true, **kwargs)
639
1332
  #
640
- # Equivalent to
1333
+ # Argument +list+ or its elements
1334
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
641
1335
  #
642
- # FileUtils.rm_r(list, force: true)
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].
643
1339
  #
644
- # WARNING: This method causes local vulnerability.
645
- # Read the documentation of rm_r first.
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].
646
1345
  #
647
1346
  def rm_rf(list, noop: nil, verbose: nil, secure: nil)
648
1347
  rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
@@ -652,37 +1351,20 @@ module FileUtils
652
1351
  alias rmtree rm_rf
653
1352
  module_function :rmtree
654
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.
655
1357
  #
656
- # This method removes a file system entry +path+. +path+ shall be a
657
- # regular file, a directory, or something. If +path+ is a directory,
658
- # remove it recursively. This method is required to avoid TOCTTOU
659
- # (time-of-check-to-time-of-use) local security vulnerability of rm_r.
660
- # #rm_r causes security hole when:
661
- #
662
- # * Parent directory is world writable (including /tmp).
663
- # * Removing directory tree includes world writable directory.
664
- # * The system has symbolic link.
665
- #
666
- # To avoid this security hole, this method applies special preprocess.
667
- # If +path+ is a directory, this method chown(2) and chmod(2) all
668
- # removing directories. This requires the current process is the
669
- # owner of the removing whole directory tree, or is the super user (root).
670
- #
671
- # WARNING: You must ensure that *ALL* parent directories cannot be
672
- # moved by other untrusted users. For example, parent directories
673
- # should not be owned by untrusted users, and should not be world
674
- # writable except when the sticky bit set.
1358
+ # Argument +path+
1359
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
675
1360
  #
676
- # WARNING: Only the owner of the removing directory tree, or Unix super
677
- # user (root) should invoke this method. Otherwise this method does not
678
- # work.
1361
+ # Avoids a local vulnerability that can exist in certain circumstances;
1362
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
679
1363
  #
680
- # For details of this security vulnerability, see Perl's case:
1364
+ # Optional argument +force+ specifies whether to ignore
1365
+ # raised exceptions of StandardError and its descendants.
681
1366
  #
682
- # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
683
- # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
684
- #
685
- # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
1367
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
686
1368
  #
687
1369
  def remove_entry_secure(path, force = false)
688
1370
  unless fu_have_symlink?
@@ -770,12 +1452,17 @@ module FileUtils
770
1452
  end
771
1453
  private_module_function :fu_stat_identical_entry?
772
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.
1458
+ #
1459
+ # Argument +path+
1460
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
773
1461
  #
774
- # This method removes a file system entry +path+.
775
- # +path+ might be a regular file, a directory, or something.
776
- # If +path+ is a directory, remove it recursively.
1462
+ # Optional argument +force+ specifies whether to ignore
1463
+ # raised exceptions of StandardError and its descendants.
777
1464
  #
778
- # See also remove_entry_secure.
1465
+ # Related: FileUtils.remove_entry_secure.
779
1466
  #
780
1467
  def remove_entry(path, force = false)
781
1468
  Entry_.new(path).postorder_traverse do |ent|
@@ -790,9 +1477,16 @@ module FileUtils
790
1477
  end
791
1478
  module_function :remove_entry
792
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].
1485
+ #
1486
+ # Optional argument +force+ specifies whether to ignore
1487
+ # raised exceptions of StandardError and its descendants.
793
1488
  #
794
- # Removes a file +path+.
795
- # This method ignores StandardError if +force+ is true.
1489
+ # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
796
1490
  #
797
1491
  def remove_file(path, force = false)
798
1492
  Entry_.new(path).remove_file
@@ -801,20 +1495,32 @@ module FileUtils
801
1495
  end
802
1496
  module_function :remove_file
803
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.
804
1501
  #
805
- # Removes a directory +dir+ and its contents recursively.
806
- # This method ignores StandardError if +force+ is true.
1502
+ # Argument +path+
1503
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
1504
+ #
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].
807
1509
  #
808
1510
  def remove_dir(path, force = false)
809
1511
  remove_entry path, force # FIXME?? check if it is a directory
810
1512
  end
811
1513
  module_function :remove_dir
812
1514
 
1515
+ # Returns +true+ if the contents of files +a+ and +b+ are identical,
1516
+ # +false+ otherwise.
813
1517
  #
814
- # Returns true if the contents of a file +a+ and a file +b+ are identical.
1518
+ # Arguments +a+ and +b+
1519
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
815
1520
  #
816
- # FileUtils.compare_file('somefile', 'somefile') #=> true
817
- # FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
1521
+ # FileUtils.identical? and FileUtils.cmp are aliases for FileUtils.compare_file.
1522
+ #
1523
+ # Related: FileUtils.compare_stream.
818
1524
  #
819
1525
  def compare_file(a, b)
820
1526
  return false unless File.size(a) == File.size(b)
@@ -831,19 +1537,19 @@ module FileUtils
831
1537
  module_function :identical?
832
1538
  module_function :cmp
833
1539
 
1540
+ # Returns +true+ if the contents of streams +a+ and +b+ are identical,
1541
+ # +false+ otherwise.
1542
+ #
1543
+ # Arguments +a+ and +b+
1544
+ # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
834
1545
  #
835
- # Returns true if the contents of a stream +a+ and +b+ are identical.
1546
+ # Related: FileUtils.compare_file.
836
1547
  #
837
1548
  def compare_stream(a, b)
838
1549
  bsize = fu_stream_blksize(a, b)
839
1550
 
840
- if RUBY_VERSION > "2.4"
841
- sa = String.new(capacity: bsize)
842
- sb = String.new(capacity: bsize)
843
- else
844
- sa = String.new
845
- sb = String.new
846
- end
1551
+ sa = String.new(capacity: bsize)
1552
+ sb = String.new(capacity: bsize)
847
1553
 
848
1554
  begin
849
1555
  a.read(bsize, sa)
@@ -854,13 +1560,69 @@ module FileUtils
854
1560
  end
855
1561
  module_function :compare_stream
856
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+:
857
1571
  #
858
- # If +src+ is not same as +dest+, copies it and changes the permission
859
- # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
860
- # This method removes destination before copy.
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"
861
1576
  #
862
- # FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true
863
- # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true
1577
+ # If +dest+ is a file entry, copies from +src+ to +dest+, overwriting:
1578
+ #
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].
864
1626
  #
865
1627
  def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
866
1628
  noop: nil, verbose: nil)
@@ -978,37 +1740,78 @@ module FileUtils
978
1740
  end
979
1741
  private_module_function :mode_to_s
980
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.
981
1814
  #
982
- # Changes permission bits on the named files (in +list+) to the bit pattern
983
- # represented by +mode+.
984
- #
985
- # +mode+ is the symbolic and absolute mode can be used.
986
- #
987
- # Absolute mode is
988
- # FileUtils.chmod 0755, 'somecommand'
989
- # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
990
- # FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true
991
- #
992
- # Symbolic mode is
993
- # FileUtils.chmod "u=wrx,go=rx", 'somecommand'
994
- # FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
995
- # FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true
996
- #
997
- # "a" :: is user, group, other mask.
998
- # "u" :: is user's mask.
999
- # "g" :: is group's mask.
1000
- # "o" :: is other's mask.
1001
- # "w" :: is write permission.
1002
- # "r" :: is read permission.
1003
- # "x" :: is execute permission.
1004
- # "X" ::
1005
- # is execute permission for directories only, must be used in conjunction with "+"
1006
- # "s" :: is uid, gid.
1007
- # "t" :: is sticky bit.
1008
- # "+" :: is added to a class given the specified mode.
1009
- # "-" :: Is removed from a given class given mode.
1010
- # "=" :: Is the exact nature of the class will be given a specified mode.
1011
-
1012
1815
  def chmod(mode, list, noop: nil, verbose: nil)
1013
1816
  list = fu_list(list)
1014
1817
  fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
@@ -1019,12 +1822,7 @@ module FileUtils
1019
1822
  end
1020
1823
  module_function :chmod
1021
1824
 
1022
- #
1023
- # Changes permission bits on the named files (in +list+)
1024
- # to the bit pattern represented by +mode+.
1025
- #
1026
- # FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
1027
- # FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
1825
+ # Like FileUtils.chmod, but changes permissions recursively.
1028
1826
  #
1029
1827
  def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
1030
1828
  list = fu_list(list)
@@ -1044,15 +1842,68 @@ module FileUtils
1044
1842
  end
1045
1843
  module_function :chmod_R
1046
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
1880
+ #
1881
+ # # Array of paths.
1882
+ # FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'])
1047
1883
  #
1048
- # Changes owner and group on the named files (in +list+)
1049
- # to the user +user+ and the group +group+. +user+ and +group+
1050
- # may be an ID (Integer/String) or a name (String).
1051
- # If +user+ or +group+ is nil, this method does not change
1052
- # the attribute.
1884
+ # # Directory (not recursive).
1885
+ # FileUtils.chown('user2', 'group1', '.')
1053
1886
  #
1054
- # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
1055
- # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true
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.
1056
1907
  #
1057
1908
  def chown(user, group, list, noop: nil, verbose: nil)
1058
1909
  list = fu_list(list)
@@ -1068,15 +1919,7 @@ module FileUtils
1068
1919
  end
1069
1920
  module_function :chown
1070
1921
 
1071
- #
1072
- # Changes owner and group on the named files (in +list+)
1073
- # to the user +user+ and the group +group+ recursively.
1074
- # +user+ and +group+ may be an ID (Integer/String) or
1075
- # a name (String). If +user+ or +group+ is nil, this
1076
- # method does not change the attribute.
1077
- #
1078
- # FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
1079
- # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true
1922
+ # Like FileUtils.chown, but changes owner and group recursively.
1080
1923
  #
1081
1924
  def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
1082
1925
  list = fu_list(list)
@@ -1127,12 +1970,50 @@ module FileUtils
1127
1970
  end
1128
1971
  private_module_function :fu_get_gid
1129
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.
1977
+ #
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.
1130
1980
  #
1131
- # Updates modification time (mtime) and access time (atime) of file(s) in
1132
- # +list+. Files are created if they don't exist.
1981
+ # Argument +list+ or its elements
1982
+ # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
1133
1983
  #
1134
- # FileUtils.touch 'timestamp'
1135
- # FileUtils.touch Dir.glob('*.c'); system 'make'
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?.
1136
2017
  #
1137
2018
  def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
1138
2019
  list = fu_list(list)
@@ -1292,12 +2173,7 @@ module FileUtils
1292
2173
  opts = {}
1293
2174
  opts[:encoding] = fu_windows? ? ::Encoding::UTF_8 : path.encoding
1294
2175
 
1295
- files = if Dir.respond_to?(:children)
1296
- Dir.children(path, **opts)
1297
- else
1298
- Dir.entries(path(), **opts)
1299
- .reject {|n| n == '.' or n == '..' }
1300
- end
2176
+ files = Dir.children(path, **opts)
1301
2177
 
1302
2178
  untaint = RUBY_VERSION < '2.7'
1303
2179
  files.map {|n| Entry_.new(prefix(), join(rel(), untaint ? n.untaint : n)) }
@@ -1499,13 +2375,21 @@ module FileUtils
1499
2375
 
1500
2376
  def postorder_traverse
1501
2377
  if directory?
1502
- 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|
1503
2388
  ent.postorder_traverse do |e|
1504
2389
  yield e
1505
2390
  end
1506
2391
  end
1507
2392
  end
1508
- ensure
1509
2393
  yield self
1510
2394
  end
1511
2395
 
@@ -1599,15 +2483,15 @@ module FileUtils
1599
2483
  end
1600
2484
  private_module_function :fu_each_src_dest
1601
2485
 
1602
- def fu_each_src_dest0(src, dest) #:nodoc:
2486
+ def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
1603
2487
  if tmp = Array.try_convert(src)
1604
2488
  tmp.each do |s|
1605
2489
  s = File.path(s)
1606
- yield s, File.join(dest, File.basename(s))
2490
+ yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
1607
2491
  end
1608
2492
  else
1609
2493
  src = File.path(src)
1610
- if File.directory?(dest)
2494
+ if target_directory and File.directory?(dest)
1611
2495
  yield src, File.join(dest, File.basename(src))
1612
2496
  else
1613
2497
  yield src, File.path(dest)
@@ -1631,6 +2515,56 @@ module FileUtils
1631
2515
  end
1632
2516
  private_module_function :fu_output_message
1633
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
+
1634
2568
  # This hash table holds command options.
1635
2569
  OPT_TABLE = {} #:nodoc: internal use only
1636
2570
  (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
@@ -1640,50 +2574,49 @@ module FileUtils
1640
2574
 
1641
2575
  public
1642
2576
 
2577
+ # Returns an array of the string names of \FileUtils methods
2578
+ # that accept one or more keyword arguments:
1643
2579
  #
1644
- # Returns an Array of names of high-level methods that accept any keyword
1645
- # arguments.
1646
- #
1647
- # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
2580
+ # FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"]
1648
2581
  #
1649
2582
  def self.commands
1650
2583
  OPT_TABLE.keys
1651
2584
  end
1652
2585
 
2586
+ # Returns an array of the string keyword names:
1653
2587
  #
1654
- # Returns an Array of option names.
1655
- #
1656
- # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
2588
+ # FileUtils.options.take(3) # => ["noop", "verbose", "force"]
1657
2589
  #
1658
2590
  def self.options
1659
2591
  OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1660
2592
  end
1661
2593
 
2594
+ # Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise;
2595
+ # the arguments may be strings or symbols:
1662
2596
  #
1663
- # Returns true if the method +mid+ have an option +opt+.
1664
- #
1665
- # p FileUtils.have_option?(:cp, :noop) #=> true
1666
- # p FileUtils.have_option?(:rm, :force) #=> true
1667
- # p FileUtils.have_option?(:rm, :preserve) #=> false
2597
+ # FileUtils.have_option?(:chmod, :noop) # => true
2598
+ # FileUtils.have_option?('chmod', 'secure') # => false
1668
2599
  #
1669
2600
  def self.have_option?(mid, opt)
1670
2601
  li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1671
2602
  li.include?(opt)
1672
2603
  end
1673
2604
 
2605
+ # Returns an array of the string keyword name for method +mid+;
2606
+ # the argument may be a string or a symbol:
1674
2607
  #
1675
- # Returns an Array of option names of the method +mid+.
1676
- #
1677
- # 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"]
1678
2610
  #
1679
2611
  def self.options_of(mid)
1680
2612
  OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1681
2613
  end
1682
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:
1683
2618
  #
1684
- # Returns an Array of methods names which have the option +opt+.
1685
- #
1686
- # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
2619
+ # FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"]
1687
2620
  #
1688
2621
  def self.collect_method(opt)
1689
2622
  OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }