pleasant_path 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Pathname
2
4
 
3
- # {https://ruby-doc.org/core/File/Constants.html#NULL +File::NULL+} as
4
- # a Pathname. On POSIX systems, this should be equivalent to
5
+ # {https://docs.ruby-lang.org/en/trunk/File/File/Constants.html#NULL +File::NULL+}
6
+ # as a Pathname. On POSIX systems, this should be equivalent to
5
7
  # +Pathname.new("/dev/null")+.
6
8
  NULL = Pathname.new(File::NULL)
7
9
 
8
10
  # Returns the Pathname unmodified. Exists for parity with
9
11
  # {String#to_pathname}.
10
12
  #
11
- # @return [Pathname]
13
+ # @return [self]
12
14
  def to_pathname
13
15
  self
14
16
  end
15
17
 
16
- # Joins the parent (+dirname+) of the Pathname with the argument. The
17
- # mnemonic for this operator is that the resultant path goes up one
18
- # directory level from the original, then goes down to the directory
19
- # specified by the argument.
18
+ # Joins the Pathname +dirname+ with the given +sibling+.
19
+ #
20
+ # The mnemonic for this operator is that the result is formed by going
21
+ # up one directory level from the original path, then going back down
22
+ # to +sibling+.
20
23
  #
21
24
  # @example
22
25
  # Pathname.new("path/to/file1") ^ "file2" # == Pathname.new("path/to/file2")
@@ -27,7 +30,7 @@ class Pathname
27
30
  self.dirname / sibling
28
31
  end
29
32
 
30
- # Returns the +basename+ of the Pathname's parent directory.
33
+ # Returns the +basename+ of the parent directory (+dirname+).
31
34
  #
32
35
  # @example
33
36
  # Pathname.new("path/to/file").parentname # == Pathname.new("to")
@@ -48,13 +51,15 @@ class Pathname
48
51
  #
49
52
  # Pathname.new("dir1/file2").existence # == nil
50
53
  #
51
- # @return [Pathname, nil]
54
+ # @return [self, nil]
52
55
  def existence
53
56
  self if self.exist?
54
57
  end
55
58
 
56
- # Computes the longest path that the Pathname and +other+ have in
57
- # common. See also {File.common_path}.
59
+ # Returns the longest path that the Pathname and +other+ have in
60
+ # common.
61
+ #
62
+ # @see File.common_path
58
63
  #
59
64
  # @example
60
65
  # f1 = Pathname.new("dir1/file1")
@@ -79,28 +84,21 @@ class Pathname
79
84
  # @return [Boolean]
80
85
  alias :dir? :directory?
81
86
 
82
- # True if the directory indicated by the Pathname contains no other
83
- # directories or files.
84
- #
85
- # @example
86
- # FileUtils.mkdir("parent")
87
- # FileUtils.mkdir("parent/dir1")
87
+ # @deprecated Use +Pathname#empty?+.
88
88
  #
89
- # Pathname.new("parent").dir_empty? # == false
90
- # Pathname.new("parent/dir1").dir_empty? # == true
89
+ # Alias of +Pathname#empty?+.
91
90
  #
92
91
  # @return [Boolean]
93
- def dir_empty?
94
- self.children(false).empty?
95
- end
92
+ alias :dir_empty? :empty?
96
93
 
97
- # Returns the immediate (non-recursive) child directories of the
98
- # directory indicated by the Pathname. Returned Pathnames are
99
- # prefixed by the original Pathname.
94
+ # Returns the immediate child directories of the directory indicated
95
+ # by the Pathname. Returned Pathnames are prefixed by the original
96
+ # Pathname.
100
97
  #
101
98
  # @example
102
99
  # FileUtils.mkdir("parent")
103
100
  # FileUtils.mkdir("parent/dir1")
101
+ # FileUtils.mkdir("parent/dir1/dir1")
104
102
  # FileUtils.mkdir("parent/dir2")
105
103
  # FileUtils.touch("parent/file1")
106
104
  #
@@ -111,13 +109,15 @@ class Pathname
111
109
  # # ]
112
110
  #
113
111
  # @return [Array<Pathname>]
112
+ # @raise [SystemCallError]
113
+ # if the Pathname does not point to an existing directory
114
114
  def dirs
115
115
  self.children.tap{|c| c.select!(&:dir?) }
116
116
  end
117
117
 
118
- # Returns the recursively descended child directories of the
119
- # directory indicated by the Pathname. Returned Pathnames are
120
- # prefixed by the original Pathname.
118
+ # Returns all (recursive) descendent directories of the directory
119
+ # indicated by the Pathname. Returned Pathnames are prefixed by the
120
+ # original Pathname, and are in depth-first order.
121
121
  #
122
122
  # @example
123
123
  # FileUtils.mkdir("parent")
@@ -135,18 +135,48 @@ class Pathname
135
135
  #
136
136
  # @return [Array<Pathname>]
137
137
  def dirs_r
138
- self.find.select(&:dir?).tap(&:shift)
138
+ self.find_dirs.to_a
139
139
  end
140
140
 
141
- # Returns the immediate (non-recursive) child files of the directory
142
- # indicated by the Pathname. Returned Pathnames are prefixed by the
143
- # original Pathname.
141
+ # Iterates over all (recursive) descendent directories of the
142
+ # directory indicated by the Pathname. Iterated Pathnames are
143
+ # prefixed by the original Pathname, and are in depth-first order.
144
+ #
145
+ # If no block is given, this method returns an Enumerator. Otherwise,
146
+ # the block is called with each descendent Pathname, and this method
147
+ # returns the original Pathname.
148
+ #
149
+ # @see https://docs.ruby-lang.org/en/trunk/Pathname.html#method-i-find Pathname#find
150
+ #
151
+ # @overload find_dirs()
152
+ # @return [Enumerator<Pathname>]
153
+ #
154
+ # @overload find_dirs(&block)
155
+ # @yieldparam descendent [Pathname]
156
+ # @return [Pathname]
157
+ def find_dirs
158
+ return to_enum(__method__) unless block_given?
159
+
160
+ self.find do |path|
161
+ if path.file?
162
+ Find.prune
163
+ elsif path != self
164
+ yield path
165
+ end
166
+ end
167
+
168
+ self
169
+ end
170
+
171
+ # Returns the immediate child files of the directory indicated by the
172
+ # Pathname. Returned Pathnames are prefixed by the original Pathname.
144
173
  #
145
174
  # @example
146
175
  # FileUtils.mkdir("parent")
147
176
  # FileUtils.touch("parent/file1")
148
- # FileUtils.touch("parent/file2")
149
177
  # FileUtils.mkdir("parent/dir1")
178
+ # FileUtils.touch("parent/dir1/file1")
179
+ # FileUtils.touch("parent/file2")
150
180
  #
