pleasant_path 1.2.0 → 1.3.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.
@@ -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