pathname 0.4.0 → 0.5.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.
@@ -0,0 +1,1219 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # = pathname.rb
4
+ #
5
+ # Object-Oriented Pathname Class
6
+ #
7
+ # Author:: Tanaka Akira <akr@m17n.org>
8
+ # Documentation:: Author and Gavin Sinclair
9
+ #
10
+ # For documentation, see class Pathname.
11
+ #
12
+
13
+ #
14
+ # Pathname represents the name of a file or directory on the filesystem,
15
+ # but not the file itself.
16
+ #
17
+ # The pathname depends on the Operating System: Unix, Windows, etc.
18
+ # This library works with pathnames of local OS, however non-Unix pathnames
19
+ # are supported experimentally.
20
+ #
21
+ # A Pathname can be relative or absolute. It's not until you try to
22
+ # reference the file that it even matters whether the file exists or not.
23
+ #
24
+ # Pathname is immutable. It has no method for destructive update.
25
+ #
26
+ # The goal of this class is to manipulate file path information in a neater
27
+ # way than standard Ruby provides. The examples below demonstrate the
28
+ # difference.
29
+ #
30
+ # *All* functionality from File, FileTest, and some from Dir and FileUtils is
31
+ # included, in an unsurprising way. It is essentially a facade for all of
32
+ # these, and more.
33
+ #
34
+ # == Examples
35
+ #
36
+ # === Example 1: Using Pathname
37
+ #
38
+ # require 'pathname'
39
+ # pn = Pathname.new("/usr/bin/ruby")
40
+ # size = pn.size # 27662
41
+ # isdir = pn.directory? # false
42
+ # dir = pn.dirname # Pathname:/usr/bin
43
+ # base = pn.basename # Pathname:ruby
44
+ # dir, base = pn.split # [Pathname:/usr/bin, Pathname:ruby]
45
+ # data = pn.read
46
+ # pn.open { |f| _ }
47
+ # pn.each_line { |line| _ }
48
+ #
49
+ # === Example 2: Using standard Ruby
50
+ #
51
+ # pn = "/usr/bin/ruby"
52
+ # size = File.size(pn) # 27662
53
+ # isdir = File.directory?(pn) # false
54
+ # dir = File.dirname(pn) # "/usr/bin"
55
+ # base = File.basename(pn) # "ruby"
56
+ # dir, base = File.split(pn) # ["/usr/bin", "ruby"]
57
+ # data = File.read(pn)
58
+ # File.open(pn) { |f| _ }
59
+ # File.foreach(pn) { |line| _ }
60
+ #
61
+ # === Example 3: Special features
62
+ #
63
+ # p1 = Pathname.new("/usr/lib") # Pathname:/usr/lib
64
+ # p2 = p1 + "ruby/1.8" # Pathname:/usr/lib/ruby/1.8
65
+ # p3 = p1.parent # Pathname:/usr
66
+ # p4 = p2.relative_path_from(p3) # Pathname:lib/ruby/1.8
67
+ # pwd = Pathname.pwd # Pathname:/home/gavin
68
+ # pwd.absolute? # true
69
+ # p5 = Pathname.new "." # Pathname:.
70
+ # p5 = p5 + "music/../articles" # Pathname:music/../articles
71
+ # p5.cleanpath # Pathname:articles
72
+ # p5.realpath # Pathname:/home/gavin/articles
73
+ # p5.children # [Pathname:/home/gavin/articles/linux, ...]
74
+ #
75
+ # == Breakdown of functionality
76
+ #
77
+ # === Core methods
78
+ #
79
+ # These methods are effectively manipulating a String, because that's
80
+ # all a path is. None of these access the file system except for
81
+ # #mountpoint?, #children, #each_child, #realdirpath and #realpath.
82
+ #
83
+ # - +
84
+ # - #join
85
+ # - #parent
86
+ # - #root?
87
+ # - #absolute?
88
+ # - #relative?
89
+ # - #relative_path_from
90
+ # - #each_filename
91
+ # - #cleanpath
92
+ # - #realpath
93
+ # - #realdirpath
94
+ # - #children
95
+ # - #each_child
96
+ # - #mountpoint?
97
+ #
98
+ # === File status predicate methods
99
+ #
100
+ # These methods are a facade for FileTest:
101
+ # - #blockdev?
102
+ # - #chardev?
103
+ # - #directory?
104
+ # - #executable?
105
+ # - #executable_real?
106
+ # - #exist?
107
+ # - #file?
108
+ # - #grpowned?
109
+ # - #owned?
110
+ # - #pipe?
111
+ # - #readable?
112
+ # - #world_readable?
113
+ # - #readable_real?
114
+ # - #setgid?
115
+ # - #setuid?
116
+ # - #size
117
+ # - #size?
118
+ # - #socket?
119
+ # - #sticky?
120
+ # - #symlink?
121
+ # - #writable?
122
+ # - #world_writable?
123
+ # - #writable_real?
124
+ # - #zero?
125
+ #
126
+ # === File property and manipulation methods
127
+ #
128
+ # These methods are a facade for File:
129
+ # - #each_line(*args, &block)
130
+ # - #read(*args)
131
+ # - #binread(*args)
132
+ # - #readlines(*args)
133
+ # - #sysopen(*args)
134
+ # - #write(*args)
135
+ # - #binwrite(*args)
136
+ # - #atime
137
+ # - #birthtime
138
+ # - #ctime
139
+ # - #mtime
140
+ # - #chmod(mode)
141
+ # - #lchmod(mode)
142
+ # - #chown(owner, group)
143
+ # - #lchown(owner, group)
144
+ # - #fnmatch(pattern, *args)
145
+ # - #fnmatch?(pattern, *args)
146
+ # - #ftype
147
+ # - #make_link(old)
148
+ # - #open(*args, &block)
149
+ # - #readlink
150
+ # - #rename(to)
151
+ # - #stat
152
+ # - #lstat
153
+ # - #make_symlink(old)
154
+ # - #truncate(length)
155
+ # - #utime(atime, mtime)
156
+ # - #lutime(atime, mtime)
157
+ # - #basename(*args)
158
+ # - #dirname
159
+ # - #extname
160
+ # - #expand_path(*args)
161
+ # - #split
162
+ #
163
+ # === Directory methods
164
+ #
165
+ # These methods are a facade for Dir:
166
+ # - Pathname.glob(*args)
167
+ # - Pathname.getwd / Pathname.pwd
168
+ # - #rmdir
169
+ # - #entries
170
+ # - #each_entry(&block)
171
+ # - #mkdir(*args)
172
+ # - #opendir(*args)
173
+ #
174
+ # === Utilities
175
+ #
176
+ # These methods are a mixture of Find, FileUtils, and others:
177
+ # - #find(&block)
178
+ # - #mkpath
179
+ # - #rmtree
180
+ # - #unlink / #delete
181
+ #
182
+ #
183
+ # == Method documentation
184
+ #
185
+ # As the above section shows, most of the methods in Pathname are facades. The
186
+ # documentation for these methods generally just says, for instance, "See
187
+ # FileTest.writable?", as you should be familiar with the original method
188
+ # anyway, and its documentation (e.g. through +ri+) will contain more
189
+ # information. In some cases, a brief description will follow.
190
+ #
191
+ class Pathname
192
+
193
+ # The version string.
194
+ VERSION = "0.5.0"
195
+
196
+ # :stopdoc:
197
+
198
+ if File::FNM_SYSCASE.nonzero?
199
+ # Avoid #zero? here because #casecmp can return nil.
200
+ private def same_paths?(a, b) a.casecmp(b) == 0 end
201
+ else
202
+ private def same_paths?(a, b) a == b end
203
+ end
204
+
205
+ attr_reader :path
206
+ protected :path
207
+
208
+ # :startdoc:
209
+
210
+ #
211
+ # Create a Pathname object from the given String (or String-like object).
212
+ # If +path+ contains a NUL character (<tt>\0</tt>), an ArgumentError is raised.
213
+ #
214
+ def initialize(path)
215
+ @path = File.path(path).dup
216
+ rescue TypeError => e
217
+ raise e.class, "Pathname.new requires a String, #to_path or #to_str", cause: nil
218
+ end
219
+
220
+ #
221
+ # Freze self.
222
+ #
223
+ def freeze
224
+ super
225
+ @path.freeze
226
+ self
227
+ end
228
+
229
+ #
230
+ # Compare this pathname with +other+. The comparison is string-based.
231
+ # Be aware that two different paths (<tt>foo.txt</tt> and <tt>./foo.txt</tt>)
232
+ # can refer to the same file.
233
+ #
234
+ def ==(other)
235
+ return false unless Pathname === other
236
+ other.path == @path
237
+ end
238
+ alias === ==
239
+ alias eql? ==
240
+
241
+ unless method_defined?(:<=>, false)
242
+ # Provides for comparing pathnames, case-sensitively.
243
+ def <=>(other)
244
+ return nil unless Pathname === other
245
+ @path.tr('/', "\0") <=> other.path.tr('/', "\0")
246
+ end
247
+ end
248
+
249
+ def hash # :nodoc:
250
+ @path.hash
251
+ end
252
+
253
+ # Return the path as a String.
254
+ def to_s
255
+ @path.dup
256
+ end
257
+
258
+ # to_path is implemented so Pathname objects are usable with File.open, etc.
259
+ alias to_path to_s
260
+
261
+ def inspect # :nodoc:
262
+ "#<#{self.class}:#{@path}>"
263
+ end
264
+
265
+ unless method_defined?(:sub, false)
266
+ # Return a pathname which is substituted by String#sub.
267
+ def sub(pattern, *args, **kwargs, &block)
268
+ if block
269
+ path = @path.sub(pattern, *args, **kwargs) {|*sub_args|
270
+ begin
271
+ old = Thread.current[:pathname_sub_matchdata]
272
+ Thread.current[:pathname_sub_matchdata] = $~
273
+ eval("$~ = Thread.current[:pathname_sub_matchdata]", block.binding)
274
+ ensure
275
+ Thread.current[:pathname_sub_matchdata] = old
276
+ end
277
+ yield(*sub_args)
278
+ }
279
+ else
280
+ path = @path.sub(pattern, *args, **kwargs)
281
+ end
282
+ self.class.new(path)
283
+ end
284
+ end
285
+
286
+ # Return a pathname with +repl+ added as a suffix to the basename.
287
+ #
288
+ # If self has no extension part, +repl+ is appended.
289
+ #
290
+ # Pathname.new('/usr/bin/shutdown').sub_ext('.rb')
291
+ # #=> #<Pathname:/usr/bin/shutdown.rb>
292
+ def sub_ext(repl)
293
+ ext = File.extname(@path)
294
+
295
+ # File.extname("foo.bar:stream") returns ".bar" on NTFS and not ".bar:stream"
296
+ # (see ruby_enc_find_extname()).
297
+ # The behavior of Pathname#sub_ext is to replace everything
298
+ # from the start of the extname until the end of the path with repl.
299
+ unless @path.end_with?(ext)
300
+ ext = @path[@path.rindex(ext)..]
301
+ end
302
+
303
+ self.class.new(@path.chomp(ext) + repl)
304
+ end
305
+
306
+ if File::ALT_SEPARATOR
307
+ # Separator list string.
308
+ SEPARATOR_LIST = Regexp.quote "#{File::ALT_SEPARATOR}#{File::SEPARATOR}"
309
+ # Regexp that matches a separator.
310
+ SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
311
+ else
312
+ SEPARATOR_LIST = Regexp.quote File::SEPARATOR
313
+ SEPARATOR_PAT = /#{SEPARATOR_LIST}/
314
+ end
315
+ SEPARATOR_LIST.freeze
316
+ SEPARATOR_PAT.freeze
317
+ private_constant :SEPARATOR_LIST, :SEPARATOR_LIST
318
+
319
+ if File.dirname('A:') == 'A:.' # DOSish drive letter
320
+ # Regexp that matches an absolute path.
321
+ ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/
322
+ else
323
+ ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/
324
+ end
325
+ ABSOLUTE_PATH.freeze
326
+ private_constant :ABSOLUTE_PATH
327
+
328
+ # :startdoc:
329
+
330
+ # Creates a full path, including any intermediate directories that don't yet
331
+ # exist.
332
+ #
333
+ # See FileUtils.mkpath and FileUtils.mkdir_p
334
+ def mkpath(mode: nil)
335
+ path = @path == '/' ? @path : @path.chomp('/')
336
+
337
+ stack = []
338
+ until File.directory?(path) || File.dirname(path) == path
339
+ stack.push path
340
+ path = File.dirname(path)
341
+ end
342
+
343
+ stack.reverse_each do |dir|
344
+ dir = dir == '/' ? dir : dir.chomp('/')
345
+ if mode
346
+ Dir.mkdir dir, mode
347
+ File.chmod mode, dir
348
+ else
349
+ Dir.mkdir dir
350
+ end
351
+ rescue SystemCallError
352
+ raise unless File.directory?(dir)
353
+ end
354
+
355
+ self
356
+ end
357
+
358
+ # chop_basename(path) -> [pre-basename, basename] or nil
359
+ def chop_basename(path) # :nodoc:
360
+ base = File.basename(path)
361
+ if /\A#{SEPARATOR_PAT}?\z/o.match?(base)
362
+ return nil
363
+ else
364
+ return path[0, path.rindex(base)], base
365
+ end
366
+ end
367
+ private :chop_basename
368
+
369
+ # split_names(path) -> prefix, [name, ...]
370
+ def split_names(path) # :nodoc:
371
+ names = []
372
+ while r = chop_basename(path)
373
+ path, basename = r
374
+ names.unshift basename
375
+ end
376
+ return path, names
377
+ end
378
+ private :split_names
379
+
380
+ def prepend_prefix(prefix, relpath) # :nodoc:
381
+ if relpath.empty?
382
+ File.dirname(prefix)
383
+ elsif SEPARATOR_PAT.match?(prefix)
384
+ prefix = File.dirname(prefix)
385
+ prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
386
+ prefix + relpath
387
+ else
388
+ prefix + relpath
389
+ end
390
+ end
391
+ private :prepend_prefix
392
+
393
+ # Returns clean pathname of +self+ with consecutive slashes and useless dots
394
+ # removed. The filesystem is not accessed.
395
+ #
396
+ # If +consider_symlink+ is +true+, then a more conservative algorithm is used
397
+ # to avoid breaking symbolic linkages. This may retain more +..+
398
+ # entries than absolutely necessary, but without accessing the filesystem,
399
+ # this can't be avoided.
400
+ #
401
+ # See Pathname#realpath.
402
+ #
403
+ def cleanpath(consider_symlink=false)
404
+ if consider_symlink
405
+ cleanpath_conservative
406
+ else
407
+ cleanpath_aggressive
408
+ end
409
+ end
410
+
411
+ #
412
+ # Clean the path simply by resolving and removing excess +.+ and +..+ entries.
413
+ # Nothing more, nothing less.
414
+ #
415
+ def cleanpath_aggressive # :nodoc:
416
+ path = @path
417
+ names = []
418
+ pre = path
419
+ while r = chop_basename(pre)
420
+ pre, base = r
421
+ case base
422
+ when '.'
423
+ when '..'
424
+ names.unshift base
425
+ else
426
+ if names[0] == '..'
427
+ names.shift
428
+ else
429
+ names.unshift base
430
+ end
431
+ end
432
+ end
433
+ pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
434
+ if SEPARATOR_PAT.match?(File.basename(pre))
435
+ names.shift while names[0] == '..'
436
+ end
437
+ self.class.new(prepend_prefix(pre, File.join(*names)))
438
+ end
439
+ private :cleanpath_aggressive
440
+
441
+ # has_trailing_separator?(path) -> bool
442
+ def has_trailing_separator?(path) # :nodoc:
443
+ if r = chop_basename(path)
444
+ pre, basename = r
445
+ pre.length + basename.length < path.length
446
+ else
447
+ false
448
+ end
449
+ end
450
+ private :has_trailing_separator?
451
+
452
+ # add_trailing_separator(path) -> path
453
+ def add_trailing_separator(path) # :nodoc:
454
+ if File.basename(path + 'a') == 'a'
455
+ path
456
+ else
457
+ File.join(path, "") # xxx: Is File.join is appropriate to add separator?
458
+ end
459
+ end
460
+ private :add_trailing_separator
461
+
462
+ def del_trailing_separator(path) # :nodoc:
463
+ if r = chop_basename(path)
464
+ pre, basename = r
465
+ pre + basename
466
+ elsif /#{SEPARATOR_PAT}+\z/o =~ path
467
+ $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
468
+ else
469
+ path
470
+ end
471
+ end
472
+ private :del_trailing_separator
473
+
474
+ def cleanpath_conservative # :nodoc:
475
+ path = @path
476
+ names = []
477
+ pre = path
478
+ while r = chop_basename(pre)
479
+ pre, base = r
480
+ names.unshift base if base != '.'
481
+ end
482
+ pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
483
+ if SEPARATOR_PAT.match?(File.basename(pre))
484
+ names.shift while names[0] == '..'
485
+ end
486
+ if names.empty?
487
+ self.class.new(File.dirname(pre))
488
+ else
489
+ if names.last != '..' && File.basename(path) == '.'
490
+ names << '.'
491
+ end
492
+ result = prepend_prefix(pre, File.join(*names))
493
+ if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
494
+ self.class.new(add_trailing_separator(result))
495
+ else
496
+ self.class.new(result)
497
+ end
498
+ end
499
+ end
500
+ private :cleanpath_conservative
501
+
502
+ # Returns the parent directory.
503
+ #
504
+ # This is same as <code>self + '..'</code>.
505
+ def parent
506
+ self + '..'
507
+ end
508
+
509
+ # Returns +true+ if +self+ points to a mountpoint.
510
+ def mountpoint?
511
+ begin
512
+ stat1 = self.lstat
513
+ stat2 = self.parent.lstat
514
+ stat1.dev != stat2.dev || stat1.ino == stat2.ino
515
+ rescue Errno::ENOENT
516
+ false
517
+ end
518
+ end
519
+
520
+ #
521
+ # Predicate method for root directories. Returns +true+ if the
522
+ # pathname consists of consecutive slashes.
523
+ #
524
+ # It doesn't access the filesystem. So it may return +false+ for some
525
+ # pathnames which points to roots such as <tt>/usr/..</tt>.
526
+ #
527
+ def root?
528
+ chop_basename(@path) == nil && SEPARATOR_PAT.match?(@path)
529
+ end
530
+
531
+ # Predicate method for testing whether a path is absolute.
532
+ #
533
+ # It returns +true+ if the pathname begins with a slash.
534
+ #
535
+ # p = Pathname.new('/im/sure')
536
+ # p.absolute?
537
+ # #=> true
538
+ #
539
+ # p = Pathname.new('not/so/sure')
540
+ # p.absolute?
541
+ # #=> false
542
+ def absolute?
543
+ ABSOLUTE_PATH.match? @path
544
+ end
545
+
546
+ # The opposite of Pathname#absolute?
547
+ #
548
+ # It returns +false+ if the pathname begins with a slash.
549
+ #
550
+ # p = Pathname.new('/im/sure')
551
+ # p.relative?
552
+ # #=> false
553
+ #
554
+ # p = Pathname.new('not/so/sure')
555
+ # p.relative?
556
+ # #=> true
557
+ def relative?
558
+ !absolute?
559
+ end
560
+
561
+ #
562
+ # Iterates over each component of the path.
563
+ #
564
+ # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
565
+ # # yields "usr", "bin", and "ruby".
566
+ #
567
+ # Returns an Enumerator if no block was given.
568
+ #
569
+ # enum = Pathname.new("/usr/bin/ruby").each_filename
570
+ # # ... do stuff ...
571
+ # enum.each { |e| ... }
572
+ # # yields "usr", "bin", and "ruby".
573
+ #
574
+ def each_filename # :yield: filename
575
+ return to_enum(__method__) unless block_given?
576
+ _, names = split_names(@path)
577
+ names.each {|filename| yield filename }
578
+ nil
579
+ end
580
+
581
+ # Iterates over and yields a new Pathname object
582
+ # for each element in the given path in descending order.
583
+ #
584
+ # Pathname.new('/path/to/some/file.rb').descend {|v| p v}
585
+ # #<Pathname:/>
586
+ # #<Pathname:/path>
587
+ # #<Pathname:/path/to>
588
+ # #<Pathname:/path/to/some>
589
+ # #<Pathname:/path/to/some/file.rb>
590
+ #
591
+ # Pathname.new('path/to/some/file.rb').descend {|v| p v}
592
+ # #<Pathname:path>
593
+ # #<Pathname:path/to>
594
+ # #<Pathname:path/to/some>
595
+ # #<Pathname:path/to/some/file.rb>
596
+ #
597
+ # Returns an Enumerator if no block was given.
598
+ #
599
+ # enum = Pathname.new("/usr/bin/ruby").descend
600
+ # # ... do stuff ...
601
+ # enum.each { |e| ... }
602
+ # # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby.
603
+ #
604
+ # It doesn't access the filesystem.
605
+ #
606
+ def descend
607
+ return to_enum(__method__) unless block_given?
608
+ vs = []
609
+ ascend {|v| vs << v }
610
+ vs.reverse_each {|v| yield v }
611
+ nil
612
+ end
613
+
614
+ # Iterates over and yields a new Pathname object
615
+ # for each element in the given path in ascending order.
616
+ #
617
+ # Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
618
+ # #<Pathname:/path/to/some/file.rb>
619
+ # #<Pathname:/path/to/some>
620
+ # #<Pathname:/path/to>
621
+ # #<Pathname:/path>
622
+ # #<Pathname:/>
623
+ #
624
+ # Pathname.new('path/to/some/file.rb').ascend {|v| p v}
625
+ # #<Pathname:path/to/some/file.rb>
626
+ # #<Pathname:path/to/some>
627
+ # #<Pathname:path/to>
628
+ # #<Pathname:path>
629
+ #
630
+ # Returns an Enumerator if no block was given.
631
+ #
632
+ # enum = Pathname.new("/usr/bin/ruby").ascend
633
+ # # ... do stuff ...
634
+ # enum.each { |e| ... }
635
+ # # yields Pathnames /usr/bin/ruby, /usr/bin, /usr, and /.
636
+ #
637
+ # It doesn't access the filesystem.
638
+ #
639
+ def ascend
640
+ return to_enum(__method__) unless block_given?
641
+ path = @path
642
+ yield self
643
+ while r = chop_basename(path)
644
+ path, = r
645
+ break if path.empty?
646
+ yield self.class.new(del_trailing_separator(path))
647
+ end
648
+ end
649
+
650
+ # call-seq:
651
+ # self + other -> new_pathname
652
+ #
653
+ # Returns a new \Pathname object;
654
+ # argument +other+ may be a string or another pathname.
655
+ #
656
+ # When +other+ specifies a relative path (see #relative?),
657
+ # it is combined with +self+ to form a new pathname:
658
+ #
659
+ # Pathname.new('/a/b') + 'c' # => #<Pathname:/a/b/c>
660
+ #
661
+ # Extra component separators (<tt>'/'</tt>) are removed:
662
+ #
663
+ # Pathname.new('/a/b/') + 'c' # => #<Pathname:/a/b/c>
664
+ #
665
+ # Extra current-directory components (<tt>'.'</tt>) are removed:
666
+ #
667
+ # Pathname.new('a') + '.' # => #<Pathname:a>
668
+ # Pathname.new('.') + 'a' # => #<Pathname:a>
669
+ # Pathname.new('.') + '.' # => #<Pathname:.>
670
+ #
671
+ # Parent-directory components (<tt>'..'</tt>) are:
672
+ #
673
+ # - Resolved, when possible:
674
+ #
675
+ # Pathname.new('a') + '..' # => #<Pathname:.>
676
+ # Pathname.new('a/b') + '..' # => #<Pathname:a>
677
+ # Pathname.new('/') + '../a' # => #<Pathname:/a>
678
+ # Pathname.new('a') + '../b' # => #<Pathname:b>
679
+ # Pathname.new('a/b') + '../c' # => #<Pathname:a/c>
680
+ # Pathname.new('a//b/c') + '../d//e' # => #<Pathname:a//b/d//e>
681
+ #
682
+ # - Removed, when not needed:
683
+ #
684
+ # Pathname.new('/') + '..' # => #<Pathname:/>
685
+ #
686
+ # - Retained, when needed:
687
+ #
688
+ # Pathname.new('..') + '..' # => #<Pathname:../..>
689
+ # Pathname.new('..') + '../a' # => #<Pathname:../../a>
690
+ #
691
+ # When +other+ specifies an absolute path (see #absolute?),
692
+ # equivalent to <tt>Pathname.new(other.to_s)</tt>:
693
+ #
694
+ # Pathname.new('/a') + '/b/c' # => #<Pathname:/b/c>
695
+ #
696
+ # Occurrences of <tt>'/'</tt>, <tt>'.'</tt>, and <tt>'..'</tt> are preserved:
697
+ #
698
+ # Pathname.new('/a') + '//b//c/./../d' # => #<Pathname://b//c/./../d>
699
+ #
700
+ # This method does not access the file system, so +other+ need not represent
701
+ # an existing (or even a valid) file or directory path:
702
+ #
703
+ # Pathname.new('/var') + 'nosuch:ever' # => #<Pathname:/var/nosuch:ever>
704
+ #
705
+ def +(other)
706
+ other = Pathname.new(other) unless Pathname === other
707
+ Pathname.new(plus(@path, other.path))
708
+ end
709
+ alias / +
710
+
711
+ def plus(path1, path2) # -> path # :nodoc:
712
+ prefix2 = path2
713
+ index_list2 = []
714
+ basename_list2 = []
715
+ while r2 = chop_basename(prefix2)
716
+ prefix2, basename2 = r2
717
+ index_list2.unshift prefix2.length
718
+ basename_list2.unshift basename2
719
+ end
720
+ return path2 if prefix2 != ''
721
+ prefix1 = path1
722
+ while true
723
+ while !basename_list2.empty? && basename_list2.first == '.'
724
+ index_list2.shift
725
+ basename_list2.shift
726
+ end
727
+ break unless r1 = chop_basename(prefix1)
728
+ prefix1, basename1 = r1
729
+ next if basename1 == '.'
730
+ if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
731
+ prefix1 = prefix1 + basename1
732
+ break
733
+ end
734
+ index_list2.shift
735
+ basename_list2.shift
736
+ end
737
+ r1 = chop_basename(prefix1)
738
+ if !r1 && (r1 = SEPARATOR_PAT.match?(File.basename(prefix1)))
739
+ while !basename_list2.empty? && basename_list2.first == '..'
740
+ index_list2.shift
741
+ basename_list2.shift
742
+ end
743
+ end
744
+ if !basename_list2.empty?
745
+ suffix2 = path2[index_list2.first..-1]
746
+ r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
747
+ else
748
+ r1 ? prefix1 : File.dirname(prefix1)
749
+ end
750
+ end
751
+ private :plus
752
+
753
+ #
754
+ # Joins the given pathnames onto +self+ to create a new Pathname object.
755
+ # This is effectively the same as using Pathname#+ to append +self+ and
756
+ # all arguments sequentially.
757
+ #
758
+ # path0 = Pathname.new("/usr") # Pathname:/usr
759
+ # path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby
760
+ # # is the same as
761
+ # path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby
762
+ # path0 == path1
763
+ # #=> true
764
+ #
765
+ def join(*args)
766
+ return self if args.empty?
767
+ result = args.pop
768
+ result = Pathname.new(result) unless Pathname === result
769
+ return result if result.absolute?
770
+ args.reverse_each {|arg|
771
+ arg = Pathname.new(arg) unless Pathname === arg
772
+ result = arg + result
773
+ return result if result.absolute?
774
+ }
775
+ self + result
776
+ end
777
+
778
+ #
779
+ # Returns the children of the directory (files and subdirectories, not
780
+ # recursive) as an array of Pathname objects.
781
+ #
782
+ # By default, the returned pathnames will have enough information to access
783
+ # the files. If you set +with_directory+ to +false+, then the returned
784
+ # pathnames will contain the filename only.
785
+ #
786
+ # For example:
787
+ # pn = Pathname("/usr/lib/ruby/1.8")
788
+ # pn.children
789
+ # # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
790
+ # Pathname:/usr/lib/ruby/1.8/Env.rb,
791
+ # Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
792
+ # pn.children(false)
793
+ # # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ]
794
+ #
795
+ # Note that the results never contain the entries +.+ and +..+ in
796
+ # the directory because they are not children.
797
+ #
798
+ def children(with_directory=true)
799
+ with_directory = false if @path == '.'
800
+ result = []
801
+ Dir.foreach(@path) {|e|
802
+ next if e == '.' || e == '..'
803
+ if with_directory
804
+ result << self.class.new(File.join(@path, e))
805
+ else
806
+ result << self.class.new(e)
807
+ end
808
+ }
809
+ result
810
+ end
811
+
812
+ # Iterates over the children of the directory
813
+ # (files and subdirectories, not recursive).
814
+ #
815
+ # It yields Pathname object for each child.
816
+ #
817
+ # By default, the yielded pathnames will have enough information to access
818
+ # the files.
819
+ #
820
+ # If you set +with_directory+ to +false+, then the returned pathnames will
821
+ # contain the filename only.
822
+ #
823
+ # Pathname("/usr/local").each_child {|f| p f }
824
+ # #=> #<Pathname:/usr/local/share>
825
+ # # #<Pathname:/usr/local/bin>
826
+ # # #<Pathname:/usr/local/games>
827
+ # # #<Pathname:/usr/local/lib>
828
+ # # #<Pathname:/usr/local/include>
829
+ # # #<Pathname:/usr/local/sbin>
830
+ # # #<Pathname:/usr/local/src>
831
+ # # #<Pathname:/usr/local/man>
832
+ #
833
+ # Pathname("/usr/local").each_child(false) {|f| p f }
834
+ # #=> #<Pathname:share>
835
+ # # #<Pathname:bin>
836
+ # # #<Pathname:games>
837
+ # # #<Pathname:lib>
838
+ # # #<Pathname:include>
839
+ # # #<Pathname:sbin>
840
+ # # #<Pathname:src>
841
+ # # #<Pathname:man>
842
+ #
843
+ # Note that the results never contain the entries +.+ and +..+ in
844
+ # the directory because they are not children.
845
+ #
846
+ # See Pathname#children
847
+ #
848
+ def each_child(with_directory=true, &b)
849
+ children(with_directory).each(&b)
850
+ end
851
+
852
+ #
853
+ # Returns a relative path from the given +base_directory+ to the receiver.
854
+ #
855
+ # If +self+ is absolute, then +base_directory+ must be absolute too.
856
+ #
857
+ # If +self+ is relative, then +base_directory+ must be relative too.
858
+ #
859
+ # This method doesn't access the filesystem. It assumes no symlinks.
860
+ #
861
+ # ArgumentError is raised when it cannot find a relative path.
862
+ #
863
+ # Note that this method does not handle situations where the case sensitivity
864
+ # of the filesystem in use differs from the operating system default.
865
+ #
866
+ def relative_path_from(base_directory)
867
+ base_directory = Pathname.new(base_directory) unless base_directory.is_a? Pathname
868
+ dest_directory = self.cleanpath.path
869
+ base_directory = base_directory.cleanpath.path
870
+ dest_prefix = dest_directory
871
+ dest_names = []
872
+ while r = chop_basename(dest_prefix)
873
+ dest_prefix, basename = r
874
+ dest_names.unshift basename if basename != '.'
875
+ end
876
+ base_prefix = base_directory
877
+ base_names = []
878
+ while r = chop_basename(base_prefix)
879
+ base_prefix, basename = r
880
+ base_names.unshift basename if basename != '.'
881
+ end
882
+ unless same_paths?(dest_prefix, base_prefix)
883
+ raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
884
+ end
885
+ while !dest_names.empty? &&
886
+ !base_names.empty? &&
887
+ same_paths?(dest_names.first, base_names.first)
888
+ dest_names.shift
889
+ base_names.shift
890
+ end
891
+ if base_names.include? '..'
892
+ raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
893
+ end
894
+ base_names.fill('..')
895
+ relpath_names = base_names + dest_names
896
+ if relpath_names.empty?
897
+ Pathname.new('.')
898
+ else
899
+ Pathname.new(File.join(*relpath_names))
900
+ end
901
+ end
902
+ end
903
+
904
+ class Pathname # * File *
905
+ #
906
+ # #each_line iterates over the line in the file. It yields a String object
907
+ # for each line.
908
+ #
909
+ # This method has existed since 1.8.1.
910
+ #
911
+ def each_line(...) # :yield: line
912
+ File.foreach(@path, ...)
913
+ end
914
+
915
+ # See <tt>File.read</tt>. Returns all data from the file, or the first +N+ bytes
916
+ # if specified.
917
+ def read(...) File.read(@path, ...) end
918
+
919
+ # See <tt>File.binread</tt>. Returns all the bytes from the file, or the first +N+
920
+ # if specified.
921
+ def binread(...) File.binread(@path, ...) end
922
+
923
+ # See <tt>File.readlines</tt>. Returns all the lines from the file.
924
+ def readlines(...) File.readlines(@path, ...) end
925
+
926
+ # See <tt>File.sysopen</tt>.
927
+ def sysopen(...) File.sysopen(@path, ...) end
928
+
929
+ # Writes +contents+ to the file. See <tt>File.write</tt>.
930
+ def write(...) File.write(@path, ...) end
931
+
932
+ # Writes +contents+ to the file, opening it in binary mode.
933
+ #
934
+ # See File.binwrite.
935
+ def binwrite(...) File.binwrite(@path, ...) end
936
+
937
+ # See <tt>File.atime</tt>. Returns last access time.
938
+ def atime() File.atime(@path) end
939
+
940
+ # Returns the birth time for the file.
941
+ # If the platform doesn't have birthtime, raises NotImplementedError.
942
+ #
943
+ # See File.birthtime.
944
+ def birthtime() File.birthtime(@path) end
945
+
946
+ # See <tt>File.ctime</tt>. Returns last (directory entry, not file) change time.
947
+ def ctime() File.ctime(@path) end
948
+
949
+ # See <tt>File.mtime</tt>. Returns last modification time.
950
+ def mtime() File.mtime(@path) end
951
+
952
+ # See <tt>File.chmod</tt>. Changes permissions.
953
+ def chmod(mode) File.chmod(mode, @path) end
954
+
955
+ # See <tt>File.lchmod</tt>.
956
+ def lchmod(mode) File.lchmod(mode, @path) end
957
+
958
+ # See <tt>File.chown</tt>. Change owner and group of file.
959
+ def chown(owner, group) File.chown(owner, group, @path) end
960
+
961
+ # See <tt>File.lchown</tt>.
962
+ def lchown(owner, group) File.lchown(owner, group, @path) end
963
+
964
+ # See <tt>File.fnmatch</tt>. Return +true+ if the receiver matches the given
965
+ # pattern.
966
+ def fnmatch(pattern, ...) File.fnmatch(pattern, @path, ...) end
967
+
968
+ # See <tt>File.fnmatch?</tt> (same as #fnmatch).
969
+ def fnmatch?(pattern, ...) File.fnmatch?(pattern, @path, ...) end
970
+
971
+ # See <tt>File.ftype</tt>. Returns "type" of file ("file", "directory",
972
+ # etc).
973
+ def ftype() File.ftype(@path) end
974
+
975
+ # See <tt>File.link</tt>. Creates a hard link.
976
+ def make_link(old) File.link(old, @path) end
977
+
978
+ # See <tt>File.open</tt>. Opens the file for reading or writing.
979
+ def open(...) # :yield: file
980
+ File.open(@path, ...)
981
+ end
982
+
983
+ # See <tt>File.readlink</tt>. Read symbolic link.
984
+ def readlink() self.class.new(File.readlink(@path)) end
985
+
986
+ # See <tt>File.rename</tt>. Rename the file.
987
+ def rename(to) File.rename(@path, to) end
988
+
989
+ # See <tt>File.stat</tt>. Returns a <tt>File::Stat</tt> object.
990
+ def stat() File.stat(@path) end
991
+
992
+ # See <tt>File.lstat</tt>.
993
+ def lstat() File.lstat(@path) end
994
+
995
+ # See <tt>File.symlink</tt>. Creates a symbolic link.
996
+ def make_symlink(old) File.symlink(old, @path) end
997
+
998
+ # See <tt>File.truncate</tt>. Truncate the file to +length+ bytes.
999
+ def truncate(length) File.truncate(@path, length) end
1000
+
1001
+ # See <tt>File.utime</tt>. Update the access and modification times.
1002
+ def utime(atime, mtime) File.utime(atime, mtime, @path) end
1003
+
1004
+ # Update the access and modification times of the file.
1005
+ #
1006
+ # Same as Pathname#utime, but does not follow symbolic links.
1007
+ #
1008
+ # See File.lutime.
1009
+ def lutime(atime, mtime) File.lutime(atime, mtime, @path) end
1010
+
1011
+ # See <tt>File.basename</tt>. Returns the last component of the path.
1012
+ def basename(...) self.class.new(File.basename(@path, ...)) end
1013
+
1014
+ # See <tt>File.dirname</tt>. Returns all but the last component of the path.
1015
+ def dirname() self.class.new(File.dirname(@path)) end
1016
+
1017
+ # See <tt>File.extname</tt>. Returns the file's extension.
1018
+ def extname() File.extname(@path) end
1019
+
1020
+ # See <tt>File.expand_path</tt>.
1021
+ def expand_path(...) self.class.new(File.expand_path(@path, ...)) end
1022
+
1023
+ # See <tt>File.split</tt>. Returns the #dirname and the #basename in an
1024
+ # Array.
1025
+ def split()
1026
+ array = File.split(@path)
1027
+ raise TypeError, 'wrong argument type nil (expected Array)' unless Array === array
1028
+ array.map {|f| self.class.new(f) }
1029
+ end
1030
+
1031
+ # Returns the real (absolute) pathname for +self+ in the actual filesystem.
1032
+ #
1033
+ # Does not contain symlinks or useless dots, +..+ and +.+.
1034
+ #
1035
+ # All components of the pathname must exist when this method is called.
1036
+ def realpath(...) self.class.new(File.realpath(@path, ...)) end
1037
+
1038
+ # Returns the real (absolute) pathname of +self+ in the actual filesystem.
1039
+ #
1040
+ # Does not contain symlinks or useless dots, +..+ and +.+.
1041
+ #
1042
+ # The last component of the real pathname can be nonexistent.
1043
+ def realdirpath(...) self.class.new(File.realdirpath(@path, ...)) end
1044
+ end
1045
+
1046
+
1047
+ class Pathname # * FileTest *
1048
+
1049
+ # See <tt>FileTest.blockdev?</tt>.
1050
+ def blockdev?() FileTest.blockdev?(@path) end
1051
+
1052
+ # See <tt>FileTest.chardev?</tt>.
1053
+ def chardev?() FileTest.chardev?(@path) end
1054
+
1055
+ # Tests the file is empty.
1056
+ #
1057
+ # See Dir#empty? and FileTest.empty?.
1058
+ def empty?
1059
+ if FileTest.directory?(@path)
1060
+ Dir.empty?(@path)
1061
+ else
1062
+ File.empty?(@path)
1063
+ end
1064
+ end
1065
+
1066
+ # See <tt>FileTest.executable?</tt>.
1067
+ def executable?() FileTest.executable?(@path) end
1068
+
1069
+ # See <tt>FileTest.executable_real?</tt>.
1070
+ def executable_real?() FileTest.executable_real?(@path) end
1071
+
1072
+ # See <tt>FileTest.exist?</tt>.
1073
+ def exist?() FileTest.exist?(@path) end
1074
+
1075
+ # See <tt>FileTest.grpowned?</tt>.
1076
+ def grpowned?() FileTest.grpowned?(@path) end
1077
+
1078
+ # See <tt>FileTest.directory?</tt>.
1079
+ def directory?() FileTest.directory?(@path) end
1080
+
1081
+ # See <tt>FileTest.file?</tt>.
1082
+ def file?() FileTest.file?(@path) end
1083
+
1084
+ # See <tt>FileTest.pipe?</tt>.
1085
+ def pipe?() FileTest.pipe?(@path) end
1086
+
1087
+ # See <tt>FileTest.socket?</tt>.
1088
+ def socket?() FileTest.socket?(@path) end
1089
+
1090
+ # See <tt>FileTest.owned?</tt>.
1091
+ def owned?() FileTest.owned?(@path) end
1092
+
1093
+ # See <tt>FileTest.readable?</tt>.
1094
+ def readable?() FileTest.readable?(@path) end
1095
+
1096
+ # See <tt>FileTest.world_readable?</tt>.
1097
+ def world_readable?() File.world_readable?(@path) end
1098
+
1099
+ # See <tt>FileTest.readable_real?</tt>.
1100
+ def readable_real?() FileTest.readable_real?(@path) end
1101
+
1102
+ # See <tt>FileTest.setuid?</tt>.
1103
+ def setuid?() FileTest.setuid?(@path) end
1104
+
1105
+ # See <tt>FileTest.setgid?</tt>.
1106
+ def setgid?() FileTest.setgid?(@path) end
1107
+
1108
+ # See <tt>FileTest.size</tt>.
1109
+ def size() FileTest.size(@path) end
1110
+
1111
+ # See <tt>FileTest.size?</tt>.
1112
+ def size?() FileTest.size?(@path) end
1113
+
1114
+ # See <tt>FileTest.sticky?</tt>.
1115
+ def sticky?() FileTest.sticky?(@path) end
1116
+
1117
+ # See <tt>FileTest.symlink?</tt>.
1118
+ def symlink?() FileTest.symlink?(@path) end
1119
+
1120
+ # See <tt>FileTest.writable?</tt>.
1121
+ def writable?() FileTest.writable?(@path) end
1122
+
1123
+ # See <tt>FileTest.world_writable?</tt>.
1124
+ def world_writable?() File.world_writable?(@path) end
1125
+
1126
+ # See <tt>FileTest.writable_real?</tt>.
1127
+ def writable_real?() FileTest.writable_real?(@path) end
1128
+
1129
+ # See <tt>FileTest.zero?</tt>.
1130
+ def zero?() FileTest.zero?(@path) end
1131
+ end
1132
+
1133
+
1134
+ class Pathname # * Dir *
1135
+ # See <tt>Dir.glob</tt>. Returns or yields Pathname objects.
1136
+ def Pathname.glob(*args, **kwargs) # :yield: pathname
1137
+ if block_given?
1138
+ Dir.glob(*args, **kwargs) {|f| yield self.new(f) }
1139
+ else
1140
+ Dir.glob(*args, **kwargs).map {|f| self.new(f) }
1141
+ end
1142
+ end
1143
+
1144
+ # Returns or yields Pathname objects.
1145
+ #
1146
+ # Pathname("ruby-2.4.2").glob("R*.md")
1147
+ # #=> [#<Pathname:ruby-2.4.2/README.md>, #<Pathname:ruby-2.4.2/README.ja.md>]
1148
+ #
1149
+ # See Dir.glob.
1150
+ # This method uses the +base+ keyword argument of Dir.glob.
1151
+ def glob(*args, **kwargs) # :yield: pathname
1152
+ if block_given?
1153
+ Dir.glob(*args, **kwargs, base: @path) {|f| yield self + f }
1154
+ else
1155
+ Dir.glob(*args, **kwargs, base: @path).map {|f| self + f }
1156
+ end
1157
+ end
1158
+
1159
+ # call-seq:
1160
+ # Pathname.getwd -> new_pathname
1161
+ #
1162
+ # Returns a new \Pathname object containing the path to the current working directory
1163
+ # (equivalent to <tt>Pathname.new(Dir.getwd)</tt>):
1164
+ #
1165
+ # Pathname.getwd # => #<Pathname:/home>
1166
+ #
1167
+ def Pathname.getwd() self.new(Dir.getwd) end
1168
+ class << self
1169
+ alias pwd getwd
1170
+ end
1171
+
1172
+ # Return the entries (files and subdirectories) in the directory, each as a
1173
+ # Pathname object.
1174
+ def entries() Dir.entries(@path).map {|f| self.class.new(f) } end
1175
+
1176
+ # Iterates over the entries (files and subdirectories) in the directory. It
1177
+ # yields a Pathname object for each entry.
1178
+ #
1179
+ # This method has existed since 1.8.1.
1180
+ def each_entry(&block) # :yield: pathname
1181
+ return to_enum(__method__) unless block_given?
1182
+ Dir.foreach(@path) {|f| yield self.class.new(f) }
1183
+ end
1184
+
1185
+ # See <tt>Dir.mkdir</tt>. Create the referenced directory.
1186
+ def mkdir(...) Dir.mkdir(@path, ...) end
1187
+
1188
+ # See <tt>Dir.rmdir</tt>. Remove the referenced directory.
1189
+ def rmdir() Dir.rmdir(@path) end
1190
+
1191
+ # See <tt>Dir.open</tt>.
1192
+ def opendir(&block) # :yield: dir
1193
+ Dir.open(@path, &block)
1194
+ end
1195
+ end
1196
+
1197
+ class Pathname # * mixed *
1198
+ # Removes a file or directory, using <tt>File.unlink</tt> or
1199
+ # <tt>Dir.unlink</tt> as necessary.
1200
+ def unlink()
1201
+ Dir.unlink @path
1202
+ rescue Errno::ENOTDIR
1203
+ File.unlink @path
1204
+ end
1205
+ alias delete unlink
1206
+ end
1207
+
1208
+ class Pathname
1209
+ undef =~ if Kernel.method_defined?(:=~)
1210
+ end
1211
+
1212
+ module Kernel
1213
+ # Creates a Pathname object.
1214
+ def Pathname(path) # :doc:
1215
+ return path if Pathname === path
1216
+ Pathname.new(path)
1217
+ end
1218
+ module_function :Pathname
1219
+ end