151
181
  # Pathname.new("parent").files
152
182
  # # == [
@@ -155,43 +185,68 @@ class Pathname
155
185
  # # ]
156
186
  #
157
187
  # @return [Array<Pathname>]
188
+ # @raise [SystemCallError]
189
+ # if the Pathname does not point to an existing directory
158
190
  def files
159
191
  self.children.tap{|c| c.select!(&:file?) }
160
192
  end
161
193
 
162
- # Returns the recursively descended child files of the directory
163
- # indicated by the Pathname. Returned Pathnames are prefixed by the
164
- # original Pathname.
194
+ # Returns all (recursive) descendent files of the directory indicated
195
+ # by the Pathname. Returned Pathnames are prefixed by the original
196
+ # Pathname, and are in depth-first order.
165
197
  #
166
198
  # @example
167
199
  # FileUtils.mkdir("parent")
200
+ # FileUtils.touch("parent/file1")
168
201
  # FileUtils.mkdir("parent/dir1")
169
202
  # FileUtils.touch("parent/dir1/file1")
170
- # FileUtils.touch("parent/file1")
203
+ # FileUtils.touch("parent/file2")
171
204
  #
172
205
  # Pathname.new("parent").files_r
173
206
  # # == [
174
207
  # # Pathname.new("parent/dir1/file1"),
175
208
  # # Pathname.new("parent/file1")
209
+ # # Pathname.new("parent/file2")
176
210
  # # ]
177
211
  #
178
212
  # @return [Array<Pathname>]
179
213
  def files_r
180
- self.find.select(&:file?)
214
+ self.find_files.to_a
181
215
  end
182
216
 
183
- # Changes the current working directory to the directory indicated by
184
- # the Pathname. If a block is given, it is called with the Pathname,
185
- # and the original working directory is restored after the block
186
- # exits.
217
+ # Iterates over all (recursive) descendent files of the directory
218
+ # indicated by the Pathname. Iterated Pathnames are prefixed by the
219
+ # original Pathname, and are in depth-first order.
220
+ #
221
+ # If no block is given, this method returns an Enumerator. Otherwise,
222
+ # the block is called with each descendent Pathname, and this method
223
+ # returns the original Pathname.
187
224
  #
188
- # Returns the return value of the block, if one is given. Otherwise,
189
- # returns the Pathname.
225
+ # @see https://docs.ruby-lang.org/en/trunk/Pathname.html#method-i-find Pathname#find
190
226
  #
191
- # Raises an exception if the directory indicated by the Pathname does
192
- # not exist.
227
+ # @overload find_files()
228
+ # @return [Enumerator<Pathname>]
229
+ #
230
+ # @overload find_files(&block)
231
+ # @yieldparam descendent [Pathname]
232
+ # @return [Pathname]
233
+ def find_files
234
+ return to_enum(__method__) unless block_given?
235
+
236
+ self.find do |path|
237
+ yield path if path.file?
238
+ end
239
+
240
+ self
241
+ end
242
+
243
+ # Changes the current working directory to the Pathname. If no block
244
+ # is given, this method returns the Pathname. Otherwise, the block is
245
+ # called with the Pathname, the original working directory is restored
246
+ # after the block exits, this method returns the return value of the
247
+ # block.
193
248
  #
194
- # See also +Dir::chdir+.
249
+ # @see https://docs.ruby-lang.org/en/trunk/Dir.html#method-c-chdir Dir.chdir
195
250
  #
196
251
  # @example
197
252
  # FileUtils.mkdir("dir1")
@@ -205,12 +260,14 @@ class Pathname
205
260
  #
206
261
  # @overload chdir()
207
262
  # @return [Pathname]
208
- # @overload chdir()
209
- # @yieldparam path [Pathname]
210
- # @yieldreturn [Object] block_retval
211
- # @return [block_retval]
263
+ #
264
+ # @overload chdir(&block)
265
+ # @yieldparam working_dir [Pathname]
266
+ # @yieldreturn [Object] retval
267
+ # @return [retval]
268
+ #
212
269
  # @raise [SystemCallError]
213
- # if the directory does not exist
270
+ # if the Pathname does not point to an existing directory
214
271
  def chdir
215
272
  if block_given?
216
273
  Dir.chdir(self) do |dir|
@@ -222,7 +279,10 @@ class Pathname
222
279
  end
223
280
  end
224
281
 
225
- # Alias of +Pathname#mkpath+, but this method returns the Pathname.
282
+ # Creates the directory indicated by the Pathname, including any
283
+ # necessary parent directories. Returns the Pathname.
284
+ #
285
+ # @see https://docs.ruby-lang.org/en/trunk/Pathname.html#method-i-mkpath Pathname#mkpath
226
286
  #
227
287
  # @example
228
288
  # Dir.exist?("path") # == false
@@ -233,14 +293,16 @@ class Pathname
233
293
  # Dir.exist?("path") # == true
234
294
  # Dir.exist?("path/to") # == true
235
295
  #
236
- # @return [Pathname]
296
+ # @return [self]
297
+ # @raise [SystemCallError]
298
+ # if the Pathname points to an existing file (non-directory)
237
299
  def make_dir
238
300
  self.mkpath
239
301
  self
240
302
  end
241
303
 
242
- # Creates the parent (+dirname+) directories of the Pathname if they
243
- # do not exist, and returns the Pathname.
304
+ # Creates the directory indicated by the Pathname +dirname+, including
305
+ # any necessary parent directories. Returns the Pathname.
244
306
  #
245
307
  # @example
246
308
  # Dir.exist?("path") # == false
@@ -252,16 +314,44 @@ class Pathname
252
314
  # Dir.exist?("path/to") # == true
253
315
  # Dir.exist?("path/to/file") # == false
254
316
  #
255
- # @return [Pathname]
317
+ # @return [self]
318
+ # @raise [SystemCallError]
319
+ # if any element of the +dirname+ points to an existing file
320
+ # (non-directory)
256
321
  def make_dirname
257
322
  self.dirname.make_dir
258
323
  self
259
324
  end
260
325
 
