pathname2 1.7.2-universal-mingw32 → 1.8.4-universal-mingw32

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