pathname2 1.6.3-x86-mswin32-60

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