261
- # Updates the modification time (mtime) and access time (atime) of the
262
- # file indicated by the Pathname, and returns the Pathname. Creates
263
- # the file and any necessary parent directories if they do not exist.
264
- # See also +FileUtils.touch+.
326
+ # Creates the file indicated by the Pathname, including any necessary
327
+ # parent directories. Returns the Pathname.
328
+ #
329
+ # @example
330
+ # Dir.exist?("path") # == false
331
+ # Dir.exist?("path/to") # == false
332
+ #
333
+ # Pathname.new("path/to/file").make_file # == Pathname.new("path/to/file")
334
+ #
335
+ # Dir.exist?("path") # == true
336
+ # Dir.exist?("path/to") # == true
337
+ # File.exist?("path/to/file") # == true
338
+ #
339
+ # @return [Pathname]
340
+ # @raise [SystemCallError]
341
+ # if the Pathname points to an existent directory
342
+ def make_file
343
+ self.make_dirname.open("a"){}
344
+ self
345
+ end
346
+
347
+ # @deprecated Use {Pathname#make_file}.
348
+ #
349
+ # Creates the file indicated by the Pathname, including any necessary
350
+ # parent directories. If the file already exists, its modification
351
+ # time (mtime) and access time (atime) are updated. Returns the
352
+ # Pathname.
353
+ #
354
+ # @see https://docs.ruby-lang.org/en/trunk/FileUtils.html#method-c-touch FileUtils.touch
265
355
  #
266
356
  # @example
267
357
  # Dir.exist?("path") # == false
@@ -273,16 +363,16 @@ class Pathname
273
363
  # Dir.exist?("path/to") # == true
274
364
  # File.exist?("path/to/file") # == true
275
365
  #
276
- # @return [Pathname]
366
+ # @return [self]
277
367
  def touch_file
278
368
  self.make_dirname
279
369
  FileUtils.touch(self)
280
370
  self
281
371
  end
282
372
 
283
- # Recursively deletes the directory or file indicated by the Pathname,
284
- # and returns the Pathname. Similar to +Pathname#rmtree+, but does
285
- # not raise an exception if the file does not exist.
373
+ # Recursively deletes the directory or file indicated by the Pathname.
374
+ # Similar to +Pathname#rmtree+, but does not raise an exception if the
375
+ # file does not exist. Returns the Pathname.
286
376
  #
287
377
  # @example
288
378
  # File.exist?("path/to/file") # == true
@@ -293,30 +383,92 @@ class Pathname
293
383
  # Dir.exist?("path/to") # == false
294
384
  # File.exist?("path/to/file") # == false
295
385
  #
296
- # @return [Pathname]
386
+ # @return [self]
297
387
  def delete!
298
388
  self.rmtree if self.exist?
299
389
  self
300
390
  end
301
391
 
302
- # Moves the file or directory indicated by the Pathname to the given
303
- # destination, and returns that destination as a Pathname. Creates
304
- # any necessary parent directories if they do not exist. See also
305
- # +FileUtils.mv+.
392
+ # Finds an available name based on the Pathname. If the Pathname does
393
+ # not point to an existing file or directory, returns the Pathname.
394
+ # Otherwise, iteratively generates and tests names until one is found
395
+ # that does not point to an existing file or directory.
396
+ #
397
+ # Names are generated using a Hash-style format string with three
398
+ # populated values:
399
+ #
400
+ # * +%{name}+: original Pathname basename *without* extname
401
+ # * +%{ext}+: original Pathname extname, including leading dot
402
+ # * +%{i}+: iteration counter; can be initialized via +:i+ kwarg
403
+ #
404
+ # @example Incremental
405
+ # Pathname.new("dir/file.txt").available_name # == Pathname.new("dir/file.txt")
406
+ #
407
+ # FileUtils.mkdir("dir")
408
+ # FileUtils.touch("dir/file.txt")
409
+ #
410
+ # Pathname.new("dir/file.txt").available_name # == Pathname.new("dir/file_1.txt")
411
+ #
412
+ # FileUtils.touch("dir/file_1.txt")
413
+ # FileUtils.touch("dir/file_2.txt")
414
+ #
415
+ # Pathname.new("dir/file.txt").available_name # == Pathname.new("dir/file_3.txt")
416
+ #
417
+ # @example Specifying format
418
+ # FileUtils.touch("file.txt")
419
+ #
420
+ # Pathname.new("file.txt").available_name("%{name} (%{i})%{ext}")
421
+ # # == Pathname.new("file (1).txt")
422
+ #
423
+ # @example Specifying initial counter
424
+ # FileUtils.touch("file.txt")
425
+ #
426
+ # Pathname.new("file.txt").available_name(i: 0)
427
+ # # == Pathname.new("file_0.txt")
428
+ #
429
+ # @param format [String]
430
+ # @param i [Integer]
431
+ # @return [Pathname]
432
+ def available_name(format = "%{name}_%{i}%{ext}", i: 1)
433
+ return self unless self.exist?
434
+
435
+ dirname = File.dirname(self)
436
+ format = "%{dirname}/" + format unless dirname == "."
437
+
438
+ values = {
439
+ dirname: dirname,
440
+ name: File.basename(self, ".*"),
441
+ ext: self.extname,
442
+ i: i,
443
+ }
444
+
445
+ while (path = format % values) && File.exist?(path)
446
+ values[:i] += 1
447
+ end
448
+
449
+ path.to_pathname
450
+ end
451
+
452
+ # Moves the file or directory indicated by the Pathname to
453
+ # +destination+, in the same manner as +FileUtils.mv+. Creates any
454
+ # necessary parent directories of the destination. Returns
455
+ # +destination+ as a Pathname.
456
+ #
457
+ # @see https://docs.ruby-lang.org/en/trunk/FileUtils.html#method-c-mv FileUtils.mv
306
458
  #
307
459
  # @example
308
- # File.exist?("path/to/file") # == true
309
- # Dir.exist?("some") # == false
310
- # Dir.exist?("some/other") # == false
311
- # File.exist?("some/other/thing") # == false
460
+ # File.exist?("path/to/file") # == true
461
+ # Dir.exist?("other") # == false
462
+ # Dir.exist?("other/dir") # == false
463
+ # File.exist?("other/dir/same_file") # == false
312
464
  #
313
- # Pathname.new("path/to/file").move("some/other/thing")
314
- # # == Pathname.new("some/other/thing")
465
+ # Pathname.new("path/to/file").move("other/dir/same_file")
466
+ # # == Pathname.new("other/dir/same_file")
315
467
  #
316
- # File.exist?("path/to/file") # == false
317
- # Dir.exist?("some") # == true
318
- # Dir.exist?("some/other") # == true
319
- # File.exist?("some/other/thing") # == true
468
+ # File.exist?("path/to/file") # == false
469
+ # Dir.exist?("other") # == true
470
+ # Dir.exist?("other/dir") # == true
471
+ # File.exist?("other/dir/same_file") # == true
320
472
  #
321
473
  # @param destination [Pathname, String]
322
474
  # @return [Pathname]
@@ -327,48 +479,189 @@ class Pathname
327
479
  destination
328
480
  end
329
481
 
