pathname2 1.6.4-x86-mingw32

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