pathname2 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGES +3 -0
  2. data/lib/pathname2.rb +557 -536
  3. data/test/tc_pathname.rb +192 -182
  4. data/test/tc_pathname_win.rb +237 -227
  5. metadata +5 -5
data/CHANGES CHANGED
@@ -1,2 +1,5 @@
1
+ == 1.1.0 - 13-Jul-2005
2
+ * Added the 'find' facade.
3
+
1
4
  == 1.0.0 - 11-Jun-2005
2
5
  * Initial release
data/lib/pathname2.rb CHANGED
@@ -1,536 +1,557 @@
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 Win32, including UNC path names. Note
10
- # that forward slashes are converted to backslashes on Win32 systems.
11
- #
12
- # == Usage
13
- #
14
- # require "pathname"
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
- # # Win32
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 Daniel J. Berger.
38
- # Licensed under the same terms as Ruby itself.
39
- #
40
- require "facade"
41
- require "ftools"
42
- require "fileutils"
43
-
44
- class Pathname < String
45
- extend Facade
46
- facade File
47
- facade Dir
48
-
49
- if PLATFORM.match("mswin32")
50
- require "Win32API"
51
- @@PathStripToRoot = Win32API.new("shlwapi","PathStripToRoot","P","L")
52
- @@PathIsUNC = Win32API.new("shlwapi","PathIsUNC","P","L")
53
- @@PathIsURL = Win32API.new("shlwapi","PathIsURL","P","L")
54
- @@PathCanonicalize = Win32API.new("shlwapi","PathCanonicalize","PP","L")
55
- @@PathAppend = Win32API.new("shlwapi","PathAppend","PP","L")
56
- @@PathIsRoot = Win32API.new("shlwapi","PathIsRoot","P","L")
57
- @@PathIsDirectory = Win32API.new("shlwapi","PathIsDirectory","P","L")
58
- @@PathIsRelative = Win32API.new("shlwapi","PathIsRelative","P","L")
59
- @@PathFileExists = Win32API.new("shlwapi","PathFileExists","P","L")
60
- @@PathUndecorate = Win32API.new("shlwapi","PathUndecorate","P","L")
61
-
62
- @@PathGetDriveNumber =
63
- Win32API.new("shlwapi","PathGetDriveNumber","P","L")
64
-
65
- @@PathCreateFromUrl =
66
- Win32API.new("shlwapi", "PathCreateFromUrl", "PPPL", "L")
67
-
68
- @@PathRemoveBackslash =
69
- Win32API.new("shlwapi", "PathRemoveBackslash", "P", "P")
70
- end
71
-
72
- VERSION = "1.0.0"
73
- MAX_PATH = 260
74
-
75
- # Creates and returns a new Pathname object.
76
- #
77
- # On Win32 systems, all forward slashes are replaced with backslashes.
78
- def initialize(path)
79
- if path.length > MAX_PATH
80
- msg = "string too long. maximum string length is " + MAX_PATH.to_s
81
- raise PathnameError, msg
82
- end
83
-
84
- @sep = File::ALT_SEPARATOR || File::SEPARATOR
85
- @win32 = PLATFORM.match("mswin32")
86
-
87
- # Handle File URL's for Windows
88
- if @win32
89
- if @@PathIsURL.call(path) > 0
90
- buf = 0.chr * MAX_PATH
91
- len = [buf.length].pack("l")
92
- if @@PathCreateFromUrl.call(path, buf, len, 0) >= 0
93
- path = buf.strip
94
- else
95
- raise PathnameError, "invalid file url: #{path}"
96
- end
97
- end
98
- end
99
-
100
- # Convert forward slashes to backslashes on Win32
101
- path = path.tr("/", @sep) if @win32
102
- super(path)
103
- end
104
-
105
- # Win32 only
106
- #
107
- # Removes the decoration from a path string. For example,
108
- # C:\Path\File[5].txt would become C:\Path\File.txt.
109
- def undecorate
110
- unless @win32
111
- raise NotImplementedError, "not supported on this platform"
112
- end
113
- buf = 0.chr * MAX_PATH
114
- buf[0..self.length-1] = self
115
- @@PathUndecorate.call(buf)
116
- Pathname.new(buf.split(0.chr).first)
117
- end
118
-
119
- # Removes trailing slash, if present.
120
- def pstrip
121
- if @win32
122
- @@PathRemoveBackslash.call(self)
123
- self.strip!
124
- else
125
- if self[-1] == @sep
126
- self.strip!
127
- self.chop!
128
- end
129
- end
130
- self
131
- end
132
-
133
- # Splits a pathname into pieces based on the path separator. For example,
134
- # "/foo/bar/baz" would return a three element array of ['foo','bar','baz'].
135
- def to_a
136
- array = split(@sep) # Split string by path separator
137
- array.delete("") # Remove empty elements
138
- array
139
- end
140
-
141
- # Yields each component of the path name to a block.
142
- def each
143
- self.to_a.each{ |element| yield element }
144
- end
145
-
146
- # Returns the root directory of the path, or '.' if there is no root
147
- # directory.
148
- #
149
- # On Unix, this means the '/' character. On Win32 systems, this can
150
- # refer to the drive letter, or the server and share path if the path
151
- # is a UNC path.
152
- def root
153
- dir = "."
154
- if @win32
155
- buf = 0.chr * MAX_PATH
156
- buf[0..self.length-1] = self
157
-
158
- if @@PathStripToRoot.call(buf) > 0
159
- dir = Pathname.new(buf.split(0.chr).first)
160
- end
161
- else
162
- dir = File.dirname(self)
163
- while dir != "/" && dir != "."
164
- dir = File.dirname(dir)
165
- end
166
- end
167
- dir = "." if dir.empty?
168
- dir
169
- end
170
-
171
- # Returns whether or not the path consists only of a root directory.
172
- def root?
173
- if @win32
174
- @@PathIsRoot.call(self) > 0
175
- else
176
- self == root
177
- end
178
- end
179
-
180
- # Win32 only
181
- #
182
- # Determines if the string is a valid Universal Naming Convention (UNC)
183
- # for a server and share path.
184
- def unc?
185
- unless @win32
186
- raise NotImplementedError, "not supported on this platform"
187
- end
188
-
189
- @@PathIsUNC.call(self) > 0
190
- end
191
-
192
- # Win32 only
193
- #
194
- # Returns the drive number that corresponds to the root, or nil if not
195
- # applicable.
196
- #
197
- # For example, Pathname.new("C:\\foo").drive_number would return 2.
198
- def drive_number
199
- unless @win32
200
- raise NotImplementedError, "not supported on this platform"
201
- end
202
-
203
- num = @@PathGetDriveNumber.call(self)
204
- num >= 0 ? num : nil
205
- end
206
-
207
- # Pathnames may only be compared against other Pathnames, not strings.
208
- def <=>(string)
209
- return nil unless string.kind_of?(Pathname)
210
- super
211
- end
212
-
213
- # Adds two Pathname objects together, or a Pathname and a String. It
214
- # also automatically cleans the Pathname.
215
- #
216
- # Example:
217
- # path1 = '/foo/bar'
218
- # path2 = '../baz'
219
- # path1 + path2 # '/foo/baz'
220
- #
221
- # Adding a root path to an existing path merely replaces the current
222
- # path. Adding '.' to an existing path does nothing.
223
- def +(string)
224
- unless string.kind_of?(Pathname)
225
- string = Pathname.new(string)
226
- end
227
-
228
- # Any path plus "." is the same directory
229
- return self if string == "."
230
-
231
- # Use the builtin PathAppend method if on Windows - much easier
232
- if @win32
233
- buf = 0.chr * MAX_PATH
234
- buf[0..self.length-1] = self
235
- @@PathAppend.call(buf, string << 0.chr)
236
- buf.strip!
237
- return Pathname.new(buf) # PathAppend cleans automatically
238
- end
239
-
240
- # If the string is an absolute directory, return it
241
- return string if string.absolute?
242
-
243
- array = self.to_a + string.to_a
244
- new_string = array.join(@sep)
245
-
246
- unless self.relative? || @win32
247
- new_string = @sep + new_string # Add root path back if needed
248
- end
249
-
250
- Pathname.new(new_string).clean
251
- end
252
-
253
- # Returns whether or not the path is an absolute path.
254
- def absolute?
255
- !relative?
256
- end
257
-
258
- # Returns whether or not the path is a relative path.
259
- def relative?
260
- if @win32
261
- @@PathIsRelative.call(self) > 0
262
- else
263
- root == "."
264
- end
265
- end
266
-
267
- # Removes unnecessary '.' paths and ellides '..' paths appropriately.
268
- def clean
269
- return self if self.empty?
270
-
271
- if @win32
272
- path = 0.chr * MAX_PATH
273
- if @@PathCanonicalize.call(path, self) > 0
274
- return Pathname.new(path.split(0.chr).first)
275
- else
276
- return self
277
- end
278
- end
279
-
280
- final = []
281
- self.to_a.each{ |element|
282
- next if element == "."
283
- final.push(element)
284
- if element == ".." && self != ".."
285
- 2.times{ final.pop }
286
- end
287
- }
288
- final = final.join(@sep)
289
- final = root + final if root != "."
290
- final = "." if final.empty?
291
- Pathname.new(final)
292
- end
293
- alias :cleanpath :clean
294
-
295
- #-- IO methods not handled by facade
296
-
297
- # IO.foreach
298
- def foreach(*args, &block)
299
- IO.foreach(self, *args, &block)
300
- end
301
-
302
- # IO.read
303
- def read(*args)
304
- IO.read(self, *args)
305
- end
306
-
307
- # IO.readlines
308
- def readlines(*args)
309
- IO.readlines(self, *args)
310
- end
311
-
312
- # IO.sysopen
313
- def sysopen(*args)
314
- IO.sysopen(self, *args)
315
- end
316
-
317
- #-- Dir methods not handled by facade
318
-
319
- # Dir.glob
320
- def glob(*args)
321
- if block_given?
322
- Dir.glob(*args){ |file| yield Pathname.new(file) }
323
- else
324
- Dir.glob(*args).map{ |file| Pathname.new(file) }
325
- end
326
- end
327
-
328
- # Dir.chdir
329
- def chdir(&block)
330
- Dir.chdir(self, &block)
331
- end
332
-
333
- # Dir.entries
334
- def entries
335
- Dir.entries(self).map{ |file| Pathname.new(file) }
336
- end
337
-
338
- # Dir.mkdir
339
- def mkdir(*args)
340
- Dir.mkdir(self, *args)
341
- end
342
-
343
- # Dir.opendir
344
- def opendir(&block)
345
- Dir.open(self, &block)
346
- end
347
-
348
- #-- File methods not handled by facade
349
-
350
- # File.chmod
351
- def chmod(mode)
352
- File.chmod(mode, self)
353
- end
354
-
355
- # File.lchmod
356
- def lchmod(mode)
357
- File.lchmod(mode, self)
358
- end
359
-
360
- # File.chown
361
- def chown(owner, group)
362
- File.chown(owner, group, self)
363
- end
364
-
365
- # File.lchown
366
- def lchown(owner, group)
367
- File.lchown(owner, group, self)
368
- end
369
-
370
- # File.fnmatch
371
- def fnmatch(pattern, *args)
372
- File.fnmatch(pattern, self, *args)
373
- end
374
-
375
- # File.fnmatch?
376
- def fnmatch?(pattern, *args)
377
- File.fnmatch?(pattern, self, *args)
378
- end
379
-
380
- # File.link
381
- def link(old)
382
- File.link(old, self)
383
- end
384
-
385
- # File.open
386
- def open(*args, &block)
387
- File.open(self, *args, &block)
388
- end
389
-
390
- # File.rename
391
- def rename(name)
392
- File.rename(self, name)
393
- end
394
-
395
- # File.symlink
396
- def symlink(old)
397
- File.symlink(old, self)
398
- end
399
-
400
- # File.truncate
401
- def truncate(length)
402
- File.truncate(self, length)
403
- end
404
-
405
- # File.utime
406
- def utime(atime, mtime)
407
- File.utime(atime, mtime, self)
408
- end
409
-
410
- # File.basename
411
- def basename(*args)
412
- File.basename(self, *args)
413
- end
414
-
415
- # File.expand_path
416
- def expand_path(*args)
417
- File.expand_path(self, *args)
418
- end
419
-
420
- #--
421
- # ftools facade
422
- #++
423
-
424
- # ftools File.copy
425
- def copy(*args)
426
- File.copy(self, *args)
427
- end
428
-
429
- #--
430
- # FileUtils facade. Note that methods already covered by File and Dir
431
- # are not defined here (pwd, mkdir, etc).
432
- #++
433
-
434
- # FileUtils.cd
435
- def cd(*args, &block)
436
- FileUtils.cd(self, *args, &block)
437
- end
438
-
439
- # FileUtils.mkdir_p
440
- def mkdir_p(*args)
441
- FileUtils.mkdir_p(self, *args)
442
- end
443
- alias mkpath mkdir_p
444
-
445
- # FileUtils.ln
446
- def ln(*args)
447
- FileUtils.ln(self, *args)
448
- end
449
-
450
- # FileUtils.ln_s
451
- def ln_s(*args)
452
- FileUtils.ln_s(self, *args)
453
- end
454
-
455
- # FileUtils.ln_sf
456
- def ln_sf(*args)
457
- FileUtils.ln_sf(self, *args)
458
- end
459
-
460
- # FileUtils.cp
461
- def cp(*args)
462
- FileUtils.cp(self, *args)
463
- end
464
-
465
- # FileUtils.cp_r
466
- def cp_r(*args)
467
- FileUtils.cp_r(self, *args)
468
- end
469
-
470
- # FileUtils.mv
471
- def mv(*args)
472
- FileUtils.mv(self, *args)
473
- end
474
-
475
- # FileUtils.rm
476
- def rm(*args)
477
- FileUtils.rm(self, *args)
478
- end
479
- alias remove rm
480
-
481
- # FileUtils.rm_f
482
- def rm_f(*args)
483
- FileUtils.rm_f(self, *args)
484
- end
485
-
486
- # FileUtils.rm_r
487
- def rm_r(*args)
488
- FileUtils.rm_r(self, *args)
489
- end
490
-
491
- # FileUtils.rm_rf
492
- def rm_rf(*args)
493
- FileUtils.rm_rf(self, *args)
494
- end
495
- alias rmtree rm_rf
496
-
497
- # FileUtils.install
498
- def install(*args)
499
- FileUtils.install(self, *args)
500
- end
501
-
502
- # FileUtils.touch
503
- def touch(*args)
504
- FileUtils.touch(*args)
505
- end
506
-
507
- # FileUtils.compare_file
508
- def compare_file(file)
509
- FileUtils.compare_file(self, file)
510
- end
511
-
512
- # FileUtils.uptodate?
513
- def uptodate?(*args)
514
- FileUtils.uptodate(self, *args)
515
- end
516
-
517
- # FileUtils.copy_file
518
- def copy_file(*args)
519
- FileUtils.copy_file(self, *args)
520
- end
521
-
522
- # FileUtils.remove_dir
523
- def remove_dir(*args)
524
- FileUtils.remove_dir(self, *args)
525
- end
526
-
527
- # FileUtils.remove_file
528
- def remove_file(*args)
529
- FileUtils.remove_dir(self, *args)
530
- end
531
-
532
- # FileUtils.copy_entry
533
- def copy_entry(*args)
534
- FileUtils.copy_entry(self, *args)
535
- end
536
- 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 Win32, including UNC path names. Note
10
+ # that forward slashes are converted to backslashes on Win32 systems.
11
+ #
12
+ # == Usage
13
+ #
14
+ # require "pathname"
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
+ # # Win32
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 Daniel J. Berger.
38
+ # Licensed under the same terms as Ruby itself.
39
+ #
40
+ require "facade"
41
+ require "ftools"
42
+ require "fileutils"
43
+
44
+ class Pathname < String
45
+ extend Facade
46
+ facade File
47
+ facade Dir
48
+
49
+ if PLATFORM.match("mswin32")
50
+ require "Win32API"
51
+ @@PathStripToRoot = Win32API.new("shlwapi","PathStripToRoot","P","L")
52
+ @@PathIsUNC = Win32API.new("shlwapi","PathIsUNC","P","L")
53
+ @@PathIsURL = Win32API.new("shlwapi","PathIsURL","P","L")
54
+ @@PathCanonicalize = Win32API.new("shlwapi","PathCanonicalize","PP","L")
55
+ @@PathAppend = Win32API.new("shlwapi","PathAppend","PP","L")
56
+ @@PathIsRoot = Win32API.new("shlwapi","PathIsRoot","P","L")
57
+ @@PathIsDirectory = Win32API.new("shlwapi","PathIsDirectory","P","L")
58
+ @@PathIsRelative = Win32API.new("shlwapi","PathIsRelative","P","L")
59
+ @@PathFileExists = Win32API.new("shlwapi","PathFileExists","P","L")
60
+ @@PathUndecorate = Win32API.new("shlwapi","PathUndecorate","P","L")
61
+
62
+ @@PathGetDriveNumber =
63
+ Win32API.new("shlwapi","PathGetDriveNumber","P","L")
64
+
65
+ @@PathCreateFromUrl =
66
+ Win32API.new("shlwapi", "PathCreateFromUrl", "PPPL", "L")
67
+
68
+ @@PathRemoveBackslash =
69
+ Win32API.new("shlwapi", "PathRemoveBackslash", "P", "P")
70
+ end
71
+
72
+ VERSION = "1.1.0"
73
+ MAX_PATH = 260
74
+
75
+ # Creates and returns a new Pathname object.
76
+ #
77
+ # On Win32 systems, all forward slashes are replaced with backslashes.
78
+ def initialize(path)
79
+ if path.length > MAX_PATH
80
+ msg = "string too long. maximum string length is " + MAX_PATH.to_s
81
+ raise PathnameError, msg
82
+ end
83
+
84
+ @sep = File::ALT_SEPARATOR || File::SEPARATOR
85
+ @win32 = PLATFORM.match("mswin32")
86
+
87
+ # Handle File URL's for Windows
88
+ if @win32
89
+ if @@PathIsURL.call(path) > 0
90
+ buf = 0.chr * MAX_PATH
91
+ len = [buf.length].pack("l")
92
+ if @@PathCreateFromUrl.call(path, buf, len, 0) >= 0
93
+ path = buf.strip
94
+ else
95
+ raise PathnameError, "invalid file url: #{path}"
96
+ end
97
+ end
98
+ end
99
+
100
+ # Convert forward slashes to backslashes on Win32
101
+ path = path.tr("/", @sep) if @win32
102
+ super(path)
103
+ end
104
+
105
+ # Win32 only
106
+ #
107
+ # Removes the decoration from a path string. For example,
108
+ # C:\Path\File[5].txt would become C:\Path\File.txt.
109
+ def undecorate
110
+ unless @win32
111
+ raise NotImplementedError, "not supported on this platform"
112
+ end
113
+ buf = 0.chr * MAX_PATH
114
+ buf[0..self.length-1] = self
115
+ @@PathUndecorate.call(buf)
116
+ Pathname.new(buf.split(0.chr).first)
117
+ end
118
+
119
+ # Removes trailing slash, if present.
120
+ def pstrip
121
+ if @win32
122
+ @@PathRemoveBackslash.call(self)
123
+ self.strip!
124
+ else
125
+ if self[-1] == @sep
126
+ self.strip!
127
+ self.chop!
128
+ end
129
+ end
130
+ self
131
+ end
132
+
133
+ # Splits a pathname into pieces based on the path separator. For example,
134
+ # "/foo/bar/baz" would return a three element array of ['foo','bar','baz'].
135
+ def to_a
136
+ array = split(@sep) # Split string by path separator
137
+ array.delete("") # Remove empty elements
138
+ array
139
+ end
140
+
141
+ # Yields each component of the path name to a block.
142
+ def each
143
+ self.to_a.each{ |element| yield element }
144
+ end
145
+
146
+ # Returns the root directory of the path, or '.' if there is no root
147
+ # directory.
148
+ #
149
+ # On Unix, this means the '/' character. On Win32 systems, this can
150
+ # refer to the drive letter, or the server and share path if the path
151
+ # is a UNC path.
152
+ def root
153
+ dir = "."
154
+ if @win32
155
+ buf = 0.chr * MAX_PATH
156
+ buf[0..self.length-1] = self
157
+
158
+ if @@PathStripToRoot.call(buf) > 0
159
+ dir = Pathname.new(buf.split(0.chr).first)
160
+ end
161
+ else
162
+ dir = File.dirname(self)
163
+ while dir != "/" && dir != "."
164
+ dir = File.dirname(dir)
165
+ end
166
+ end
167
+ dir = "." if dir.empty?
168
+ dir
169
+ end
170
+
171
+ # Returns whether or not the path consists only of a root directory.
172
+ def root?
173
+ if @win32
174
+ @@PathIsRoot.call(self) > 0
175
+ else
176
+ self == root
177
+ end
178
+ end
179
+
180
+ # Win32 only
181
+ #
182
+ # Determines if the string is a valid Universal Naming Convention (UNC)
183
+ # for a server and share path.
184
+ def unc?
185
+ unless @win32
186
+ raise NotImplementedError, "not supported on this platform"
187
+ end
188
+
189
+ @@PathIsUNC.call(self) > 0
190
+ end
191
+
192
+ # Win32 only
193
+ #
194
+ # Returns the drive number that corresponds to the root, or nil if not
195
+ # applicable.
196
+ #
197
+ # For example, Pathname.new("C:\\foo").drive_number would return 2.
198
+ def drive_number
199
+ unless @win32
200
+ raise NotImplementedError, "not supported on this platform"
201
+ end
202
+
203
+ num = @@PathGetDriveNumber.call(self)
204
+ num >= 0 ? num : nil
205
+ end
206
+
207
+ # Pathnames may only be compared against other Pathnames, not strings.
208
+ def <=>(string)
209
+ return nil unless string.kind_of?(Pathname)
210
+ super
211
+ end
212
+
213
+ # Adds two Pathname objects together, or a Pathname and a String. It
214
+ # also automatically cleans the Pathname.
215
+ #
216
+ # Example:
217
+ # path1 = '/foo/bar'
218
+ # path2 = '../baz'
219
+ # path1 + path2 # '/foo/baz'
220
+ #
221
+ # Adding a root path to an existing path merely replaces the current
222
+ # path. Adding '.' to an existing path does nothing.
223
+ def +(string)
224
+ unless string.kind_of?(Pathname)
225
+ string = Pathname.new(string)
226
+ end
227
+
228
+ # Any path plus "." is the same directory
229
+ return self if string == "."
230
+
231
+ # Use the builtin PathAppend method if on Windows - much easier
232
+ if @win32
233
+ buf = 0.chr * MAX_PATH
234
+ buf[0..self.length-1] = self
235
+ @@PathAppend.call(buf, string << 0.chr)
236
+ buf.strip!
237
+ return Pathname.new(buf) # PathAppend cleans automatically
238
+ end
239
+
240
+ # If the string is an absolute directory, return it
241
+ return string if string.absolute?
242
+
243
+ array = self.to_a + string.to_a
244
+ new_string = array.join(@sep)
245
+
246
+ unless self.relative? || @win32
247
+ new_string = @sep + new_string # Add root path back if needed
248
+ end
249
+
250
+ Pathname.new(new_string).clean
251
+ end
252
+
253
+ # Returns whether or not the path is an absolute path.
254
+ def absolute?
255
+ !relative?
256
+ end
257
+
258
+ # Returns whether or not the path is a relative path.
259
+ def relative?
260
+ if @win32
261
+ @@PathIsRelative.call(self) > 0
262
+ else
263
+ root == "."
264
+ end
265
+ end
266
+
267
+ # Removes unnecessary '.' paths and ellides '..' paths appropriately.
268
+ def clean
269
+ return self if self.empty?
270
+
271
+ if @win32
272
+ path = 0.chr * MAX_PATH
273
+ if @@PathCanonicalize.call(path, self) > 0
274
+ return Pathname.new(path.split(0.chr).first)
275
+ else
276
+ return self
277
+ end
278
+ end
279
+
280
+ final = []
281
+ self.to_a.each{ |element|
282
+ next if element == "."
283
+ final.push(element)
284
+ if element == ".." && self != ".."
285
+ 2.times{ final.pop }
286
+ end
287
+ }
288
+ final = final.join(@sep)
289
+ final = root + final if root != "."
290
+ final = "." if final.empty?
291
+ Pathname.new(final)
292
+ end
293
+ alias :cleanpath :clean
294
+
295
+ #-- Find facade
296
+
297
+ # Pathname#find is an iterator to traverse a directory tree in a depth first
298
+ # manner. It yields a Pathname for each file under the directory passed to
299
+ # Pathname.new.
300
+ #
301
+ # Since it is implemented by the Find module, Find.prune can be used to
302
+ # control the traverse.
303
+ #
304
+ # If +self+ is ".", yielded pathnames begin with a filename in the current
305
+ # current directory, not ".".
306
+ #
307
+ def find(&block)
308
+ require "find"
309
+ if self == "."
310
+ Find.find(self){ |f| yield Pathname.new(f.sub(%r{\A\./}, '')) }
311
+ else
312
+ Find.find(self) {|f| yield Pathname.new(f) }
313
+ end
314
+ end
315
+
316
+ #-- IO methods not handled by facade
317
+
318
+ # IO.foreach
319
+ def foreach(*args, &block)
320
+ IO.foreach(self, *args, &block)
321
+ end
322
+
323
+ # IO.read
324
+ def read(*args)
325
+ IO.read(self, *args)
326
+ end
327
+
328
+ # IO.readlines
329
+ def readlines(*args)
330
+ IO.readlines(self, *args)
331
+ end
332
+
333
+ # IO.sysopen
334
+ def sysopen(*args)
335
+ IO.sysopen(self, *args)
336
+ end
337
+
338
+ #-- Dir methods not handled by facade
339
+
340
+ # Dir.glob
341
+ def glob(*args)
342
+ if block_given?
343
+ Dir.glob(*args){ |file| yield Pathname.new(file) }
344
+ else
345
+ Dir.glob(*args).map{ |file| Pathname.new(file) }
346
+ end
347
+ end
348
+
349
+ # Dir.chdir
350
+ def chdir(&block)
351
+ Dir.chdir(self, &block)
352
+ end
353
+
354
+ # Dir.entries
355
+ def entries
356
+ Dir.entries(self).map{ |file| Pathname.new(file) }
357
+ end
358
+
359
+ # Dir.mkdir
360
+ def mkdir(*args)
361
+ Dir.mkdir(self, *args)
362
+ end
363
+
364
+ # Dir.opendir
365
+ def opendir(&block)
366
+ Dir.open(self, &block)
367
+ end
368
+
369
+ #-- File methods not handled by facade
370
+
371
+ # File.chmod
372
+ def chmod(mode)
373
+ File.chmod(mode, self)
374
+ end
375
+
376
+ # File.lchmod
377
+ def lchmod(mode)
378
+ File.lchmod(mode, self)
379
+ end
380
+
381
+ # File.chown
382
+ def chown(owner, group)
383
+ File.chown(owner, group, self)
384
+ end
385
+
386
+ # File.lchown
387
+ def lchown(owner, group)
388
+ File.lchown(owner, group, self)
389
+ end
390
+
391
+ # File.fnmatch
392
+ def fnmatch(pattern, *args)
393
+ File.fnmatch(pattern, self, *args)
394
+ end
395
+
396
+ # File.fnmatch?
397
+ def fnmatch?(pattern, *args)
398
+ File.fnmatch?(pattern, self, *args)
399
+ end
400
+
401
+ # File.link
402
+ def link(old)
403
+ File.link(old, self)
404
+ end
405
+
406
+ # File.open
407
+ def open(*args, &block)
408
+ File.open(self, *args, &block)
409
+ end
410
+
411
+ # File.rename
412
+ def rename(name)
413
+ File.rename(self, name)
414
+ end
415
+
416
+ # File.symlink
417
+ def symlink(old)
418
+ File.symlink(old, self)
419
+ end
420
+
421
+ # File.truncate
422
+ def truncate(length)
423
+ File.truncate(self, length)
424
+ end
425
+
426
+ # File.utime
427
+ def utime(atime, mtime)
428
+ File.utime(atime, mtime, self)
429
+ end
430
+
431
+ # File.basename
432
+ def basename(*args)
433
+ File.basename(self, *args)
434
+ end
435
+
436
+ # File.expand_path
437
+ def expand_path(*args)
438
+ File.expand_path(self, *args)
439
+ end
440
+
441
+ #--
442
+ # ftools facade
443
+ #++
444
+
445
+ # ftools File.copy
446
+ def copy(*args)
447
+ File.copy(self, *args)
448
+ end
449
+
450
+ #--
451
+ # FileUtils facade. Note that methods already covered by File and Dir
452
+ # are not defined here (pwd, mkdir, etc).
453
+ #++
454
+
455
+ # FileUtils.cd
456
+ def cd(*args, &block)
457
+ FileUtils.cd(self, *args, &block)
458
+ end
459
+
460
+ # FileUtils.mkdir_p
461
+ def mkdir_p(*args)
462
+ FileUtils.mkdir_p(self, *args)
463
+ end
464
+ alias mkpath mkdir_p
465
+
466
+ # FileUtils.ln
467
+ def ln(*args)
468
+ FileUtils.ln(self, *args)
469
+ end
470
+
471
+ # FileUtils.ln_s
472
+ def ln_s(*args)
473
+ FileUtils.ln_s(self, *args)
474
+ end
475
+
476
+ # FileUtils.ln_sf
477
+ def ln_sf(*args)
478
+ FileUtils.ln_sf(self, *args)
479
+ end
480
+
481
+ # FileUtils.cp
482
+ def cp(*args)
483
+ FileUtils.cp(self, *args)
484
+ end
485
+
486
+ # FileUtils.cp_r
487
+ def cp_r(*args)
488
+ FileUtils.cp_r(self, *args)
489
+ end
490
+
491
+ # FileUtils.mv
492
+ def mv(*args)
493
+ FileUtils.mv(self, *args)
494
+ end
495
+
496
+ # FileUtils.rm
497
+ def rm(*args)
498
+ FileUtils.rm(self, *args)
499
+ end
500
+ alias remove rm
501
+
502
+ # FileUtils.rm_f
503
+ def rm_f(*args)
504
+ FileUtils.rm_f(self, *args)
505
+ end
506
+
507
+ # FileUtils.rm_r
508
+ def rm_r(*args)
509
+ FileUtils.rm_r(self, *args)
510
+ end
511
+
512
+ # FileUtils.rm_rf
513
+ def rm_rf(*args)
514
+ FileUtils.rm_rf(self, *args)
515
+ end
516
+ alias rmtree rm_rf
517
+
518
+ # FileUtils.install
519
+ def install(*args)
520
+ FileUtils.install(self, *args)
521
+ end
522
+
523
+ # FileUtils.touch
524
+ def touch(*args)
525
+ FileUtils.touch(*args)
526
+ end
527
+
528
+ # FileUtils.compare_file
529
+ def compare_file(file)
530
+ FileUtils.compare_file(self, file)
531
+ end
532
+
533
+ # FileUtils.uptodate?
534
+ def uptodate?(*args)
535
+ FileUtils.uptodate(self, *args)
536
+ end
537
+
538
+ # FileUtils.copy_file
539
+ def copy_file(*args)
540
+ FileUtils.copy_file(self, *args)
541
+ end
542
+
543
+ # FileUtils.remove_dir
544
+ def remove_dir(*args)
545
+ FileUtils.remove_dir(self, *args)
546
+ end
547
+
548
+ # FileUtils.remove_file
549
+ def remove_file(*args)
550
+ FileUtils.remove_dir(self, *args)
551
+ end
552
+
553
+ # FileUtils.copy_entry
554
+ def copy_entry(*args)
555
+ FileUtils.copy_entry(self, *args)
556
+ end
557
+ end