330
- # Moves the file or directory indicated by the Pathname into the given
331
- # directory, and returns the resultant path as a Pathname. Creates
332
- # any necessary parent directories if they do not exist.
333
- #
334
- # @example
335
- # File.exist?("path/to/file") # == true
336
- # Dir.exist?("other") # == false
337
- # Dir.exist?("other/path") # == false
338
- # File.exist?("other/path/file") # == false
339
- #
340
- # Pathname.new("path/to/file").move_into("other/path")
341
- # # == Pathname.new("other/path/file")
482
+ # Moves the file or directory indicated by the Pathname to a
483
+ # destination, replacing any existing file or directory.
484
+ #
485
+ # If a block is given and a file or directory does exist at the
486
+ # destination, the block is called with the source and destination
487
+ # Pathnames, and the return value of the block is used as the new
488
+ # destination. If the block returns the source Pathname or +nil+, the
489
+ # move is aborted.
490
+ #
491
+ # Creates any necessary parent directories of the destination.
492
+ # Returns the destination as a Pathname (or the source Pathname in the
493
+ # case that the move is aborted).
494
+ #
495
+ # *WARNING:* Due to system API limitations, the move is performed in
496
+ # two steps, non-atomically. First, any file or directory existing
497
+ # at the destination is deleted. Next, the source is moved to the
498
+ # destination. The second step can fail independently of the first,
499
+ # e.g. due to insufficient disk space, leaving the file or directory
500
+ # previously at the destination deleted without replacement.
501
+ #
502
+ # @example Without a block
503
+ # FileUtils.touch("file")
504
+ #
505
+ # Pathname.new("file").move_as("dir/file")
506
+ # # == Pathname.new("dir/file")
507
+ #
508
+ # File.exist?("file") # == false
509
+ # File.exist?("dir/file") # == true
510
+ #
511
+ # @example Error on older source
512
+ # FileUtils.touch("file")
513
+ # sleep 1
514
+ # FileUtils.touch("file.new")
515
+ #
516
+ # Pathname.new("file.new").move_as("file") do |source, destination|
517
+ # if source.mtime < destination.mtime
518
+ # raise "cannot replace newer file #{destination} with #{source}"
519
+ # end
520
+ # destination
521
+ # end # == Pathname.new("file")
522
+ #
523
+ # File.exist?("file.new") # == false
524
+ # File.exist?("file") # == true
525
+ #
526
+ # @example Abort on conflict
527
+ # FileUtils.touch("file1")
528
+ # FileUtils.touch("file2")
529
+ #
530
+ # Pathname.new("file1").move_as("file2") do |source, destination|
531
+ # puts "#{source} not moved to #{destination} due to conflict"
532
+ # nil
533
+ # end # == Pathname.new("file1")
534
+ #
535
+ # File.exist?("file1") # == true
536
+ # File.exist?("file2") # == true
537
+ #
538
+ # @example New destination on conflict
539
+ # FileUtils.touch("file1")
540
+ # FileUtils.touch("file2")
541
+ #
542
+ # Pathname.new("file1").move_as("file2") do |source, destination|
543
+ # destination.available_name
544
+ # end # == Pathname.new("file2_1")
545
+ #
546
+ # File.exist?("file1") # == false
547
+ # File.exist?("file2") # == true
548
+ # File.exist?("file2_1") # == true
549
+ #
550
+ # @overload move_as(destination)
551
+ # @param destination [Pathname, String]
552
+ # @return [Pathname]
342
553
  #
343
- # File.exist?("path/to/file") # == false
344
- # Dir.exist?("other") # == true
345
- # Dir.exist?("other/path") # == true
346
- # File.exist?("other/path/file") # == true
554
+ # @overload move_as(destination, &block)
555
+ # @param destination [Pathname, String]
556
+ # @yieldparam source [Pathname]
557
+ # @yieldparam destination [Pathname]
558
+ # @yieldreturn [Pathname, nil]
559
+ # @return [Pathname]
560
+ def move_as(destination)
561
+ destination = destination.to_pathname
562
+
563
+ if block_given? && destination.exist? && self.exist? && !File.identical?(self, destination)
564
+ destination = (yield self, destination) || self
565
+ end
566
+
567
+ if destination != self
568
+ if File.identical?(self, destination)
569
+ # FileUtils.mv raises an ArgumentError when both paths refer to
570
+ # the same file. On case-insensitive file systems, this occurs
571
+ # even when both paths have different casing. We want to
572
+ # disregard the ArgumentError at all times, and change the
573
+ # filename casing when applicable.
574
+ File.rename(self, destination)
575
+ else
576
+ destination.delete!
577
+ self.move(destination)
578
+ end
579
+ end
580
+
581
+ destination
582
+ end
583
+
584
+ # Moves the file or directory indicated by the Pathname into
585
+ # +directory+, replacing any existing file or directory of the same
586
+ # basename.
587
+ #
588
+ # If a block is given and a file or directory does exist at the
589
+ # resultant destination, the block is called with the source and
590
+ # destination Pathnames, and the return value of the block is used as
591
+ # the new destination. If the block returns the source Pathname or
592
+ # +nil+, the move is aborted.
593
+ #
594
+ # Creates any necessary parent directories of the destination.
595
+ # Returns the destination as a Pathname (or the source Pathname in the
596
+ # case that the move is aborted).
597
+ #
598
+ # *WARNING:* Due to system API limitations, the move is performed in
599
+ # two steps, non-atomically. First, any file or directory existing
600
+ # at the destination is deleted. Next, the source is moved to the
601
+ # destination. The second step can fail independently of the first,
602
+ # e.g. due to insufficient disk space, leaving the file or directory
603
+ # previously at the destination deleted without replacement.
604
+ #
605
+ # @see Pathname#move_as
606
+ #
607
+ # @example Without a block
608
+ # FileUtils.touch("file")
609
+ #
610
+ # Pathname.new("file").move_into("dir")
611
+ # # == Pathname.new("dir/file")
612
+ #
613
+ # File.exist?("file") # == false
614
+ # File.exist?("dir/file") # == true
615
+ #
616
+ # @example With a block
617
+ # FileUtils.mkpath("files")
618
+ # FileUtils.touch("files/file1")
619
+ # FileUtils.mkpath("dir/files")
620
+ # FileUtils.touch("dir/files/file2")
621
+ #
622
+ # Pathname.new("files").move_into("dir") do |source, destination|
623
+ # source # == Pathname.new("files")
624
+ # destination # == Pathname.new("dir/files")
625
+ # end # == Pathname.new("dir/files")
626
+ #
627
+ # Dir.exist?("files") # == false
628
+ # File.exist?("dir/files/file1") # == true
629
+ # File.exist?("dir/files/file2") # == false
630
+ #
631
+ # @overload move_into(directory)
632
+ # @param directory [Pathname, String]
633
+ # @return [Pathname]
347
634
  #
