pathname2 1.7.2-universal-mingw32 → 1.7.3-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. checksums.yaml +14 -6
  2. data/CHANGES +151 -148
  3. data/MANIFEST +38 -38
  4. data/README +98 -98
  5. data/lib/pathname2.rb +1140 -1140
  6. data/pathname2.gemspec +36 -36
  7. data/test/test_pathname.rb +484 -484
  8. data/test/test_version.rb +13 -13
  9. metadata +23 -25
data/lib/pathname2.rb CHANGED
@@ -1,1140 +1,1140 @@
1
- # == Synopsis
2
- #
3
- # Pathname represents a path name on a filesystem. A Pathname can be
4
- # relative or absolute. It does not matter whether the path exists or not.
5
- #
6
- # All functionality from File, FileTest, and Dir is included, using a facade
7
- # pattern.
8
- #
9
- # This class works on both Unix and Windows, including UNC path names. Note
10
- # that forward slashes are converted to backslashes on Windows systems.
11
- #
12
- # == Usage
13
- #
14
- # require "pathname2"
15
- #
16
- # # Unix
17
- # path1 = Pathname.new("/foo/bar/baz")
18
- # path2 = Pathname.new("../zap")
19
- #
20
- # path1 + path2 # "/foo/bar/zap"
21
- # path1.dirname # "/foo/bar"
22
- #
23
- # # Windows
24
- # path1 = Pathname.new("C:\\foo\\bar\\baz")
25
- # path2 = Pathname.new("..\\zap")
26
- #
27
- # path1 + path2 # "C:\\foo\\bar\\zap"
28
- # path1.exists? # Does the path exist?
29
- #
30
- require 'facade'
31
- require 'fileutils'
32
- require 'pp'
33
-
34
- if File::ALT_SEPARATOR
35
- require 'ffi'
36
- class String
37
- # Convenience method for converting strings to UTF-16LE for wide character
38
- # functions that require it.
39
- def wincode
40
- if self.encoding.name != 'UTF-16LE'
41
- temp = self.dup
42
- (temp.tr(File::SEPARATOR, File::ALT_SEPARATOR) << 0.chr).encode('UTF-16LE')
43
- end
44
- end
45
- end
46
- end
47
-
48
- # You're mine now.
49
- Object.send(:remove_const, :Pathname) if defined?(Pathname)
50
-
51
- class Pathname < String
52
- class Error < StandardError; end
53
- extend Facade
54
-
55
- undef_method :pretty_print
56
-
57
- facade File, File.methods(false).map{ |m| m.to_sym } - [
58
- :chmod, :lchmod, :chown, :lchown, :dirname, :fnmatch, :fnmatch?,
59
- :link, :open, :realpath, :rename, :symlink, :truncate, :utime,
60
- :basename, :expand_path, :join
61
- ]
62
-
63
- facade Dir, Dir.methods(false).map{ |m| m.to_sym } - [
64
- :chdir, :entries, :glob, :foreach, :mkdir, :open
65
- ]
66
-
67
- private
68
-
69
- alias :_plus_ :+ # Used to prevent infinite loops in some cases
70
-
71
- if File::ALT_SEPARATOR
72
- extend FFI::Library
73
- ffi_lib :shlwapi
74
-
75
- attach_function :PathAppendW, [:pointer, :pointer], :bool
76
- attach_function :PathCanonicalizeW, [:pointer, :buffer_in], :bool
77
- attach_function :PathCreateFromUrlW, [:buffer_in, :pointer, :pointer, :ulong], :long
78
- attach_function :PathGetDriveNumberW, [:buffer_in], :int
79
- attach_function :PathIsRelativeW, [:buffer_in], :bool
80
- attach_function :PathIsRootW, [:buffer_in], :bool
81
- attach_function :PathIsUNCW, [:buffer_in], :bool
82
- attach_function :PathIsURLW, [:buffer_in], :bool
83
- attach_function :PathRemoveBackslashW, [:buffer_in], :pointer
84
- attach_function :PathStripToRootW, [:pointer], :bool
85
- attach_function :PathUndecorateW, [:pointer], :void
86
-
87
- ffi_lib :kernel32
88
-
89
- attach_function :GetLongPathNameW, [:buffer_in, :buffer_out, :ulong], :ulong
90
- attach_function :GetShortPathNameW, [:buffer_in, :pointer, :ulong], :ulong
91
- end
92
-
93
- public
94
-
95
- # The version of the pathname2 library
96
- VERSION = '1.7.2'
97
-
98
- # The maximum length of a path
99
- MAXPATH = 1024 unless defined? MAXPATH # Yes, I willfully violate POSIX
100
-
101
- # Returns the expanded path of the current working directory.
102
- #
103
- # Synonym for Pathname.new(Dir.pwd).
104
- #
105
- def self.pwd
106
- new(Dir.pwd)
107
- end
108
-
109
- class << self
110
- alias getwd pwd
111
- end
112
-
113
- # Creates and returns a new Pathname object.
114
- #
115
- # On platforms that define File::ALT_SEPARATOR, all forward slashes are
116
- # replaced with the value of File::ALT_SEPARATOR. On MS Windows, for
117
- # example, all forward slashes are replaced with backslashes.
118
- #
119
- # File URL's will be converted to Pathname objects, e.g. the file URL
120
- # "file:///C:/Documents%20and%20Settings" will become 'C:\Documents and Settings'.
121
- #
122
- # Examples:
123
- #
124
- # Pathname.new("/foo/bar/baz")
125
- # Pathname.new("foo")
126
- # Pathname.new("file:///foo/bar/baz")
127
- # Pathname.new("C:\\Documents and Settings\\snoopy")
128
- #
129
- def initialize(path)
130
- if path.length > MAXPATH
131
- msg = "string too long. maximum string length is " + MAXPATH.to_s
132
- raise ArgumentError, msg
133
- end
134
-
135
- @sep = File::ALT_SEPARATOR || File::SEPARATOR
136
- @win = File::ALT_SEPARATOR
137
-
138
- # Handle File URL's. The separate approach for Windows is necessary
139
- # because Ruby's URI class does not (currently) parse absolute file URL's
140
- # properly when they include a drive letter.
141
- if @win
142
- wpath = path.wincode
143
-
144
- if PathIsURLW(wpath)
145
- buf = FFI::MemoryPointer.new(:char, MAXPATH)
146
- len = FFI::MemoryPointer.new(:ulong)
147
- len.write_ulong(buf.size)
148
-
149
- if PathCreateFromUrlW(wpath, buf, len, 0) == 0
150
- path = buf.read_string(path.size * 2).tr(0.chr, '')
151
- else
152
- raise Error, "invalid file url: #{path}"
153
- end
154
- end
155
- else
156
- if path.index('file:///', 0)
157
- require 'uri'
158
- path = URI::Parser.new.unescape(path)[7..-1]
159
- end
160
- end
161
-
162
- # Convert forward slashes to backslashes on Windows
163
- path = path.tr(File::SEPARATOR, File::ALT_SEPARATOR) if @win
164
-
165
- super(path)
166
- end
167
-
168
- # Returns a real (absolute) pathname of +self+ in the actual filesystem.
169
- #
170
- # Unlike most Pathname methods, this one assumes that the path actually
171
- # exists on your filesystem. If it doesn't, an error is raised. If a
172
- # circular symlink is encountered a system error will be raised.
173
- #
174
- # Example:
175
- #
176
- # Dir.pwd # => /usr/local
177
- # File.exists?('foo') # => true
178
- # Pathname.new('foo').realpath # => /usr/local/foo
179
- #
180
- def realpath
181
- File.stat(self) # Check to ensure that the path exists
182
-
183
- if File.symlink?(self)
184
- file = self.dup
185
-
186
- while true
187
- file = File.join(File.dirname(file), File.readlink(file))
188
- break unless File.symlink?(file)
189
- end
190
-
191
- self.class.new(file).clean
192
- else
193
- self.class.new(Dir.pwd) + self
194
- end
195
- end
196
-
197
- # Returns the children of the directory, files and subdirectories, as an
198
- # array of Pathname objects. If you set +with_directory+ to +false+, then
199
- # the returned pathnames will contain the filename only.
200
- #
201
- # Note that the result never contain the entries '.' and '..' in the
202
- # the directory because they are not children. Also note that this method
203
- # is *not* recursive.
204
- #
205
- # Example:
206
- #
207
- # path = Pathname.new('/usr/bin')
208
- # path.children # => ['/usr/bin/ruby', '/usr/bin/perl', ...]
209
- # path.children(false) # => ['ruby', 'perl', ...]
210
- #
211
- def children(with_directory = true)
212
- with_directory = false if self == '.'
213
- result = []
214
- Dir.foreach(self) { |file|
215
- next if file == '.' || file == '..'
216
- if with_directory
217
- result << self.class.new(File.join(self, file))
218
- else
219
- result << self.class.new(file)
220
- end
221
- }
222
- result
223
- end
224
-
225
- # Windows only
226
- #
227
- # Removes the decoration from a path string. Non-destructive.
228
- #
229
- # Example:
230
- #
231
- # path = Pathname.new('C:\Path\File[5].txt')
232
- # path.undecorate # => C:\Path\File.txt.
233
- #
234
- def undecorate
235
- unless @win
236
- raise NotImplementedError, "not supported on this platform"
237
- end
238
-
239
- wpath = FFI::MemoryPointer.from_string(self.wincode)
240
-
241
- PathUndecorateW(wpath)
242
-
243
- self.class.new(wpath.read_string(wpath.size).split("\000\000").first.tr(0.chr, ''))
244
- end
245
-
246
- # Windows only
247
- #
248
- # Performs the substitution of Pathname#undecorate in place.
249
- #
250
- def undecorate!
251
- self.replace(undecorate)
252
- end
253
-
254
- # Windows only
255
- #
256
- # Returns the short path for a long path name.
257
- #
258
- # Example:
259
- #
260
- # path = Pathname.new('C:\Program Files\Java')
261
- # path.short_path # => C:\Progra~1\Java.
262
- #
263
- def short_path
264
- raise NotImplementedError, "not supported on this platform" unless @win
265
-
266
- buf = FFI::MemoryPointer.new(:char, MAXPATH)
267
- wpath = self.wincode
268
-
269
- size = GetShortPathNameW(wpath, buf, buf.size)
270
-
271
- raise SystemCallError.new('GetShortPathName', FFI.errno) if size == 0
272
-
273
- self.class.new(buf.read_bytes(size * 2).delete(0.chr))
274
- end
275
-
276
- # Windows only
277
- #
278
- # Returns the long path for a long path name.
279
- #
280
- # Example:
281
- #
282
- # path = Pathname.new('C:\Progra~1\Java')
283
- # path.long_path # => C:\Program Files\Java.
284
- #
285
- def long_path
286
- raise NotImplementedError, "not supported on this platform" unless @win
287
-
288
- buf = FFI::MemoryPointer.new(:char, MAXPATH)
289
- wpath = self.wincode
290
-
291
- size = GetLongPathNameW(wpath, buf, buf.size)
292
-
293
- raise SystemCallError.new('GetShortPathName', FFI.errno) if size == 0
294
-
295
- self.class.new(buf.read_bytes(size * 2).delete(0.chr))
296
- end
297
-
298
- # Removes all trailing slashes, if present. Non-destructive.
299
- #
300
- # Example:
301
- #
302
- # path = Pathname.new('/usr/local/')
303
- # path.pstrip # => '/usr/local'
304
- #
305
- def pstrip
306
- str = self.dup
307
- return str if str.empty?
308
-
309
- while ["/", "\\"].include?(str.to_s[-1].chr)
310
- str.strip!
311
- str.chop!
312
- end
313
-
314
- self.class.new(str)
315
- end
316
-
317
- # Performs the substitution of Pathname#pstrip in place.
318
- #
319
- def pstrip!
320
- self.replace(pstrip)
321
- end
322
-
323
- # Splits a pathname into strings based on the path separator.
324
- #
325
- # Examples:
326
- #
327
- # Pathname.new('/usr/local/bin').to_a # => ['usr', 'local', 'bin']
328
- # Pathname.new('C:\WINNT\Fonts').to_a # => ['C:', 'WINNT', 'Fonts']
329
- #
330
- def to_a
331
- # Split string by path separator
332
- if @win
333
- array = tr(File::SEPARATOR, File::ALT_SEPARATOR).split(@sep)
334
- else
335
- array = split(@sep)
336
- end
337
- array.delete("") # Remove empty elements
338
- array
339
- end
340
-
341
- # Yields each component of the path name to a block.
342
- #
343
- # Example:
344
- #
345
- # Pathname.new('/usr/local/bin').each{ |element|
346
- # puts "Element: #{element}"
347
- # }
348
- #
349
- # Yields 'usr', 'local', and 'bin', in turn
350
- #
351
- def each
352
- to_a.each{ |element| yield element }
353
- end
354
-
355
- # Returns the path component at +index+, up to +length+ components, joined
356
- # by the path separator. If the +index+ is a Range, then that is used
357
- # instead and the +length+ is ignored.
358
- #
359
- # Keep in mind that on MS Windows the drive letter is the first element.
360
- #
361
- # Examples:
362
- #
363
- # path = Pathname.new('/home/john/source/ruby')
364
- # path[0] # => 'home'
365
- # path[1] # => 'john'
366
- # path[0, 3] # => '/home/john/source'
367
- # path[0..1] # => '/home/john'
368
- #
369
- # path = Pathname.new('C:/Documents and Settings/John/Source/Ruby')
370
- # path[0] # => 'C:\'
371
- # path[1] # => 'Documents and Settings'
372
- # path[0, 3] # => 'C:\Documents and Settings\John'
373
- # path[0..1] # => 'C:\Documents and Settings'
374
- #
375
- def [](index, length=nil)
376
- if index.is_a?(Fixnum)
377
- if length
378
- path = File.join(to_a[index, length])
379
- else
380
- path = to_a[index]
381
- end
382
- elsif index.is_a?(Range)
383
- if length
384
- warn 'Length argument ignored'
385
- end
386
- path = File.join(to_a[index])
387
- else
388
- raise TypeError, "Only Fixnums and Ranges allowed as first argument"
389
- end
390
-
391
- if path && @win
392
- path = path.tr("/", "\\")
393
- end
394
-
395
- path
396
- end
397
-
398
- # Yields each component of the path, concatenating the next component on
399
- # each iteration as a new Pathname object, starting with the root path.
400
- #
401
- # Example:
402
- #
403
- # path = Pathname.new('/usr/local/bin')
404
- #
405
- # path.descend{ |name|
406
- # puts name
407
- # }
408
- #
409
- # First iteration => '/'
410
- # Second iteration => '/usr'
411
- # Third iteration => '/usr/local'
412
- # Fourth iteration => '/usr/local/bin'
413
- #
414
- def descend
415
- if root?
416
- yield root
417
- return
418
- end
419
-
420
- if @win
421
- path = unc? ? "#{root}\\" : ""
422
- else
423
- path = absolute? ? root : ""
424
- end
425
-
426
- # Yield the root directory if an absolute path (and not Windows)
427
- unless @win && !unc?
428
- yield root if absolute?
429
- end
430
-
431
- each{ |element|
432
- if @win && unc?
433
- next if root.to_a.include?(element)
434
- end
435
- path << element << @sep
436
- yield self.class.new(path.chop)
437
- }
438
- end
439
-
440
- # Yields the path, minus one component on each iteration, as a new
441
- # Pathname object, ending with the root path.
442
- #
443
- # Example:
444
- #
445
- # path = Pathname.new('/usr/local/bin')
446
- #
447
- # path.ascend{ |name|
448
- # puts name
449
- # }
450
- #
451
- # First iteration => '/usr/local/bin'
452
- # Second iteration => '/usr/local'
453
- # Third iteration => '/usr'
454
- # Fourth iteration => '/'
455
- #
456
- def ascend
457
- if root?
458
- yield root
459
- return
460
- end
461
-
462
- n = to_a.length
463
-
464
- while n > 0
465
- path = to_a[0..n-1].join(@sep)
466
- if absolute?
467
- if @win && unc?
468
- path = "\\\\" << path
469
- end
470
- unless @win
471
- path = root << path
472
- end
473
- end
474
-
475
- path = self.class.new(path)
476
- yield path
477
-
478
- if @win && unc?
479
- break if path.root?
480
- end
481
-
482
- n -= 1
483
- end
484
-
485
- # Yield the root directory if an absolute path (and not Windows)
486
- unless @win
487
- yield root if absolute?
488
- end
489
- end
490
-
491
- # Returns the root directory of the path, or '.' if there is no root
492
- # directory.
493
- #
494
- # On Unix, this means the '/' character. On Windows, this can refer
495
- # to the drive letter, or the server and share path if the path is a
496
- # UNC path.
497
- #
498
- # Examples:
499
- #
500
- # Pathname.new('/usr/local').root # => '/'
501
- # Pathname.new('lib').root # => '.'
502
- #
503
- # On MS Windows:
504
- #
505
- # Pathname.new('C:\WINNT').root # => 'C:'
506
- # Pathname.new('\\some\share\foo').root # => '\\some\share'
507
- #
508
- def root
509
- dir = "."
510
-
511
- if @win
512
- wpath = FFI::MemoryPointer.from_string(self.wincode)
513
- if PathStripToRootW(wpath)
514
- dir = wpath.read_string(wpath.size).split("\000\000").first.tr(0.chr, '')
515
- end
516
- else
517
- dir = "/" if self =~ /^\//
518
- end
519
-
520
- self.class.new(dir)
521
- end
522
-
523
- # Returns whether or not the path consists only of a root directory.
524
- #
525
- # Examples:
526
- #
527
- # Pathname.new('/').root? # => true
528
- # Pathname.new('/foo').root? # => false
529
- #
530
- def root?
531
- if @win
532
- PathIsRootW(self.wincode)
533
- else
534
- self == root
535
- end
536
- end
537
-
538
- # MS Windows only
539
- #
540
- # Determines if the string is a valid Universal Naming Convention (UNC)
541
- # for a server and share path.
542
- #
543
- # Examples:
544
- #
545
- # Pathname.new("\\\\foo\\bar").unc? # => true
546
- # Pathname.new('C:\Program Files').unc? # => false
547
- #
548
- def unc?
549
- raise NotImplementedError, "not supported on this platform" unless @win
550
- PathIsUNCW(self.wincode)
551
- end
552
-
553
- # MS Windows only
554
- #
555
- # Returns the drive number that corresponds to the root, or nil if not
556
- # applicable.
557
- #
558
- # Example:
559
- #
560
- # Pathname.new("C:\\foo").drive_number # => 2
561
- #
562
- def drive_number
563
- unless @win
564
- raise NotImplementedError, "not supported on this platform"
565
- end
566
-
567
- num = PathGetDriveNumberW(self.wincode)
568
- num >= 0 ? num : nil
569
- end
570
-
571
- # Compares two Pathname objects. Note that Pathnames may only be compared
572
- # against other Pathnames, not strings. Otherwise nil is returned.
573
- #
574
- # Example:
575
- #
576
- # path1 = Pathname.new('/usr/local')
577
- # path2 = Pathname.new('/usr/local')
578
- # path3 = Pathname.new('/usr/local/bin')
579
- #
580
- # path1 <=> path2 # => 0
581
- # path1 <=> path3 # => -1
582
- #
583
- def <=>(string)
584
- return nil unless string.kind_of?(Pathname)
585
- super
586
- end
587
-
588
- # Returns the parent directory of the given path.
589
- #
590
- # Example:
591
- #
592
- # Pathname.new('/usr/local/bin').parent # => '/usr/local'
593
- #
594
- def parent
595
- return self if root?
596
- self + ".." # Use our custom '+' method
597
- end
598
-
599
- # Returns a relative path from the argument to the receiver. If +self+
600
- # is absolute, the argument must be absolute too. If +self+ is relative,
601
- # the argument must be relative too. For relative paths, this method uses
602
- # an imaginary, common parent path.
603
- #
604
- # This method does not access the filesystem. It assumes no symlinks.
605
- # You should only compare directories against directories, or files against
606
- # files, or you may get unexpected results.
607
- #
608
- # Raises an ArgumentError if it cannot find a relative path.
609
- #
610
- # Examples:
611
- #
612
- # path = Pathname.new('/usr/local/bin')
613
- # path.relative_path_from('/usr/bin') # => "../local/bin"
614
- #
615
- # path = Pathname.new("C:\\WINNT\\Fonts")
616
- # path.relative_path_from("C:\\Program Files") # => "..\\WINNT\\Fonts"
617
- #
618
- def relative_path_from(base)
619
- base = self.class.new(base) unless base.kind_of?(Pathname)
620
-
621
- if self.absolute? != base.absolute?
622
- raise ArgumentError, "relative path between absolute and relative path"
623
- end
624
-
625
- return self.class.new(".") if self == base
626
- return self if base == "."
627
-
628
- # Because of the way the Windows version handles Pathname#clean, we need
629
- # a little extra help here.
630
- if @win
631
- if root != base.root
632
- msg = 'cannot determine relative paths from different root paths'
633
- raise ArgumentError, msg
634
- end
635
- if base == '..' && (self != '..' || self != '.')
636
- raise ArgumentError, "base directory may not contain '..'"
637
- end
638
- end
639
-
640
- dest_arr = self.clean.to_a
641
- base_arr = base.clean.to_a
642
- dest_arr.delete('.')
643
- base_arr.delete('.')
644
-
645
- # diff_arr = dest_arr - base_arr
646
-
647
- while !base_arr.empty? && !dest_arr.empty? && base_arr[0] == dest_arr[0]
648
- base_arr.shift
649
- dest_arr.shift
650
- end
651
-
652
- if base_arr.include?("..")
653
- raise ArgumentError, "base directory may not contain '..'"
654
- end
655
-
656
- base_arr.fill("..")
657
- rel_path = base_arr + dest_arr
658
-
659
- if rel_path.empty?
660
- self.class.new(".")
661
- else
662
- self.class.new(rel_path.join(@sep))
663
- end
664
- end
665
-
666
- # Adds two Pathname objects together, or a Pathname and a String. It
667
- # also automatically cleans the Pathname.
668
- #
669
- # Adding a root path to an existing path merely replaces the current
670
- # path. Adding '.' to an existing path does nothing.
671
- #
672
- # Example:
673
- #
674
- # path1 = '/foo/bar'
675
- # path2 = '../baz'
676
- # path1 + path2 # '/foo/baz'
677
- #
678
- def +(string)
679
- unless string.kind_of?(Pathname)
680
- string = self.class.new(string)
681
- end
682
-
683
- # Any path plus "." is the same directory
684
- return self if string == "."
685
- return string if self == "."
686
-
687
- # Use the builtin PathAppend() function if on Windows - much easier
688
- if @win
689
- path = FFI::MemoryPointer.new(:char, MAXPATH)
690
- path.write_string(self.dup.wincode)
691
- more = FFI::MemoryPointer.from_string(string.wincode)
692
-
693
- PathAppendW(path, more)
694
-
695
- path = path.read_string(path.size).split("\000\000").first.delete(0.chr)
696
-
697
- return self.class.new(path) # PathAppend cleans automatically
698
- end
699
-
700
- # If the string is an absolute directory, return it
701
- return string if string.absolute?
702
-
703
- array = to_a + string.to_a
704
- new_string = array.join(@sep)
705
-
706
- unless relative? || @win
707
- temp = @sep + new_string # Add root path back if needed
708
- new_string.replace(temp)
709
- end
710
-
711
- self.class.new(new_string).clean
712
- end
713
-
714
- alias :/ :+
715
-
716
- # Returns whether or not the path is an absolute path.
717
- #
718
- # Example:
719
- #
720
- # Pathname.new('/usr/bin').absolute? # => true
721
- # Pathname.new('usr').absolute? # => false
722
- #
723
- def absolute?
724
- !relative?
725
- end
726
-
727
- # Returns whether or not the path is a relative path.
728
- #
729
- # Example:
730
- #
731
- # Pathname.new('/usr/bin').relative? # => true
732
- # Pathname.new('usr').relative? # => false
733
- #
734
- def relative?
735
- if @win
736
- PathIsRelativeW(self.wincode)
737
- else
738
- root == "."
739
- end
740
- end
741
-
742
- # Removes unnecessary '.' paths and ellides '..' paths appropriately.
743
- # This method is non-destructive.
744
- #
745
- # Example:
746
- #
747
- # path = Pathname.new('/usr/./local/../bin')
748
- # path.clean # => '/usr/bin'
749
- #
750
- def clean
751
- return self if self.empty?
752
-
753
- if @win
754
- ptr = FFI::MemoryPointer.new(:char, MAXPATH)
755
- if PathCanonicalizeW(ptr, self.wincode)
756
- return self.class.new(ptr.read_string(ptr.size).delete(0.chr))
757
- else
758
- return self
759
- end
760
- end
761
-
762
- final = []
763
-
764
- to_a.each{ |element|
765
- next if element == "."
766
- final.push(element)
767
- if element == ".." && self != ".."
768
- 2.times{ final.pop }
769
- end
770
- }
771
-
772
- final = final.join(@sep)
773
- final = root._plus_(final) if root != "."
774
- final = "." if final.empty?
775
-
776
- self.class.new(final)
777
- end
778
-
779
- alias :cleanpath :clean
780
-
781
- # Identical to Pathname#clean, except that it modifies the receiver
782
- # in place.
783
- #
784
- def clean!
785
- self.replace(clean)
786
- end
787
-
788
- alias cleanpath! clean!
789
-
790
- # Similar to File.dirname, but this method allows you to specify the number
791
- # of levels up you wish to refer to.
792
- #
793
- # The default level is 1, i.e. it works the same as File.dirname. A level of
794
- # 0 will return the original path. A level equal to or greater than the
795
- # number of path elements will return the root path.
796
- #
797
- # A number less than 0 will raise an ArgumentError.
798
- #
799
- # Example:
800
- #
801
- # path = Pathname.new('/usr/local/bin/ruby')
802
- #
803
- # puts path.dirname # => /usr/local/bin
804
- # puts path.dirname(2) # => /usr/local
805
- # puts path.dirname(3) # => /usr
806
- # puts path.dirname(9) # => /
807
- #
808
- def dirname(level = 1)
809
- raise ArgumentError if level < 0
810
- local_path = self.dup
811
-
812
- level.times{ |n| local_path = File.dirname(local_path) }
813
- local_path
814
- end
815
-
816
- # Joins the given pathnames onto +self+ to create a new Pathname object.
817
- #
818
- # path = Pathname.new("C:/Users")
819
- # path = path.join("foo", "Downloads") # => C:/Users/foo/Downloads
820
- #
821
- def join(*args)
822
- args.unshift self
823
- result = args.pop
824
- result = self.class.new(result) unless result === self.class
825
- return result if result.absolute?
826
-
827
- args.reverse_each{ |path|
828
- path = self.class.new(path) unless path === self.class
829
- result = path + result
830
- break if result.absolute?
831
- }
832
-
833
- result
834
- end
835
-
836
- # A custom pretty printer
837
- def pretty_print(q)
838
- if File::ALT_SEPARATOR
839
- q.text(self.to_s.tr(File::SEPARATOR, File::ALT_SEPARATOR))
840
- else
841
- q.text(self.to_s)
842
- end
843
- end
844
-
845
- #-- Find facade
846
-
847
- # Pathname#find is an iterator to traverse a directory tree in a depth first
848
- # manner. It yields a Pathname for each file under the directory passed to
849
- # Pathname.new.
850
- #
851
- # Since it is implemented by the Find module, Find.prune can be used to
852
- # control the traverse.
853
- #
854
- # If +self+ is ".", yielded pathnames begin with a filename in the current
855
- # current directory, not ".".
856
- #
857
- def find(&block)
858
- require "find"
859
- if self == "."
860
- Find.find(self){ |f| yield self.class.new(f.sub(%r{\A\./}, '')) }
861
- else
862
- Find.find(self){ |f| yield self.class.new(f) }
863
- end
864
- end
865
-
866
- #-- IO methods not handled by facade
867
-
868
- # IO.foreach
869
- def foreach(*args, &block)
870
- IO.foreach(self, *args, &block)
871
- end
872
-
873
- # IO.read
874
- def read(*args)
875
- IO.read(self, *args)
876
- end
877
-
878
- # IO.readlines
879
- def readlines(*args)
880
- IO.readlines(self, *args)
881
- end
882
-
883
- # IO.sysopen
884
- def sysopen(*args)
885
- IO.sysopen(self, *args)
886
- end
887
-
888
- #-- Dir methods not handled by facade
889
-
890
- # Dir.glob
891
- #
892
- # :no-doc:
893
- # This differs from Tanaka's implementation in that it does a temporary
894
- # chdir to the path in question, then performs the glob.
895
- #
896
- def glob(*args)
897
- Dir.chdir(self){
898
- if block_given?
899
- Dir.glob(*args){ |file| yield self.class.new(file) }
900
- else
901
- Dir.glob(*args).map{ |file| self.class.new(file) }
902
- end
903
- }
904
- end
905
-
906
- # Dir.chdir
907
- def chdir(&block)
908
- Dir.chdir(self, &block)
909
- end
910
-
911
- # Dir.entries
912
- def entries
913
- Dir.entries(self).map{ |file| self.class.new(file) }
914
- end
915
-
916
- # Dir.mkdir
917
- def mkdir(*args)
918
- Dir.mkdir(self, *args)
919
- end
920
-
921
- # Dir.opendir
922
- def opendir(&block)
923
- Dir.open(self, &block)
924
- end
925
-
926
- #-- File methods not handled by facade
927
-
928
- # File.chmod
929
- def chmod(mode)
930
- File.chmod(mode, self)
931
- end
932
-
933
- # File.lchmod
934
- def lchmod(mode)
935
- File.lchmod(mode, self)
936
- end
937
-
938
- # File.chown
939
- def chown(owner, group)
940
- File.chown(owner, group, self)
941
- end
942
-
943
- # File.lchown
944
- def lchown(owner, group)
945
- File.lchown(owner, group, self)
946
- end
947
-
948
- # File.fnmatch
949
- def fnmatch(pattern, *args)
950
- File.fnmatch(pattern, self, *args)
951
- end
952
-
953
- # File.fnmatch?
954
- def fnmatch?(pattern, *args)
955
- File.fnmatch?(pattern, self, *args)
956
- end
957
-
958
- # File.link
959
- def link(old)
960
- File.link(old, self)
961
- end
962
-
963
- # File.open
964
- def open(*args, &block)
965
- File.open(self, *args, &block)
966
- end
967
-
968
- # File.rename
969
- def rename(name)
970
- File.rename(self, name)
971
- end
972
-
973
- # File.symlink
974
- def symlink(old)
975
- File.symlink(old, self)
976
- end
977
-
978
- # File.truncate
979
- def truncate(length)
980
- File.truncate(self, length)
981
- end
982
-
983
- # File.utime
984
- def utime(atime, mtime)
985
- File.utime(atime, mtime, self)
986
- end
987
-
988
- # File.basename
989
- def basename(*args)
990
- File.basename(self, *args)
991
- end
992
-
993
- # File.expand_path
994
- def expand_path(*args)
995
- self.class.new(File.expand_path(self, *args))
996
- end
997
-
998
- #--
999
- # FileUtils facade. Note that methods already covered by File and Dir
1000
- # are not defined here (pwd, mkdir, etc).
1001
- #++
1002
-
1003
- # FileUtils.cd
1004
- def cd(*args, &block)
1005
- FileUtils.cd(self, *args, &block)
1006
- end
1007
-
1008
- # FileUtils.mkdir_p
1009
- def mkdir_p(*args)
1010
- FileUtils.mkdir_p(self, *args)
1011
- end
1012
-
1013
- alias mkpath mkdir_p
1014
-
1015
- # FileUtils.ln
1016
- def ln(*args)
1017
- FileUtils.ln(self, *args)
1018
- end
1019
-
1020
- # FileUtils.ln_s
1021
- def ln_s(*args)
1022
- FileUtils.ln_s(self, *args)
1023
- end
1024
-
1025
- # FileUtils.ln_sf
1026
- def ln_sf(*args)
1027
- FileUtils.ln_sf(self, *args)
1028
- end
1029
-
1030
- # FileUtils.cp
1031
- def cp(*args)
1032
- FileUtils.cp(self, *args)
1033
- end
1034
-
1035
- # FileUtils.cp_r
1036
- def cp_r(*args)
1037
- FileUtils.cp_r(self, *args)
1038
- end
1039
-
1040
- # FileUtils.mv
1041
- def mv(*args)
1042
- FileUtils.mv(self, *args)
1043
- end
1044
-
1045
- # FileUtils.rm
1046
- def rm(*args)
1047
- FileUtils.rm(self, *args)
1048
- end
1049
-
1050
- alias remove rm
1051
-
1052
- # FileUtils.rm_f
1053
- def rm_f(*args)
1054
- FileUtils.rm_f(self, *args)
1055
- end
1056
-
1057
- # FileUtils.rm_r
1058
- def rm_r(*args)
1059
- FileUtils.rm_r(self, *args)
1060
- end
1061
-
1062
- # FileUtils.rm_rf
1063
- def rm_rf(*args)
1064
- FileUtils.rm_rf(self, *args)
1065
- end
1066
-
1067
- # FileUtils.rmtree
1068
- def rmtree(*args)
1069
- FileUtils.rmtree(self, *args)
1070
- end
1071
-
1072
- # FileUtils.install
1073
- def install(*args)
1074
- FileUtils.install(self, *args)
1075
- end
1076
-
1077
- # FileUtils.touch
1078
- def touch(*args)
1079
- FileUtils.touch(*args)
1080
- end
1081
-
1082
- # FileUtils.compare_file
1083
- def compare_file(file)
1084
- FileUtils.compare_file(self, file)
1085
- end
1086
-
1087
- # FileUtils.uptodate?
1088
- def uptodate?(*args)
1089
- FileUtils.uptodate(self, *args)
1090
- end
1091
-
1092
- # FileUtils.copy_file
1093
- def copy_file(*args)
1094
- FileUtils.copy_file(self, *args)
1095
- end
1096
-
1097
- # FileUtils.remove_dir
1098
- def remove_dir(*args)
1099
- FileUtils.remove_dir(self, *args)
1100
- end
1101
-
1102
- # FileUtils.remove_file
1103
- def remove_file(*args)
1104
- FileUtils.remove_dir(self, *args)
1105
- end
1106
-
1107
- # FileUtils.copy_entry
1108
- def copy_entry(*args)
1109
- FileUtils.copy_entry(self, *args)
1110
- end
1111
- end
1112
-
1113
- module Kernel
1114
- # Usage: pn{ path }
1115
- #
1116
- # A shortcut for Pathname.new
1117
- #
1118
- def pn
1119
- instance_eval{ Pathname.new(yield) }
1120
- end
1121
-
1122
- begin
1123
- remove_method(:Pathname)
1124
- rescue NoMethodError, NameError
1125
- # Do nothing, not defined.
1126
- end
1127
-
1128
- # Synonym for Pathname.new
1129
- #
1130
- def Pathname(path)
1131
- Pathname.new(path)
1132
- end
1133
- end
1134
-
1135
- class String
1136
- # Convert a string directly into a Pathname object.
1137
- def to_path
1138
- Pathname.new(self)
1139
- end
1140
- end
1
+ # == Synopsis
2
+ #
3
+ # Pathname represents a path name on a filesystem. A Pathname can be
4
+ # relative or absolute. It does not matter whether the path exists or not.
5
+ #
6
+ # All functionality from File, FileTest, and Dir is included, using a facade
7
+ # pattern.
8
+ #
9
+ # This class works on both Unix and Windows, including UNC path names. Note
10
+ # that forward slashes are converted to backslashes on Windows systems.
11
+ #
12
+ # == Usage
13
+ #
14
+ # require "pathname2"
15
+ #
16
+ # # Unix
17
+ # path1 = Pathname.new("/foo/bar/baz")
18
+ # path2 = Pathname.new("../zap")
19
+ #
20
+ # path1 + path2 # "/foo/bar/zap"
21
+ # path1.dirname # "/foo/bar"
22
+ #
23
+ # # Windows
24
+ # path1 = Pathname.new("C:\\foo\\bar\\baz")
25
+ # path2 = Pathname.new("..\\zap")
26
+ #
27
+ # path1 + path2 # "C:\\foo\\bar\\zap"
28
+ # path1.exists? # Does the path exist?
29
+ #
30
+ require 'facade'
31
+ require 'fileutils'
32
+ require 'pp'
33
+
34
+ if File::ALT_SEPARATOR
35
+ require 'ffi'
36
+ class String
37
+ # Convenience method for converting strings to UTF-16LE for wide character
38
+ # functions that require it.
39
+ def wincode
40
+ if self.encoding.name != 'UTF-16LE'
41
+ temp = self.dup
42
+ (temp.tr(File::SEPARATOR, File::ALT_SEPARATOR) << 0.chr).encode('UTF-16LE')
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ # You're mine now.
49
+ Object.send(:remove_const, :Pathname) if defined?(Pathname)
50
+
51
+ class Pathname < String
52
+ class Error < StandardError; end
53
+ extend Facade
54
+
55
+ undef_method :pretty_print
56
+
57
+ facade File, File.methods(false).map{ |m| m.to_sym } - [
58
+ :chmod, :lchmod, :chown, :lchown, :dirname, :fnmatch, :fnmatch?,
59
+ :link, :open, :realpath, :rename, :symlink, :truncate, :utime,
60
+ :basename, :expand_path, :join
61
+ ]
62
+
63
+ facade Dir, Dir.methods(false).map{ |m| m.to_sym } - [
64
+ :chdir, :entries, :glob, :foreach, :mkdir, :open
65
+ ]
66
+
67
+ private
68
+
69
+ alias :_plus_ :+ # Used to prevent infinite loops in some cases
70
+
71
+ if File::ALT_SEPARATOR
72
+ extend FFI::Library
73
+ ffi_lib :shlwapi
74
+
75
+ attach_function :PathAppendW, [:pointer, :pointer], :bool
76
+ attach_function :PathCanonicalizeW, [:pointer, :buffer_in], :bool
77
+ attach_function :PathCreateFromUrlW, [:buffer_in, :pointer, :pointer, :ulong], :long
78
+ attach_function :PathGetDriveNumberW, [:buffer_in], :int
79
+ attach_function :PathIsRelativeW, [:buffer_in], :bool
80
+ attach_function :PathIsRootW, [:buffer_in], :bool
81
+ attach_function :PathIsUNCW, [:buffer_in], :bool
82
+ attach_function :PathIsURLW, [:buffer_in], :bool
83
+ attach_function :PathRemoveBackslashW, [:buffer_in], :pointer
84
+ attach_function :PathStripToRootW, [:pointer], :bool
85
+ attach_function :PathUndecorateW, [:pointer], :void
86
+
87
+ ffi_lib :kernel32
88
+
89
+ attach_function :GetLongPathNameW, [:buffer_in, :buffer_out, :ulong], :ulong
90
+ attach_function :GetShortPathNameW, [:buffer_in, :pointer, :ulong], :ulong
91
+ end
92
+
93
+ public
94
+
95
+ # The version of the pathname2 library
96
+ VERSION = '1.7.3'
97
+
98
+ # The maximum length of a path
99
+ MAXPATH = 1024 unless defined? MAXPATH # Yes, I willfully violate POSIX
100
+
101
+ # Returns the expanded path of the current working directory.
102
+ #
103
+ # Synonym for Pathname.new(Dir.pwd).
104
+ #
105
+ def self.pwd
106
+ new(Dir.pwd)
107
+ end
108
+
109
+ class << self
110
+ alias getwd pwd
111
+ end
112
+
113
+ # Creates and returns a new Pathname object.
114
+ #
115
+ # On platforms that define File::ALT_SEPARATOR, all forward slashes are
116
+ # replaced with the value of File::ALT_SEPARATOR. On MS Windows, for
117
+ # example, all forward slashes are replaced with backslashes.
118
+ #
119
+ # File URL's will be converted to Pathname objects, e.g. the file URL
120
+ # "file:///C:/Documents%20and%20Settings" will become 'C:\Documents and Settings'.
121
+ #
122
+ # Examples:
123
+ #
124
+ # Pathname.new("/foo/bar/baz")
125
+ # Pathname.new("foo")
126
+ # Pathname.new("file:///foo/bar/baz")
127
+ # Pathname.new("C:\\Documents and Settings\\snoopy")
128
+ #
129
+ def initialize(path)
130
+ if path.length > MAXPATH
131
+ msg = "string too long. maximum string length is " + MAXPATH.to_s
132
+ raise ArgumentError, msg
133
+ end
134
+
135
+ @sep = File::ALT_SEPARATOR || File::SEPARATOR
136
+ @win = File::ALT_SEPARATOR
137
+
138
+ # Handle File URL's. The separate approach for Windows is necessary
139
+ # because Ruby's URI class does not (currently) parse absolute file URL's
140
+ # properly when they include a drive letter.
141
+ if @win
142
+ wpath = path.wincode
143
+
144
+ if PathIsURLW(wpath)
145
+ buf = FFI::MemoryPointer.new(:char, MAXPATH)
146
+ len = FFI::MemoryPointer.new(:ulong)
147
+ len.write_ulong(buf.size)
148
+
149
+ if PathCreateFromUrlW(wpath, buf, len, 0) == 0
150
+ path = buf.read_string(path.size * 2).tr(0.chr, '')
151
+ else
152
+ raise Error, "invalid file url: #{path}"
153
+ end
154
+ end
155
+ else
156
+ if path.index('file:///', 0)
157
+ require 'uri'
158
+ path = URI::Parser.new.unescape(path)[7..-1]
159
+ end
160
+ end
161
+
162
+ # Convert forward slashes to backslashes on Windows
163
+ path = path.tr(File::SEPARATOR, File::ALT_SEPARATOR) if @win
164
+
165
+ super(path)
166
+ end
167
+
168
+ # Returns a real (absolute) pathname of +self+ in the actual filesystem.
169
+ #
170
+ # Unlike most Pathname methods, this one assumes that the path actually
171
+ # exists on your filesystem. If it doesn't, an error is raised. If a
172
+ # circular symlink is encountered a system error will be raised.
173
+ #
174
+ # Example:
175
+ #
176
+ # Dir.pwd # => /usr/local
177
+ # File.exists?('foo') # => true
178
+ # Pathname.new('foo').realpath # => /usr/local/foo
179
+ #
180
+ def realpath
181
+ File.stat(self) # Check to ensure that the path exists
182
+
183
+ if File.symlink?(self)
184
+ file = self.dup
185
+
186
+ while true
187
+ file = File.join(File.dirname(file), File.readlink(file))
188
+ break unless File.symlink?(file)
189
+ end
190
+
191
+ self.class.new(file).clean
192
+ else
193
+ self.class.new(Dir.pwd) + self
194
+ end
195
+ end
196
+
197
+ # Returns the children of the directory, files and subdirectories, as an
198
+ # array of Pathname objects. If you set +with_directory+ to +false+, then
199
+ # the returned pathnames will contain the filename only.
200
+ #
201
+ # Note that the result never contain the entries '.' and '..' in the
202
+ # the directory because they are not children. Also note that this method
203
+ # is *not* recursive.
204
+ #
205
+ # Example:
206
+ #
207
+ # path = Pathname.new('/usr/bin')
208
+ # path.children # => ['/usr/bin/ruby', '/usr/bin/perl', ...]
209
+ # path.children(false) # => ['ruby', 'perl', ...]
210
+ #
211
+ def children(with_directory = true)
212
+ with_directory = false if self == '.'
213
+ result = []
214
+ Dir.foreach(self) { |file|
215
+ next if file == '.' || file == '..'
216
+ if with_directory
217
+ result << self.class.new(File.join(self, file))
218
+ else
219
+ result << self.class.new(file)
220
+ end
221
+ }
222
+ result
223
+ end
224
+
225
+ # Windows only
226
+ #
227
+ # Removes the decoration from a path string. Non-destructive.
228
+ #
229
+ # Example:
230
+ #
231
+ # path = Pathname.new('C:\Path\File[5].txt')
232
+ # path.undecorate # => C:\Path\File.txt.
233
+ #
234
+ def undecorate
235
+ unless @win
236
+ raise NotImplementedError, "not supported on this platform"
237
+ end
238
+
239
+ wpath = FFI::MemoryPointer.from_string(self.wincode)
240
+
241
+ PathUndecorateW(wpath)
242
+
243
+ self.class.new(wpath.read_string(wpath.size).split("\000\000").first.tr(0.chr, ''))
244
+ end
245
+
246
+ # Windows only
247
+ #
248
+ # Performs the substitution of Pathname#undecorate in place.
249
+ #
250
+ def undecorate!
251
+ self.replace(undecorate)
252
+ end
253
+
254
+ # Windows only
255
+ #
256
+ # Returns the short path for a long path name.
257
+ #
258
+ # Example:
259
+ #
260
+ # path = Pathname.new('C:\Program Files\Java')
261
+ # path.short_path # => C:\Progra~1\Java.
262
+ #
263
+ def short_path
264
+ raise NotImplementedError, "not supported on this platform" unless @win
265
+
266
+ buf = FFI::MemoryPointer.new(:char, MAXPATH)
267
+ wpath = self.wincode
268
+
269
+ size = GetShortPathNameW(wpath, buf, buf.size)
270
+
271
+ raise SystemCallError.new('GetShortPathName', FFI.errno) if size == 0
272
+
273
+ self.class.new(buf.read_bytes(size * 2).delete(0.chr))
274
+ end
275
+
276
+ # Windows only
277
+ #
278
+ # Returns the long path for a long path name.
279
+ #
280
+ # Example:
281
+ #
282
+ # path = Pathname.new('C:\Progra~1\Java')
283
+ # path.long_path # => C:\Program Files\Java.
284
+ #
285
+ def long_path
286
+ raise NotImplementedError, "not supported on this platform" unless @win
287
+
288
+ buf = FFI::MemoryPointer.new(:char, MAXPATH)
289
+ wpath = self.wincode
290
+
291
+ size = GetLongPathNameW(wpath, buf, buf.size)
292
+
293
+ raise SystemCallError.new('GetShortPathName', FFI.errno) if size == 0
294
+
295
+ self.class.new(buf.read_bytes(size * 2).delete(0.chr))
296
+ end
297
+
298
+ # Removes all trailing slashes, if present. Non-destructive.
299
+ #
300
+ # Example:
301
+ #
302
+ # path = Pathname.new('/usr/local/')
303
+ # path.pstrip # => '/usr/local'
304
+ #
305
+ def pstrip
306
+ str = self.dup
307
+ return str if str.empty?
308
+
309
+ while ["/", "\\"].include?(str.to_s[-1].chr)
310
+ str.strip!
311
+ str.chop!
312
+ end
313
+
314
+ self.class.new(str)
315
+ end
316
+
317
+ # Performs the substitution of Pathname#pstrip in place.
318
+ #
319
+ def pstrip!
320
+ self.replace(pstrip)
321
+ end
322
+
323
+ # Splits a pathname into strings based on the path separator.
324
+ #
325
+ # Examples:
326
+ #
327
+ # Pathname.new('/usr/local/bin').to_a # => ['usr', 'local', 'bin']
328
+ # Pathname.new('C:\WINNT\Fonts').to_a # => ['C:', 'WINNT', 'Fonts']
329
+ #
330
+ def to_a
331
+ # Split string by path separator
332
+ if @win
333
+ array = tr(File::SEPARATOR, File::ALT_SEPARATOR).split(@sep)
334
+ else
335
+ array = split(@sep)
336
+ end
337
+ array.delete("") # Remove empty elements
338
+ array
339
+ end
340
+
341
+ # Yields each component of the path name to a block.
342
+ #
343
+ # Example:
344
+ #
345
+ # Pathname.new('/usr/local/bin').each{ |element|
346
+ # puts "Element: #{element}"
347
+ # }
348
+ #
349
+ # Yields 'usr', 'local', and 'bin', in turn
350
+ #
351
+ def each
352
+ to_a.each{ |element| yield element }
353
+ end
354
+
355
+ # Returns the path component at +index+, up to +length+ components, joined
356
+ # by the path separator. If the +index+ is a Range, then that is used
357
+ # instead and the +length+ is ignored.
358
+ #
359
+ # Keep in mind that on MS Windows the drive letter is the first element.
360
+ #
361
+ # Examples:
362
+ #
363
+ # path = Pathname.new('/home/john/source/ruby')
364
+ # path[0] # => 'home'
365
+ # path[1] # => 'john'
366
+ # path[0, 3] # => '/home/john/source'
367
+ # path[0..1] # => '/home/john'
368
+ #
369
+ # path = Pathname.new('C:/Documents and Settings/John/Source/Ruby')
370
+ # path[0] # => 'C:\'
371
+ # path[1] # => 'Documents and Settings'
372
+ # path[0, 3] # => 'C:\Documents and Settings\John'
373
+ # path[0..1] # => 'C:\Documents and Settings'
374
+ #
375
+ def [](index, length=nil)
376
+ if index.is_a?(Fixnum)
377
+ if length
378
+ path = File.join(to_a[index, length])
379
+ else
380
+ path = to_a[index]
381
+ end
382
+ elsif index.is_a?(Range)
383
+ if length
384
+ warn 'Length argument ignored'
385
+ end
386
+ path = File.join(to_a[index])
387
+ else
388
+ raise TypeError, "Only Fixnums and Ranges allowed as first argument"
389
+ end
390
+
391
+ if path && @win
392
+ path = path.tr("/", "\\")
393
+ end
394
+
395
+ path
396
+ end
397
+
398
+ # Yields each component of the path, concatenating the next component on
399
+ # each iteration as a new Pathname object, starting with the root path.
400
+ #
401
+ # Example:
402
+ #
403
+ # path = Pathname.new('/usr/local/bin')
404
+ #
405
+ # path.descend{ |name|
406
+ # puts name
407
+ # }
408
+ #
409
+ # First iteration => '/'
410
+ # Second iteration => '/usr'
411
+ # Third iteration => '/usr/local'
412
+ # Fourth iteration => '/usr/local/bin'
413
+ #
414
+ def descend
415
+ if root?
416
+ yield root
417
+ return
418
+ end
419
+
420
+ if @win
421
+ path = unc? ? "#{root}\\" : ""
422
+ else
423
+ path = absolute? ? root : ""
424
+ end
425
+
426
+ # Yield the root directory if an absolute path (and not Windows)
427
+ unless @win && !unc?
428
+ yield root if absolute?
429
+ end
430
+
431
+ each{ |element|
432
+ if @win && unc?
433
+ next if root.to_a.include?(element)
434
+ end
435
+ path << element << @sep
436
+ yield self.class.new(path.chop)
437
+ }
438
+ end
439
+
440
+ # Yields the path, minus one component on each iteration, as a new
441
+ # Pathname object, ending with the root path.
442
+ #
443
+ # Example:
444
+ #
445
+ # path = Pathname.new('/usr/local/bin')
446
+ #
447
+ # path.ascend{ |name|
448
+ # puts name
449
+ # }
450
+ #
451
+ # First iteration => '/usr/local/bin'
452
+ # Second iteration => '/usr/local'
453
+ # Third iteration => '/usr'
454
+ # Fourth iteration => '/'
455
+ #
456
+ def ascend
457
+ if root?
458
+ yield root
459
+ return
460
+ end
461
+
462
+ n = to_a.length
463
+
464
+ while n > 0
465
+ path = to_a[0..n-1].join(@sep)
466
+ if absolute?
467
+ if @win && unc?
468
+ path = "\\\\" << path
469
+ end
470
+ unless @win
471
+ path = root << path
472
+ end
473
+ end
474
+
475
+ path = self.class.new(path)
476
+ yield path
477
+
478
+ if @win && unc?
479
+ break if path.root?
480
+ end
481
+
482
+ n -= 1
483
+ end
484
+
485
+ # Yield the root directory if an absolute path (and not Windows)
486
+ unless @win
487
+ yield root if absolute?
488
+ end
489
+ end
490
+
491
+ # Returns the root directory of the path, or '.' if there is no root
492
+ # directory.
493
+ #
494
+ # On Unix, this means the '/' character. On Windows, this can refer
495
+ # to the drive letter, or the server and share path if the path is a
496
+ # UNC path.
497
+ #
498
+ # Examples:
499
+ #
500
+ # Pathname.new('/usr/local').root # => '/'
501
+ # Pathname.new('lib').root # => '.'
502
+ #
503
+ # On MS Windows:
504
+ #
505
+ # Pathname.new('C:\WINNT').root # => 'C:'
506
+ # Pathname.new('\\some\share\foo').root # => '\\some\share'
507
+ #
508
+ def root
509
+ dir = "."
510
+
511
+ if @win
512
+ wpath = FFI::MemoryPointer.from_string(self.wincode)
513
+ if PathStripToRootW(wpath)
514
+ dir = wpath.read_string(wpath.size).split("\000\000").first.tr(0.chr, '')
515
+ end
516
+ else
517
+ dir = "/" if self =~ /^\//
518
+ end
519
+
520
+ self.class.new(dir)
521
+ end
522
+
523
+ # Returns whether or not the path consists only of a root directory.
524
+ #
525
+ # Examples:
526
+ #
527
+ # Pathname.new('/').root? # => true
528
+ # Pathname.new('/foo').root? # => false
529
+ #
530
+ def root?
531
+ if @win
532
+ PathIsRootW(self.wincode)
533
+ else
534
+ self == root
535
+ end
536
+ end
537
+
538
+ # MS Windows only
539
+ #
540
+ # Determines if the string is a valid Universal Naming Convention (UNC)
541
+ # for a server and share path.
542
+ #
543
+ # Examples:
544
+ #
545
+ # Pathname.new("\\\\foo\\bar").unc? # => true
546
+ # Pathname.new('C:\Program Files').unc? # => false
547
+ #
548
+ def unc?
549
+ raise NotImplementedError, "not supported on this platform" unless @win
550
+ PathIsUNCW(self.wincode)
551
+ end
552
+
553
+ # MS Windows only
554
+ #
555
+ # Returns the drive number that corresponds to the root, or nil if not
556
+ # applicable.
557
+ #
558
+ # Example:
559
+ #
560
+ # Pathname.new("C:\\foo").drive_number # => 2
561
+ #
562
+ def drive_number
563
+ unless @win
564
+ raise NotImplementedError, "not supported on this platform"
565
+ end
566
+
567
+ num = PathGetDriveNumberW(self.wincode)
568
+ num >= 0 ? num : nil
569
+ end
570
+
571
+ # Compares two Pathname objects. Note that Pathnames may only be compared
572
+ # against other Pathnames, not strings. Otherwise nil is returned.
573
+ #
574
+ # Example:
575
+ #
576
+ # path1 = Pathname.new('/usr/local')
577
+ # path2 = Pathname.new('/usr/local')
578
+ # path3 = Pathname.new('/usr/local/bin')
579
+ #
580
+ # path1 <=> path2 # => 0
581
+ # path1 <=> path3 # => -1
582
+ #
583
+ def <=>(string)
584
+ return nil unless string.kind_of?(Pathname)
585
+ super
586
+ end
587
+
588
+ # Returns the parent directory of the given path.
589
+ #
590
+ # Example:
591
+ #
592
+ # Pathname.new('/usr/local/bin').parent # => '/usr/local'
593
+ #
594
+ def parent
595
+ return self if root?
596
+ self + ".." # Use our custom '+' method
597
+ end
598
+
599
+ # Returns a relative path from the argument to the receiver. If +self+
600
+ # is absolute, the argument must be absolute too. If +self+ is relative,
601
+ # the argument must be relative too. For relative paths, this method uses
602
+ # an imaginary, common parent path.
603
+ #
604
+ # This method does not access the filesystem. It assumes no symlinks.
605
+ # You should only compare directories against directories, or files against
606
+ # files, or you may get unexpected results.
607
+ #
608
+ # Raises an ArgumentError if it cannot find a relative path.
609
+ #
610
+ # Examples:
611
+ #
612
+ # path = Pathname.new('/usr/local/bin')
613
+ # path.relative_path_from('/usr/bin') # => "../local/bin"
614
+ #
615
+ # path = Pathname.new("C:\\WINNT\\Fonts")
616
+ # path.relative_path_from("C:\\Program Files") # => "..\\WINNT\\Fonts"
617
+ #
618
+ def relative_path_from(base)
619
+ base = self.class.new(base) unless base.kind_of?(Pathname)
620
+
621
+ if self.absolute? != base.absolute?
622
+ raise ArgumentError, "relative path between absolute and relative path"
623
+ end
624
+
625
+ return self.class.new(".") if self == base
626
+ return self if base == "."
627
+
628
+ # Because of the way the Windows version handles Pathname#clean, we need
629
+ # a little extra help here.
630
+ if @win
631
+ if root != base.root
632
+ msg = 'cannot determine relative paths from different root paths'
633
+ raise ArgumentError, msg
634
+ end
635
+ if base == '..' && (self != '..' || self != '.')
636
+ raise ArgumentError, "base directory may not contain '..'"
637
+ end
638
+ end
639
+
640
+ dest_arr = self.clean.to_a
641
+ base_arr = base.clean.to_a
642
+ dest_arr.delete('.')
643
+ base_arr.delete('.')
644
+
645
+ # diff_arr = dest_arr - base_arr
646
+
647
+ while !base_arr.empty? && !dest_arr.empty? && base_arr[0] == dest_arr[0]
648
+ base_arr.shift
649
+ dest_arr.shift
650
+ end
651
+
652
+ if base_arr.include?("..")
653
+ raise ArgumentError, "base directory may not contain '..'"
654
+ end
655
+
656
+ base_arr.fill("..")
657
+ rel_path = base_arr + dest_arr
658
+
659
+ if rel_path.empty?
660
+ self.class.new(".")
661
+ else
662
+ self.class.new(rel_path.join(@sep))
663
+ end
664
+ end
665
+
666
+ # Adds two Pathname objects together, or a Pathname and a String. It
667
+ # also automatically cleans the Pathname.
668
+ #
669
+ # Adding a root path to an existing path merely replaces the current
670
+ # path. Adding '.' to an existing path does nothing.
671
+ #
672
+ # Example:
673
+ #
674
+ # path1 = '/foo/bar'
675
+ # path2 = '../baz'
676
+ # path1 + path2 # '/foo/baz'
677
+ #
678
+ def +(string)
679
+ unless string.kind_of?(Pathname)
680
+ string = self.class.new(string)
681
+ end
682
+
683
+ # Any path plus "." is the same directory
684
+ return self if string == "."
685
+ return string if self == "."
686
+
687
+ # Use the builtin PathAppend() function if on Windows - much easier
688
+ if @win
689
+ path = FFI::MemoryPointer.new(:char, MAXPATH)
690
+ path.write_string(self.dup.wincode)
691
+ more = FFI::MemoryPointer.from_string(string.wincode)
692
+
693
+ PathAppendW(path, more)
694
+
695
+ path = path.read_string(path.size).split("\000\000").first.delete(0.chr)
696
+
697
+ return self.class.new(path) # PathAppend cleans automatically
698
+ end
699
+
700
+ # If the string is an absolute directory, return it
701
+ return string if string.absolute?
702
+
703
+ array = to_a + string.to_a
704
+ new_string = array.join(@sep)
705
+
706
+ unless relative? || @win
707
+ temp = @sep + new_string # Add root path back if needed
708
+ new_string.replace(temp)
709
+ end
710
+
711
+ self.class.new(new_string).clean
712
+ end
713
+
714
+ alias :/ :+
715
+
716
+ # Returns whether or not the path is an absolute path.
717
+ #
718
+ # Example:
719
+ #
720
+ # Pathname.new('/usr/bin').absolute? # => true
721
+ # Pathname.new('usr').absolute? # => false
722
+ #
723
+ def absolute?
724
+ !relative?
725
+ end
726
+
727
+ # Returns whether or not the path is a relative path.
728
+ #
729
+ # Example:
730
+ #
731
+ # Pathname.new('/usr/bin').relative? # => true
732
+ # Pathname.new('usr').relative? # => false
733
+ #
734
+ def relative?
735
+ if @win
736
+ PathIsRelativeW(self.wincode)
737
+ else
738
+ root == "."
739
+ end
740
+ end
741
+
742
+ # Removes unnecessary '.' paths and ellides '..' paths appropriately.
743
+ # This method is non-destructive.
744
+ #
745
+ # Example:
746
+ #
747
+ # path = Pathname.new('/usr/./local/../bin')
748
+ # path.clean # => '/usr/bin'
749
+ #
750
+ def clean
751
+ return self if self.empty?
752
+
753
+ if @win
754
+ ptr = FFI::MemoryPointer.new(:char, MAXPATH)
755
+ if PathCanonicalizeW(ptr, self.wincode)
756
+ return self.class.new(ptr.read_string(ptr.size).delete(0.chr))
757
+ else
758
+ return self
759
+ end
760
+ end
761
+
762
+ final = []
763
+
764
+ to_a.each{ |element|
765
+ next if element == "."
766
+ final.push(element)
767
+ if element == ".." && self != ".."
768
+ 2.times{ final.pop }
769
+ end
770
+ }
771
+
772
+ final = final.join(@sep)
773
+ final = root._plus_(final) if root != "."
774
+ final = "." if final.empty?
775
+
776
+ self.class.new(final)
777
+ end
778
+
779
+ alias :cleanpath :clean
780
+
781
+ # Identical to Pathname#clean, except that it modifies the receiver
782
+ # in place.
783
+ #
784
+ def clean!
785
+ self.replace(clean)
786
+ end
787
+
788
+ alias cleanpath! clean!
789
+
790
+ # Similar to File.dirname, but this method allows you to specify the number
791
+ # of levels up you wish to refer to.
792
+ #
793
+ # The default level is 1, i.e. it works the same as File.dirname. A level of
794
+ # 0 will return the original path. A level equal to or greater than the
795
+ # number of path elements will return the root path.
796
+ #
797
+ # A number less than 0 will raise an ArgumentError.
798
+ #
799
+ # Example:
800
+ #
801
+ # path = Pathname.new('/usr/local/bin/ruby')
802
+ #
803
+ # puts path.dirname # => /usr/local/bin
804
+ # puts path.dirname(2) # => /usr/local
805
+ # puts path.dirname(3) # => /usr
806
+ # puts path.dirname(9) # => /
807
+ #
808
+ def dirname(level = 1)
809
+ raise ArgumentError if level < 0
810
+ local_path = self.dup
811
+
812
+ level.times{ |n| local_path = File.dirname(local_path) }
813
+ self.class.new(local_path)
814
+ end
815
+
816
+ # Joins the given pathnames onto +self+ to create a new Pathname object.
817
+ #
818
+ # path = Pathname.new("C:/Users")
819
+ # path = path.join("foo", "Downloads") # => C:/Users/foo/Downloads
820
+ #
821
+ def join(*args)
822
+ args.unshift self
823
+ result = args.pop
824
+ result = self.class.new(result) unless result === self.class
825
+ return result if result.absolute?
826
+
827
+ args.reverse_each{ |path|
828
+ path = self.class.new(path) unless path === self.class
829
+ result = path + result
830
+ break if result.absolute?
831
+ }
832
+
833
+ result
834
+ end
835
+
836
+ # A custom pretty printer
837
+ def pretty_print(q)
838
+ if File::ALT_SEPARATOR
839
+ q.text(self.to_s.tr(File::SEPARATOR, File::ALT_SEPARATOR))
840
+ else
841
+ q.text(self.to_s)
842
+ end
843
+ end
844
+
845
+ #-- Find facade
846
+
847
+ # Pathname#find is an iterator to traverse a directory tree in a depth first
848
+ # manner. It yields a Pathname for each file under the directory passed to
849
+ # Pathname.new.
850
+ #
851
+ # Since it is implemented by the Find module, Find.prune can be used to
852
+ # control the traverse.
853
+ #
854
+ # If +self+ is ".", yielded pathnames begin with a filename in the current
855
+ # current directory, not ".".
856
+ #
857
+ def find(&block)
858
+ require "find"
859
+ if self == "."
860
+ Find.find(self){ |f| yield self.class.new(f.sub(%r{\A\./}, '')) }
861
+ else
862
+ Find.find(self){ |f| yield self.class.new(f) }
863
+ end
864
+ end
865
+
866
+ #-- IO methods not handled by facade
867
+
868
+ # IO.foreach
869
+ def foreach(*args, &block)
870
+ IO.foreach(self, *args, &block)
871
+ end
872
+
873
+ # IO.read
874
+ def read(*args)
875
+ IO.read(self, *args)
876
+ end
877
+
878
+ # IO.readlines
879
+ def readlines(*args)
880
+ IO.readlines(self, *args)
881
+ end
882
+
883
+ # IO.sysopen
884
+ def sysopen(*args)
885
+ IO.sysopen(self, *args)
886
+ end
887
+
888
+ #-- Dir methods not handled by facade
889
+
890
+ # Dir.glob
891
+ #
892
+ # :no-doc:
893
+ # This differs from Tanaka's implementation in that it does a temporary
894
+ # chdir to the path in question, then performs the glob.
895
+ #
896
+ def glob(*args)
897
+ Dir.chdir(self){
898
+ if block_given?
899
+ Dir.glob(*args){ |file| yield self.class.new(file) }
900
+ else
901
+ Dir.glob(*args).map{ |file| self.class.new(file) }
902
+ end
903
+ }
904
+ end
905
+
906
+ # Dir.chdir
907
+ def chdir(&block)
908
+ Dir.chdir(self, &block)
909
+ end
910
+
911
+ # Dir.entries
912
+ def entries
913
+ Dir.entries(self).map{ |file| self.class.new(file) }
914
+ end
915
+
916
+ # Dir.mkdir
917
+ def mkdir(*args)
918
+ Dir.mkdir(self, *args)
919
+ end
920
+
921
+ # Dir.opendir
922
+ def opendir(&block)
923
+ Dir.open(self, &block)
924
+ end
925
+
926
+ #-- File methods not handled by facade
927
+
928
+ # File.chmod
929
+ def chmod(mode)
930
+ File.chmod(mode, self)
931
+ end
932
+
933
+ # File.lchmod
934
+ def lchmod(mode)
935
+ File.lchmod(mode, self)
936
+ end
937
+
938
+ # File.chown
939
+ def chown(owner, group)
940
+ File.chown(owner, group, self)
941
+ end
942
+
943
+ # File.lchown
944
+ def lchown(owner, group)
945
+ File.lchown(owner, group, self)
946
+ end
947
+
948
+ # File.fnmatch
949
+ def fnmatch(pattern, *args)
950
+ File.fnmatch(pattern, self, *args)
951
+ end
952
+
953
+ # File.fnmatch?
954
+ def fnmatch?(pattern, *args)
955
+ File.fnmatch?(pattern, self, *args)
956
+ end
957
+
958
+ # File.link
959
+ def link(old)
960
+ File.link(old, self)
961
+ end
962
+
963
+ # File.open
964
+ def open(*args, &block)
965
+ File.open(self, *args, &block)
966
+ end
967
+
968
+ # File.rename
969
+ def rename(name)
970
+ File.rename(self, name)
971
+ end
972
+
973
+ # File.symlink
974
+ def symlink(old)
975
+ File.symlink(old, self)
976
+ end
977
+
978
+ # File.truncate
979
+ def truncate(length)
980
+ File.truncate(self, length)
981
+ end
982
+
983
+ # File.utime
984
+ def utime(atime, mtime)
985
+ File.utime(atime, mtime, self)
986
+ end
987
+
988
+ # File.basename
989
+ def basename(*args)
990
+ self.class.new(File.basename(self, *args))
991
+ end
992
+
993
+ # File.expand_path
994
+ def expand_path(*args)
995
+ self.class.new(File.expand_path(self, *args))
996
+ end
997
+
998
+ #--
999
+ # FileUtils facade. Note that methods already covered by File and Dir
1000
+ # are not defined here (pwd, mkdir, etc).
1001
+ #++
1002
+
1003
+ # FileUtils.cd
1004
+ def cd(*args, &block)
1005
+ FileUtils.cd(self, *args, &block)
1006
+ end
1007
+
1008
+ # FileUtils.mkdir_p
1009
+ def mkdir_p(*args)
1010
+ FileUtils.mkdir_p(self, *args)
1011
+ end
1012
+
1013
+ alias mkpath mkdir_p
1014
+
1015
+ # FileUtils.ln
1016
+ def ln(*args)
1017
+ FileUtils.ln(self, *args)
1018
+ end
1019
+
1020
+ # FileUtils.ln_s
1021
+ def ln_s(*args)
1022
+ FileUtils.ln_s(self, *args)
1023
+ end
1024
+
1025
+ # FileUtils.ln_sf
1026
+ def ln_sf(*args)
1027
+ FileUtils.ln_sf(self, *args)
1028
+ end
1029
+
1030
+ # FileUtils.cp
1031
+ def cp(*args)
1032
+ FileUtils.cp(self, *args)
1033
+ end
1034
+
1035
+ # FileUtils.cp_r
1036
+ def cp_r(*args)
1037
+ FileUtils.cp_r(self, *args)
1038
+ end
1039
+
1040
+ # FileUtils.mv
1041
+ def mv(*args)
1042
+ FileUtils.mv(self, *args)
1043
+ end
1044
+
1045
+ # FileUtils.rm
1046
+ def rm(*args)
1047
+ FileUtils.rm(self, *args)
1048
+ end
1049
+
1050
+ alias remove rm
1051
+
1052
+ # FileUtils.rm_f
1053
+ def rm_f(*args)
1054
+ FileUtils.rm_f(self, *args)
1055
+ end
1056
+
1057
+ # FileUtils.rm_r
1058
+ def rm_r(*args)
1059
+ FileUtils.rm_r(self, *args)
1060
+ end
1061
+
1062
+ # FileUtils.rm_rf
1063
+ def rm_rf(*args)
1064
+ FileUtils.rm_rf(self, *args)
1065
+ end
1066
+
1067
+ # FileUtils.rmtree
1068
+ def rmtree(*args)
1069
+ FileUtils.rmtree(self, *args)
1070
+ end
1071
+
1072
+ # FileUtils.install
1073
+ def install(*args)
1074
+ FileUtils.install(self, *args)
1075
+ end
1076
+
1077
+ # FileUtils.touch
1078
+ def touch(*args)
1079
+ FileUtils.touch(*args)
1080
+ end
1081
+
1082
+ # FileUtils.compare_file
1083
+ def compare_file(file)
1084
+ FileUtils.compare_file(self, file)
1085
+ end
1086
+
1087
+ # FileUtils.uptodate?
1088
+ def uptodate?(*args)
1089
+ FileUtils.uptodate(self, *args)
1090
+ end
1091
+
1092
+ # FileUtils.copy_file
1093
+ def copy_file(*args)
1094
+ FileUtils.copy_file(self, *args)
1095
+ end
1096
+
1097
+ # FileUtils.remove_dir
1098
+ def remove_dir(*args)
1099
+ FileUtils.remove_dir(self, *args)
1100
+ end
1101
+
1102
+ # FileUtils.remove_file
1103
+ def remove_file(*args)
1104
+ FileUtils.remove_dir(self, *args)
1105
+ end
1106
+
1107
+ # FileUtils.copy_entry
1108
+ def copy_entry(*args)
1109
+ FileUtils.copy_entry(self, *args)
1110
+ end
1111
+ end
1112
+
1113
+ module Kernel
1114
+ # Usage: pn{ path }
1115
+ #
1116
+ # A shortcut for Pathname.new
1117
+ #
1118
+ def pn
1119
+ instance_eval{ Pathname.new(yield) }
1120
+ end
1121
+
1122
+ begin
1123
+ remove_method(:Pathname)
1124
+ rescue NoMethodError, NameError
1125
+ # Do nothing, not defined.
1126
+ end
1127
+
1128
+ # Synonym for Pathname.new
1129
+ #
1130
+ def Pathname(path)
1131
+ Pathname.new(path)
1132
+ end
1133
+ end
1134
+
1135
+ class String
1136
+ # Convert a string directly into a Pathname object.
1137
+ def to_path
1138
+ Pathname.new(self)
1139
+ end
1140
+ end