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.
- data/CHANGES +129 -0
- data/MANIFEST +11 -0
- data/README +99 -0
- data/Rakefile +43 -0
- data/benchmarks/bench_pathname.rb +127 -0
- data/benchmarks/bench_plus.rb +34 -0
- data/examples/example_pathname.rb +25 -0
- data/lib/pathname2.rb +1124 -0
- data/pathname2.gemspec +37 -0
- data/test/test_pathname.rb +480 -0
- data/test/test_pathname_windows.rb +678 -0
- metadata +126 -0
@@ -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
|