348
- # @param directory [Pathname, String]
349
- # @return [Pathname]
350
- def move_into(directory)
351
- self.move(directory / self.basename)
635
+ # @overload move_into(directory, &block)
636
+ # @param directory [Pathname, String]
637
+ # @yieldparam source [Pathname]
638
+ # @yieldparam destination [Pathname]
639
+ # @yieldreturn [Pathname, nil]
640
+ # @return [Pathname]
641
+ def move_into(directory, &block)
642
+ self.move_as(directory / self.basename, &block)
352
643
  end
353
644
 
354
- # Copies the file or directory indicated by the Pathname to the given
355
- # destination, and returns that destination as a Pathname. Creates
356
- # any necessary parent directories if they do not exist. See also
357
- # +FileUtils.cp_r+.
645
+ # Copies the file or directory indicated by the Pathname to
646
+ # +destination+, in the same manner as +FileUtils.cp_r+. Creates any
647
+ # necessary parent directories of the destination. Returns
648
+ # +destination+ as a Pathname.
649
+ #
650
+ # @see https://docs.ruby-lang.org/en/trunk/FileUtils.html#method-c-cp_r FileUtils.cp_r
358
651
  #
359
652
  # @example
360
- # File.exist?("path/to/file") # == true
361
- # Dir.exist?("some") # == false
362
- # Dir.exist?("some/other") # == false
363
- # File.exist?("some/other/thing") # == false
653
+ # File.exist?("path/to/file") # == true
654
+ # Dir.exist?("other") # == false
655
+ # Dir.exist?("other/dir") # == false
656
+ # File.exist?("other/dir/same_file") # == false
364
657
  #
365
- # Pathname.new("path/to/file").copy("some/other/thing")
366
- # # == Pathname.new("some/other/thing")
658
+ # Pathname.new("path/to/file").copy("other/dir/same_file")
659
+ # # == Pathname.new("other/dir/same_file")
367
660
  #
368
- # File.exist?("path/to/file") # == true
369
- # Dir.exist?("some") # == true
370
- # Dir.exist?("some/other") # == true
371
- # File.exist?("some/other/thing") # == true
661
+ # File.exist?("path/to/file") # == true
662
+ # Dir.exist?("other") # == true
663
+ # Dir.exist?("other/dir") # == true
664
+ # File.exist?("other/dir/same_file") # == true
372
665
  #
373
666
  # @param destination [Pathname, String]
374
667
  # @return [Pathname]
@@ -379,86 +672,311 @@ class Pathname
379
672
  destination
380
673
  end
381
674
 
382
- # Copies the file or directory indicated by the Pathname into the
383
- # given directory, and returns the resultant path as a Pathname.
384
- # Creates any necessary parent directories if they do not exist.
385
- #
386
- # @example
387
- # File.exist?("path/to/file") # == true
388
- # Dir.exist?("other") # == false
389
- # Dir.exist?("other/path") # == false
390
- # File.exist?("other/path/file") # == false
675
+ # Copies the file or directory indicated by the Pathname to a
676
+ # destination, replacing any existing file or directory.
677
+ #
678
+ # If a block is given and a file or directory does exist at the
679
+ # destination, the block is called with the source and destination
680
+ # Pathnames, and the return value of the block is used as the new
681
+ # destination. If the block returns the source Pathname or +nil+, the
682
+ # copy is aborted.
683
+ #
684
+ # Creates any necessary parent directories of the destination.
685
+ # Returns the destination as a Pathname (or the source Pathname in the
686
+ # case that the copy is aborted).
687
+ #
688
+ # *WARNING:* Due to system API limitations, the copy is performed in
689
+ # two steps, non-atomically. First, any file or directory existing
690
+ # at the destination is deleted. Next, the source is copied to the
691
+ # destination. The second step can fail independently of the first,
692
+ # e.g. due to insufficient disk space, leaving the file or directory
693
+ # previously at the destination deleted without replacement.
694
+ #
695
+ # @example Without a block
696
+ # FileUtils.touch("file")
697
+ #
698
+ # Pathname.new("file").copy_as("dir/file")
699
+ # # == Pathname.new("dir/file")
700
+ #
701
+ # File.exist?("file") # == true
702
+ # File.exist?("dir/file") # == true
703
+ #
704
+ # @example Error on older source
705
+ # File.write("file", "A")
706
+ # sleep 1
707
+ # File.write("file.new", "B")
708
+ #
709
+ # Pathname.new("file.new").copy_as("file") do |source, destination|
710
+ # if source.mtime < destination.mtime
711
+ # raise "cannot replace newer file #{destination} with #{source}"
712
+ # end
713
+ # destination
714
+ # end # == Pathname.new("file")
715
+ #
716
+ # File.read("file.new") # == "B"
717
+ # File.read("file") # == "B"
718
+ #
719
+ # @example Abort on conflict
720
+ # File.write("file1", "A")
721
+ # File.write("file2", "B")
722
+ #
723
+ # Pathname.new("file1").copy_as("file2") do |source, destination|
724
+ # puts "#{source} not copied to #{destination} due to conflict"
725
+ # nil
726
+ # end # == Pathname.new("file1")
727
+ #
728
+ # File.read("file1") # == "A"
729
+ # File.read("file2") # == "B"
730
+ #
731
+ # @example New destination on conflict
732
+ # File.write("file1", "A")
733
+ # File.write("file2", "B")
734
+ #
735
+ # Pathname.new("file1").copy_as("file2") do |source, destination|
736
+ # destination.available_name
737
+ # end # == Pathname.new("file2_1")
738
+ #
739
+ # File.read("file1") # == "A"
740
+ # File.read("file2") # == "B"
741
+ # File.read("file2_1") # == "A"
742
+ #
743
+ # @overload copy_as(destination)
744
+ # @param destination [Pathname, String]
745
+ # @return [Pathname]
391
746
  #
392
- # Pathname.new("path/to/file").copy_into("other/path")
393
- # # == Pathname.new("other/path/file")
747
+ # @overload copy_as(destination, &block)
748
+ # @param destination [Pathname, String]
749
+ # @yieldparam source [Pathname]
750
+ # @yieldparam destination [Pathname]
751
+ # @yieldreturn [Pathname, nil]
752
+ # @return [Pathname]
753
+ def copy_as(destination)
754
+ destination = destination.to_pathname
755
+
756
+ if block_given? && destination.exist? && self.exist? && !File.identical?(self, destination)
757
+ destination = yield self, destination
758
+ destination = nil if destination == self
759
+ end
760
+
761
+ if destination
762
+ destination.delete! unless File.identical?(self, destination)
763
+ self.copy(destination)
764
+ end
765
+
766
+ destination || self
767
+ end
768
+
769
+
770
+ # Copies the file or directory indicated by the Pathname into
771
+ # +directory+, replacing any existing file or directory of the same
772
+ # basename.
773
+ #
774
+ # If a block is given and a file or directory does exist at the
775
+ # resultant destination, the block is called with the source and
776
+ # destination Pathnames, and the return value of the block is used as
777
+ # the new destination. If the block returns the source Pathname or
778
+ # +nil+, the copy is aborted.
779
+ #
780
+ # Creates any necessary parent directories of the destination.
781
+ # Returns the destination as a Pathname (or the source Pathname in the
782
+ # case that the copy is aborted).
783
+ #
784
+ # *WARNING:* Due to system API limitations, the copy is performed in
785
+ # two steps, non-atomically. First, any file or directory existing
786
+ # at the destination is deleted. Next, the source is copied to the
787
+ # destination. The second step can fail independently of the first,
788
+ # e.g. due to insufficient disk space, leaving the file or directory
789
+ # previously at the destination deleted without replacement.
790
+ #
791
+ # @see Pathname#copy_as
792
+ #
793
+ # @example Without a block
794
+ # FileUtils.touch("file")
795
+ #
796
+ # Pathname.new("file").copy_into("dir")
797
+ # # == Pathname.new("dir/file")
798
+ #
799
+ # File.exist?("file") # == true
800
+ # File.exist?("dir/file") # == true
801
+ #
802
+ # @example With a block
803
+ # FileUtils.mkpath("files")
804
+ # FileUtils.touch("files/file1")
805
+ # FileUtils.mkpath("dir/files")
806
+ # FileUtils.touch("dir/files/file2")
807
+ #
808
+ # Pathname.new("files").copy_into("dir") do |source, destination|
809
+ # source # == Pathname.new("files")
810
+ # destination # == Pathname.new("dir/files")
811
+ # end # == Pathname.new("dir/files")
812
+ #
813
+ # File.exist?("files/file1") # == true
814
+ # File.exist?("dir/files/file1") # == true
815
+ # File.exist?("dir/files/file2") # == false
816
+ #
817
+ # @overload copy_into(directory)
818
+ # @param directory [Pathname, String]
819
+ # @return [Pathname]
394
820
  #
395
- # File.exist?("path/to/file") # == true
396
- # Dir.exist?("other") # == true
397
- # Dir.exist?("other/path") # == true
398
- # File.exist?("other/path/file") # == true
821
+ # @overload copy_into(directory, &block)
822
+ # @param directory [Pathname, String]
823
+ # @yieldparam source [Pathname]
824
+ # @yieldparam destination [Pathname]
825
+ # @yieldreturn [Pathname, nil]
826
+ # @return [Pathname]
827
+ def copy_into(directory, &block)
828
+ self.copy_as(directory / self.basename, &block)
829
+ end
830
+
831
+ # Alias of +Pathname#move_as+.
399
832
  #
400
- # @param directory [Pathname, String]
401
833
  # @return [Pathname]
402
- def copy_into(directory)
403
- self.copy(directory / self.basename)
404
- end
834
+ alias :rename_as :move_as
405
835
 
406
- # Renames the file or directory indicated by the Pathname, but
407
- # preserves its location as indicated by +dirname+. Returns the
408
- # resultant path as a Pathname.
836
+ # Renames the file or directory indicated by the Pathname relative to
837
+ # its +dirname+, replacing any existing file or directory of the same
838
+ # basename.
409
839
  #
410
- # @example
411
- # File.exist?("path/to/file") # == true
840
+ # If a block is given and a file or directory does exist at the
841
+ # resultant destination, the block is called with the source and
842
+ # destination Pathnames, and the return value of the block is used as
843
+ # the new destination. If the block returns the source Pathname or
844
+ # +nil+, the rename is aborted.
412
845
  #
413
- # Pathname.new("path/to/file").rename_basename("other")
414
- # # == Pathname.new("path/to/other")
846
+ # Returns the destination as a Pathname (or the source Pathname in the
847
+ # case that the rename is aborted).
415
848
  #
416
- # File.exist?("path/to/file") # == false
417
- # File.exist?("path/to/other") # == true
849
+ # *WARNING:* Due to system API limitations, the rename is performed in
850
+ # two steps, non-atomically. First, any file or directory existing
851
+ # at the destination is deleted. Next, the source is moved to the
852
+ # destination. The second step can fail independently of the first,
853
+ # e.g. due to insufficient disk space, leaving the file or directory
854
+ # previously at the destination deleted without replacement.
418
855
  #
419
- # @param new_basename [String]
420
- # @return [Pathname]
421
- def rename_basename(new_basename)
422
- new_path = self.dirname / new_basename
423
- self.rename(new_path)
424
- new_path
856
+ # @see Pathname#move_as
857
+ #
858
+ # @example Without a block
859
+ # FileUtils.mkpath("dir")
860
+ # FileUtils.touch("dir/file")
861
+ #
862
+ # Pathname.new("dir/file").rename_basename("same_file")
863
+ # # == Pathname.new("dir/same_file")
864
+ #
865
+ # File.exist?("dir/file") # == false
866
+ # File.exist?("dir/same_file") # == true
867
+ #
868
+ # @example With a block
869
+ # FileUtils.mkpath("dir")
870
+ # FileUtils.touch("dir/file1")
871
+ # FileUtils.touch("dir/file2")
872
+ #
873
+ # Pathname.new("dir/file1").rename_basename("file2") do |source, destination|
874
+ # source # == Pathname.new("dir/file1")
875
+ # destination # == Pathname.new("dir/file2")
876
+ # end # == Pathname.new("dir/file2")
877
+ #
878
+ # File.exist?("dir/file1") # == false
879
+ # File.exist?("dir/file2") # == true
880
+ #
881
+ # @overload rename_basename(new_basename)
882
+ # @param new_basename [Pathname, String]
883
+ # @return [Pathname]
884
+ #
885
+ # @overload rename_basename(new_basename, &block)
886
+ # @param new_basename [Pathname, String]
887
+ # @yieldparam source [Pathname]
888
+ # @yieldparam destination [Pathname]
889
+ # @yieldreturn [Pathname, nil]
890
+ # @return [Pathname]
891
+ def rename_basename(new_basename, &block)
892
+ self.move_as(self.dirname / new_basename, &block)
425
893
  end
426
894
 
427
- # Renames the file extension of the file indicated by the Pathname.
428
- # If the file has no extension, the new extension is appended.
895
+ # Changes the file extension (+extname+) of the file indicated by the
896
+ # Pathname, replacing any existing file or directory of the same
897
+ # resultant basename.
429
898
  #
430
- # @example replace extension
431
- # File.exist?("path/to/file.abc") # == true
899
+ # If a block is given and a file or directory does exist at the
900
+ # resultant destination, the block is called with the source and
901
+ # destination Pathnames, and the return value of the block is used as
902
+ # the new destination. If the block returns the source Pathname or
903
+ # +nil+, the rename is aborted.
432
904
  #
433
- # Pathname.new("path/to/file.abc").rename_extname(".xyz")
434
- # # == Pathname.new("path/to/file.xyz")
905
+ # Returns the destination as a Pathname (or the source Pathname in the
906
+ # case that the rename is aborted).
435
907
  #
436
- # File.exist?("path/to/file.abc") # == false
437
- # File.exist?("path/to/file.xyz") # == true
908
+ # *WARNING:* Due to system API limitations, the rename is performed in
909
+ # two steps, non-atomically. First, any file or directory existing
910
+ # at the destination is deleted. Next, the source is moved to the
911
+ # destination. The second step can fail independently of the first,
912
+ # e.g. due to insufficient disk space, leaving the file or directory
913
+ # previously at the destination deleted without replacement.
438
914
  #
439
- # @example remove extension
440
- # File.exist?("path/to/file.abc") # == true
915
+ # @see Pathname#move_as
441
916
  #
442
- # Pathname.new("path/to/file.abc").rename_extname("")
443
- # # == Pathname.new("path/to/file")
917
+ # @example Replace extension
918
+ # FileUtils.mkpath("dir")
919
+ # FileUtils.touch("dir/file.abc")
444
920
  #
445
- # File.exist?("path/to/file.abc") # == false
446
- # File.exist?("path/to/file") # == true
921
+ # Pathname.new("dir/file.abc").rename_extname(".xyz")
922
+ # # == Pathname.new("dir/file.xyz")
447
923
  #
448
- # @param new_extname [String]
449
- # @return [Pathname]
450
- def rename_extname(new_extname)
924
+ # File.exist?("dir/file.abc") # == false
925
+ # File.exist?("dir/file.xyz") # == true
926
+ #
927
+ # @example Add extension
928
+ # FileUtils.mkpath("dir")
929
+ # FileUtils.touch("dir/file")
930
+ #
931
+ # Pathname.new("dir/file").rename_extname(".abc")
932
+ # # == Pathname.new("dir/file.abc")
933
+ #
934
+ # File.exist?("dir/file") # == false
935
+ # File.exist?("dir/file.abc") # == true
936
+ #
937
+ # @example Remove extension
938
+ # FileUtils.mkpath("dir")
939
+ # FileUtils.touch("dir/file.abc")
940
+ #
941
+ # Pathname.new("dir/file.abc").rename_extname("")
942
+ # # == Pathname.new("dir/file")
943
+ #
944
+ # File.exist?("dir/file.abc") # == false
945
+ # File.exist?("dir/file") # == true
946
+ #
947
+ # @example With a block
948
+ # FileUtils.mkpath("dir")
949
+ # FileUtils.touch("dir/file.abc")
950
+ # FileUtils.touch("dir/file.xyz")
951
+ #
952
+ # Pathname.new("dir/file.abc").rename_extname(".xyz") do |source, destination|
953
+ # source # == Pathname.new("dir/file.abc")
954
+ # destination # == Pathname.new("dir/file.xyz")
955
+ # end # == Pathname.new("dir/file.xyz")
956
+ #
957
+ # File.exist?("dir/file.abc") # == false
958
+ # File.exist?("dir/file.xyz") # == true
959
+ #
960
+ # @overload rename_extname(new_extname)
961
+ # @param new_extname [String]
962
+ # @return [Pathname]
963
+ #
964
+ # @overload rename_extname(new_extname, &block)
965
+ # @param new_extname [String]
966
+ # @yieldparam source [Pathname]
967
+ # @yieldparam destination [Pathname]
968
+ # @yieldreturn [Pathname, nil]
969
+ # @return [Pathname]
970
+ def rename_extname(new_extname, &block)
451
971
  unless new_extname.start_with?(".") || new_extname.empty?
452
972
  new_extname = ".#{new_extname}"
453
973
  end
454
- new_path = self.sub_ext(new_extname)
455
- self.rename(new_path)
456
- new_path
974
+ self.move_as(self.sub_ext(new_extname), &block)
457
975
  end
458
976
 
459
- # Writes given text to the file indicated by the Pathname, and returns
460
- # the Pathname. The file is overwritten if it already exists. Any
461
- # necessary parent directories are created if they do not exist.
977
+ # Writes +text+ to the file indicated by the Pathname, overwriting the
978
+ # file if it exists. Creates the file if it does not exist, including
979
+ # any necessary parent directories. Returns the Pathname.
462
980
  #
463
981
  # @example
464
982
  # Dir.exist?("path") # == false
@@ -471,15 +989,15 @@ class Pathname
471
989
  # File.read("path/to/file") # == "hello world"
472
990
  #
473
991
  # @param text [String]
474
- # @return [Pathname]
992
+ # @return [self]
475
993
  def write_text(text)
476
994
  self.make_dirname.open("w"){|f| f.write(text) }
477
995
  self
478
996
  end
479
997
 
480
- # Appends given text to the file indicated by the Pathname, and
481
- # returns the Pathname. The file is created if it does not exist.
482
- # Any necessary parent directories are created if they do not exist.
998
+ # Appends +text+ to the file indicated by the Pathname. Creates the
999
+ # file if it does not exist, including any necessary parent
1000
+ # directories. Returns the Pathname.
483
1001
  #
484
1002
  # @example
485
1003
  # Dir.exist?("path") # == false
@@ -492,16 +1010,16 @@ class Pathname
492
1010
  # File.read("path/to/file") # == "hello world"
493
1011
  #
494
1012
  # @param text [String]
495
- # @return [Pathname]
1013
+ # @return [self]
496
1014
  def append_text(text)
497
1015
  self.make_dirname.open("a"){|f| f.write(text) }
498
1016
  self
499
1017
  end
500
1018
 
501
- # Writes each object as a string plus a succeeding new line character
502
- # (<code>$/</code>) to the file indicated by the Pathname. Returns
503
- # the Pathname. The file is overwritten if it already exists. Any
504
- # necessary parent directories are created if they do not exist.
1019
+ # Writes each object in +lines+ as a string plus end-of-line (EOL)
1020
+ # characters to the file indicated by the Pathname, overwriting the
1021
+ # file if it exists. Creates the file if it does not exist, including
1022
+ # any necessary parent directories. Returns the Pathname.
505
1023
  #
506
1024
  # @example
507
1025
  # File.exist?("path/to/file") # false
@@ -512,16 +1030,17 @@ class Pathname
512
1030
  # File.read("path/to/file") # == "one\ntwo\n"
513
1031
  #
514
1032
  # @param lines [Enumerable<#to_s>]
515
- # @return [Pathname]
516
- def write_lines(lines)
517
- self.make_dirname.open("w"){|f| f.write_lines(lines) }
1033
+ # @param eol [String]
1034
+ # @return [self]
1035
+ def write_lines(lines, eol: $/)
1036
+ self.make_dirname.open("w"){|f| f.write_lines(lines, eol: eol) }
518
1037
  self
519
1038
  end
520
1039
 
521
- # Appends each object as a string plus a succeeding new line character
522
- # (<code>$/</code>) to the file indicated by the Pathname. Returns
523
- # the Pathname. The file is created if it does not exist. Any
524
- # necessary parent directories are created if they do not exist.
1040
+ # Appends each object in +lines+ as a string plus end-of-line (EOL)
1041
+ # characters to the file indicated by the Pathname. Creates the file
1042
+ # if it does not exist, including any necessary parent directories.
1043
+ # Returns the Pathname.
525
1044
  #
526
1045
  # @example
527
1046
  # File.exist?("path/to/file") # false
@@ -532,9 +1051,10 @@ class Pathname
532
1051
  # File.read("path/to/file") # == "one\ntwo\nthree\nfour\n"
533
1052
  #
534
1053
  # @param lines [Enumerable<#to_s>]
535
- # @return [Pathname]
536
- def append_lines(lines)
537
- self.make_dirname.open("a"){|f| f.write_lines(lines) }
1054
+ # @param eol [String]
1055
+ # @return [self]
1056
+ def append_lines(lines, eol: $/)
1057
+ self.make_dirname.open("a"){|f| f.write_lines(lines, eol: eol) }
538
1058
  self
539
1059
  end
540
1060
 
@@ -543,31 +1063,33 @@ class Pathname
543
1063
  # @return [String]
544
1064
  alias :read_text :read
545
1065
 
546
- # Reads from the file indicated by the Pathname all lines, and returns
547
- # them as an array, end-of-line characters excluded. The
548
- # <code>$/</code> global string specifies what end-of-line characters
549
- # to look for. See also {IO#read_lines}.
1066
+ # Reads all lines from the file indicated by the Pathname, and returns
1067
+ # them with all end-of-line (EOL) characters stripped.
1068
+ #
1069
+ # @see IO#read_lines
550
1070
  #
551
- # (Not to be confused with +Pathname#readlines+ which retains
552
- # end-of-line characters in every string it returns.)
1071
+ # @note Not to be confused with +Pathname#readlines+, which retains
1072
+ # end-of-line (EOL) characters.
553
1073
  #
554
1074
  # @example
555
1075
  # File.read("path/to/file") # == "one\ntwo\n"
556
1076
  #
557
1077
  # Pathname.new("path/to/file").read_lines # == ["one", "two"]
558
1078
  #
1079
+ # @param eol [String]
559
1080
  # @return [Array<String>]
560
- def read_lines
561
- self.open("r"){|f| f.read_lines }
1081
+ def read_lines(eol: $/)
1082
+ self.open("r"){|f| f.read_lines(eol: eol) }
562
1083
  end
563
1084
 
564
- # Reads the contents of the file indicated by the Pathname into memory
565
- # as a string, and yields the string to the given block for editing.
1085
+ # Reads the entire contents of the file indicated by the Pathname as a
1086
+ # string, and yields that string to the given block for editing.
566
1087
  # Writes the return value of the block back to the file, overwriting
567
- # previous contents. Returns the file's new contents. See also
568
- # {File.edit_text}.
1088
+ # previous contents. Returns the return value of the block.
1089
+ #
1090
+ # @see File.edit_text
569
1091
  #
570
- # @example update JSON data file
1092
+ # @example Update JSON data file
571
1093
  # File.read("data.json") # == '{"nested":{"key":"value"}}'
572
1094
  #
573
1095
  # Pathname.new("data.json").edit_text do |text|
@@ -578,23 +1100,24 @@ class Pathname
578
1100
  #
579
1101
  # File.read("data.json") # == '{"nested":{"key":"new value"}}'
580
1102
  #
581
- # @yield [text] edits current file contents
582
- # @yieldparam text [String] current contents
583
- # @yieldreturn [String] new contents
1103
+ # @yield [text]
1104
+ # @yieldparam text [String]
1105
+ # @yieldreturn [String]
584
1106
  # @return [String]
585
1107
  def edit_text(&block)
586
1108
  File.edit_text(self, &block)
587
1109
  end
588
1110
 
589
- # Reads the contents of the file indicated by the Pathname into memory
590
- # as an array of lines, and yields the array to the given block for
1111
+ # Reads the entire contents of the file indicated by the Pathname as
1112
+ # an array of lines, and yields that array to the given block for
591
1113
  # editing. Writes the return value of the block back to the file,
592
- # overwriting previous contents. The <code>$/</code> global string
593
- # specifies what end-of-line characters to use for both reading and
594
- # writing. Returns the array of lines that comprises the file's new
595
- # contents. See also {File.edit_lines}.
1114
+ # overwriting previous contents. End-of-line (EOL) characters are
1115
+ # stripped when reading, and appended after each line when writing.
1116
+ # Returns the return value of the block.
596
1117
  #
597
- # @example dedup lines of file
1118
+ # @see File.edit_lines
1119
+ #
1120
+ # @example Dedup lines of file
598
1121
  # File.read("entries.txt") # == "AAA\nBBB\nBBB\nCCC\nAAA\n"
599
1122
  #
600
1123
  # Pathname.new("entries.txt").edit_lines(&:uniq)
@@ -602,16 +1125,17 @@ class Pathname
602
1125
  #
603
1126
  # File.read("entries.txt") # == "AAA\nBBB\nCCC\n"
604
1127
  #
605
- # @yield [lines] edits current file contents
606
- # @yieldparam lines [Array<String>] current contents
607
- # @yieldreturn [Array<String>] new contents
1128
+ # @param eol [String]
1129
+ # @yield [lines]
1130
+ # @yieldparam lines [Array<String>]
1131
+ # @yieldreturn [Array<String>]
608
1132
  # @return [Array<String>]
609
- def edit_lines(&block)
610
- File.edit_lines(self, &block)
1133
+ def edit_lines(eol: $/, &block)
1134
+ File.edit_lines(self, eol: eol, &block)
611
1135
  end
612
1136
 
613
- # Appends the contents of another file to the destination indicated by
614
- # Pathname. Returns the destination Pathname.
1137
+ # Appends the contents of file indicated by +source+ to the file
1138
+ # indicated by the Pathname. Returns the Pathname.
615
1139
  #
616
1140
  # @example
617
1141
  # File.read("yearly.log") # == "one\ntwo\n"
@@ -623,7 +1147,7 @@ class Pathname
623
1147
  # File.read("yearly.log") # == "one\ntwo\nthree\nfour\n"
624
1148
  #
625
1149
  # @param source [String, Pathname]
626
- # @return [Pathname]
1150
+ # @return [self]
627
1151
  def append_file(source)
628
1152
  self.open("a"){|destination| IO::copy_stream(source, destination) }
629
1153
  self