stouset-pathname3 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,38 @@
1
+ = pathname3 Changelog
2
+
3
+ == Version 1.2.3
4
+
5
+ * Fixes gem building on GitHub
6
+
7
+ == Version 1.2.2
8
+
9
+ * Fixes a bug where split didn't return Pathnames
10
+ * Fixes a bug where empty components of a path were joined
11
+ * Fixes bugs where methods were called on the wrong class
12
+ * Documentation improvements
13
+
14
+ == Version 1.2.1
15
+
16
+ * Fix a bug where Pathname#open was accidentally redefined due to misnamed
17
+ method
18
+
19
+ == Version 1.2.0
20
+
21
+ * == compares paths semantically
22
+ * Better compatibility with Pathname
23
+ * Full documentation
24
+
25
+ == Version 1.1.0
26
+
27
+ * More compliant with original Pathname.
28
+ * Specs testing compliance from Rubinius
29
+
30
+ == Version 1.0.1
31
+
32
+ * Included the MIT license. Whoops.
33
+
34
+ == Version 1.0.0
35
+
36
+ * First release
37
+ * Mostly-compatible support with pathname
38
+ * String-based pathname implementation
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Stephen Touset
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,43 @@
1
+ = pathname3
2
+
3
+ This library is a replacement for the venerable pathname and pathname2
4
+ libraries.
5
+
6
+ The original implementation of pathname is extremely slow, and
7
+ instantiates extreme numbers of objects for relatively simple operations.
8
+ Twenty or so Pathname instantiations on some method calls is relatively
9
+ common.
10
+
11
+ An implementation by Daniel J. Berger, pathname2, improved on the original
12
+ significantly, adding Windows support, a Facade implementation, and a String-
13
+ based implementation. While his version is faster, it's still very slow at
14
+ instantiation. And it's Facade implementation misses some odd case methods
15
+ like Pathname#join.
16
+
17
+ This version will focus on being fast and lightweight, while still being pure
18
+ Ruby. Windows support will be forthcoming once I have access to a Windows
19
+ development machine. Until then, patches adding Windows compatibility are
20
+ welcome.
21
+
22
+ == Installation
23
+
24
+ You can install pathname3 through Rubygems. The gem is hosted on GitHub, and
25
+ can be installed via
26
+
27
+ $ sudo gem install --source http://gems.github.com/ stouset-pathname3
28
+
29
+ == Contribution
30
+
31
+ The pathname3 project is hosted on GitHub.
32
+
33
+ http://github.com/stouset/pathname3/
34
+
35
+ To clone the repository, simply run:
36
+
37
+ git clone git://github.com/stouset/pathname3.git
38
+
39
+ == License
40
+
41
+ pathname3 is available under the MIT license.
42
+
43
+ :include: LICENSE
data/lib/pathname3.rb ADDED
@@ -0,0 +1,570 @@
1
+ require 'fileutils'
2
+ require 'find'
3
+
4
+ #
5
+ # Pathname represents a path to a file on a filesystem. It can be relative or
6
+ # absolute. It exists to provide a more instance-oriented approach to managing
7
+ # paths than the class-level methods on File, FileTest, Dir, and Find.
8
+ #
9
+ class Pathname < String
10
+ SYMLOOP_MAX = 8 # deepest symlink traversal
11
+
12
+ ROOT = '/'.freeze
13
+ DOT = '.'.freeze
14
+ DOT_DOT = '..'.freeze
15
+
16
+ #
17
+ # Creates a new Pathname. Any path with a null is rejected.
18
+ #
19
+ def initialize(path)
20
+ if path =~ %r{\0}
21
+ raise ArgumentError, "path cannot contain ASCII NULLs"
22
+ end
23
+
24
+ super(path)
25
+ end
26
+
27
+ #
28
+ # Compares pathnames, case-sensitively. Sorts directories higher than other
29
+ # files named similarly.
30
+ #
31
+ def <=>(other)
32
+ self.tr('/', "\0").to_s <=> other.to_str.tr('/', "\0")
33
+ rescue NoMethodError # doesn't respond to to_str
34
+ nil
35
+ end
36
+
37
+ #
38
+ # Compares two pathnames for equality. Considers pathnames equal if they
39
+ # both point to the same location, and are both absolute or both relative.
40
+ #
41
+ def ==(other)
42
+ left = self.cleanpath.tr('/', "\0").to_s
43
+ right = other.to_str.to_path.cleanpath.tr('/', "\0").to_s
44
+
45
+ left == right
46
+ rescue NoMethodError # doesn't implement to_str
47
+ false
48
+ end
49
+
50
+ #
51
+ # Appends a component of a path to self. Returns a Pathname to the combined
52
+ # path. Cleans any redundant components of the path.
53
+ #
54
+ def +(path)
55
+ dup << path
56
+ end
57
+
58
+ #
59
+ # Appends (destructively) a component of a path to self. Replaces the
60
+ # contents of the current Pathname with the new, combined path. Cleans any
61
+ # redundant components of the path.
62
+ #
63
+ def <<(path)
64
+ replace( join(path).cleanpath! )
65
+ end
66
+
67
+ #
68
+ # Returns true if this is an absolute path.
69
+ #
70
+ def absolute?
71
+ self[0, 1].to_s == ROOT
72
+ end
73
+
74
+ #
75
+ # Yields to each component of the path, going up to the root.
76
+ #
77
+ # Pathname.new('/path/to/some/file').ascend {|path| p path }
78
+ # "/path/to/some/file"
79
+ # "/path/to/some"
80
+ # "/path/to"
81
+ # "/path"
82
+ # "/"
83
+ #
84
+ # Pathname.new('a/relative/path').ascend {|path| p path }
85
+ # "a/relative/path"
86
+ # "a/relative"
87
+ # "a"
88
+ #
89
+ # Does not actually access the filesystem.
90
+ #
91
+ def ascend
92
+ parts = to_a
93
+ parts.length.downto(1) do |i|
94
+ yield self.class.join(parts[0, i])
95
+ end
96
+ end
97
+
98
+ #
99
+ # Returns all children of this path. "." and ".." are not included, since
100
+ # they aren't under the current path.
101
+ #
102
+ def children
103
+ entries[2..-1]
104
+ end
105
+
106
+ #
107
+ # Cleans the path by removing consecutive slashes, and useless dots.
108
+ # Replaces the contents of the current Pathname.
109
+ #
110
+ def cleanpath!
111
+ parts = to_a
112
+ final = []
113
+
114
+ parts.each do |part|
115
+ case part
116
+ when DOT then next
117
+ when DOT_DOT then
118
+ case final.last
119
+ when ROOT then next
120
+ when DOT_DOT then final.push(DOT_DOT)
121
+ when nil then final.push(DOT_DOT)
122
+ else final.pop
123
+ end
124
+ else final.push(part)
125
+ end
126
+ end
127
+
128
+ replace(final.empty? ? DOT : self.class.join(*final))
129
+ end
130
+
131
+ #
132
+ # Cleans the path by removing consecutive slashes, and useless dots.
133
+ #
134
+ def cleanpath
135
+ dup.cleanpath!
136
+ end
137
+
138
+ #
139
+ # Yields to each component of the path, going down from the root.
140
+ #
141
+ # Pathname.new('/path/to/some/file').ascend {|path| p path }
142
+ # "/"
143
+ # "/path"
144
+ # "/path/to"
145
+ # "/path/to/some"
146
+ # "/path/to/some/file"
147
+
148
+ #
149
+ # Pathname.new('a/relative/path').ascend {|path| p path }
150
+ # "a"
151
+ # "a/relative"
152
+ # "a/relative/path"
153
+ #
154
+ # Does not actually access the filesystem.
155
+ #
156
+ def descend
157
+ parts = to_a
158
+ 1.upto(parts.length) do |i|
159
+ yield self.class.join(parts[0, i])
160
+ end
161
+ end
162
+
163
+ #
164
+ # Returns true if this path is simply a '.'.
165
+ #
166
+ def dot?
167
+ self == DOT
168
+ end
169
+
170
+ #
171
+ # Returns true if this path is simply a '..'.
172
+ #
173
+ def dot_dot?
174
+ self == DOT_DOT
175
+ end
176
+
177
+ #
178
+ # Iterates over every component of the path.
179
+ #
180
+ # Pathname.new('/path/to/some/file').ascend {|path| p path }
181
+ # "/"
182
+ # "path"
183
+ # "to"
184
+ # "some"
185
+ # "file"
186
+ #
187
+ # Pathname.new('a/relative/path').each_filename {|part| p part }
188
+ # "a"
189
+ # "relative"
190
+ # "path"
191
+ #
192
+ def each_filename(&blk)
193
+ to_a.each(&blk)
194
+ end
195
+
196
+ #
197
+ # Returns true if the path is a mountpoint.
198
+ #
199
+ def mountpoint?
200
+ stat1 = self.lstat
201
+ stat2 = self.parent.lstat
202
+
203
+ stat1.dev != stat2.dev || stat1.ino == stat2.ino
204
+ rescue Errno::ENOENT
205
+ false
206
+ end
207
+
208
+ #
209
+ # Returns a path to the parent directory. Simply appends a "..".
210
+ #
211
+ def parent
212
+ self + '..'
213
+ end
214
+
215
+ #
216
+ # Resolves a path to locate a real location on the filesystem. Resolves
217
+ # symlinks up to a deptho of SYMLOOP_MAX.
218
+ #
219
+ def realpath
220
+ path = self
221
+
222
+ SYMLOOP_MAX.times do
223
+ link = path.readlink
224
+ link = path.dirname + link if link.relative?
225
+ path = link
226
+ end
227
+
228
+ raise Errno::ELOOP, self
229
+ rescue Errno::EINVAL
230
+ path.expand_path
231
+ end
232
+
233
+ #
234
+ # Returns true if this is a relative path.
235
+ #
236
+ def relative?
237
+ !absolute?
238
+ end
239
+
240
+ #
241
+ # Returns this path as a relative location from +base+. The path and +base+
242
+ # must both be relative or both be absolute. An ArgumentError is raised if
243
+ # a relative path can't be generated between the two locations.
244
+ #
245
+ # Does not access the filesystem.
246
+ #
247
+ def relative_path_from(base)
248
+ base = base.to_path
249
+
250
+ # both must be relative, or both must be absolute
251
+ if self.absolute? != base.absolute?
252
+ raise ArgumentError, 'no relative path between a relative and absolute'
253
+ end
254
+
255
+ return self if base.dot?
256
+ return DOT.to_path if self == base
257
+
258
+ base = base.cleanpath.to_a
259
+ dest = self.cleanpath.to_a
260
+
261
+ while !dest.empty? && !base.empty? && dest[0] == base[0]
262
+ base.shift
263
+ dest.shift
264
+ end
265
+
266
+ base.shift if base[0] == DOT
267
+ dest.shift if dest[0] == DOT
268
+
269
+ if base.include?(DOT_DOT)
270
+ raise ArgumentError, "base directory may not contain '#{DOT_DOT}'"
271
+ end
272
+
273
+ path = base.fill(DOT_DOT) + dest
274
+ path = self.class.join(*path)
275
+ path = DOT.to_path if path.empty?
276
+
277
+ path
278
+ end
279
+
280
+ #
281
+ # Returns true if this path points to the root of the filesystem.
282
+ #
283
+ def root?
284
+ !!(self =~ %r{^#{ROOT}+$})
285
+ end
286
+
287
+ #
288
+ # Splits the path into an array of its components.
289
+ #
290
+ def to_a
291
+ array = to_s.split(File::SEPARATOR)
292
+ array.delete('')
293
+ array.insert(0, ROOT) if absolute?
294
+ array
295
+ end
296
+
297
+ #
298
+ # Returns self.
299
+ #
300
+ def to_path
301
+ self
302
+ end
303
+
304
+ #
305
+ # Unlinks the file or directory at the path.
306
+ #
307
+ def unlink
308
+ Dir.unlink(self)
309
+ rescue Errno::ENOTDIR
310
+ File.unlink(self)
311
+ end
312
+ end
313
+
314
+ class Pathname
315
+ # See Dir::[]
316
+ def self.[](pattern); Dir[pattern].map! {|d| d.to_path }; end
317
+
318
+ # See Dir::pwd
319
+ def self.pwd; Dir.pwd.to_path; end
320
+
321
+ # See Dir::entries
322
+ def entries; Dir.entries(self).map! {|e| e.to_path }; end
323
+
324
+ # See Dir::mkdir
325
+ def mkdir(mode = 0777); Dir.mkdir(self, mode); end
326
+
327
+ # See Dir::open
328
+ def opendir(&blk); Dir.open(self, &blk); end
329
+
330
+ # See Dir::rmdir
331
+ def rmdir; Dir.rmdir(self); end
332
+
333
+ # See Dir::glob
334
+ def self.glob(pattern, flags = 0)
335
+ dirs = Dir.glob(pattern, flags)
336
+ dirs.map! {|path| path.to_path }
337
+
338
+ if block_given?
339
+ dirs.each {|dir| yield dir }
340
+ nil
341
+ else
342
+ dirs
343
+ end
344
+ end
345
+
346
+ # See Dir::chdir
347
+ def chdir
348
+ blk = lambda { yield self } if block_given?
349
+ Dir.chdir(self, &blk)
350
+ end
351
+ end
352
+
353
+ class Pathname
354
+ # See FileTest::blockdev?
355
+ def blockdev?; FileTest.blockdev?(self); end
356
+
357
+ # See FileTest::chardev?
358
+ def chardev?; FileTest.chardev?(self); end
359
+
360
+ # See FileTest::directory?
361
+ def directory?; FileTest.directory?(self); end
362
+
363
+ # See FileTest::executable?
364
+ def executable?; FileTest.executable?(self); end
365
+
366
+ # See FileTest::executable_real?
367
+ def executable_real?; FileTest.executable_real?(self); end
368
+
369
+ # See FileTest::exists?
370
+ def exists?; FileTest.exists?(self); end
371
+
372
+ # See FileTest::file?
373
+ def file?; FileTest.file?(self); end
374
+
375
+ # See FileTest::grpowned?
376
+ def grpowned?; FileTest.grpowned?(self); end
377
+
378
+ # See FileTest::owned?
379
+ def owned?; FileTest.owned?(self); end
380
+
381
+ # See FileTest::pipe?
382
+ def pipe?; FileTest.pipe?(self); end
383
+
384
+ # See FileTest::readable?
385
+ def readable?; FileTest.readable?(self); end
386
+
387
+ # See FileTest::readable_real?
388
+ def readable_real?; FileTest.readable_real?(self); end
389
+
390
+ # See FileTest::setgid?
391
+ def setgid?; FileTest.setgit?(self); end
392
+
393
+ # See FileTest::setuid?
394
+ def setuid?; FileTest.setuid?(self); end
395
+
396
+ # See FileTest::socket?
397
+ def socket?; FileTest.socket?(self); end
398
+
399
+ # See FileTest::sticky?
400
+ def sticky?; FileTest.sticky?(self); end
401
+
402
+ # See FileTest::symlink?
403
+ def symlink?; FileTest.symlink?(self); end
404
+
405
+ # See FileTest::world_readable?
406
+ def world_readable?; FileTest.world_readable?(self); end
407
+
408
+ # See FileTest::world_writable?
409
+ def world_writable?; FileTest.world_writable?(self); end
410
+
411
+ # See FileTest::writable?
412
+ def writable?; FileTest.writable?(self); end
413
+
414
+ # See FileTest::writable_real?
415
+ def writable_real?; FileTest.writable_real?(self); end
416
+
417
+ # See FileTest::zero?
418
+ def zero?; FileTest.zero?(self); end
419
+ end
420
+
421
+ class Pathname
422
+ # See File::atime
423
+ def atime; File.atime(self); end
424
+
425
+ # See File::ctime
426
+ def ctime; File.ctime(self); end
427
+
428
+ # See File::ftype
429
+ def ftype; File.ftype(self); end
430
+
431
+ # See File::lstat
432
+ def lstat; File.lstat(self); end
433
+
434
+ # See File::mtime
435
+ def mtime; File.mtime(self); end
436
+
437
+ # See File::stat
438
+ def stat; File.stat(self); end
439
+
440
+ # See File::utime
441
+ def utime(atime, mtime); File.utime(self, atime, mtime); end
442
+ end
443
+
444
+ class Pathname
445
+ # See File::join
446
+ def self.join(*parts); File.join(*parts.reject {|p| p.empty? }).to_path; end
447
+
448
+ # See File::basename
449
+ def basename; File.basename(self).to_path; end
450
+
451
+ # See File::chmod
452
+ def chmod(mode); File.chmod(mode, self); end
453
+
454
+ # See File::chown
455
+ def chown(owner, group); File.chown(owner, group, self); end
456
+
457
+ # See File::dirname
458
+ def dirname; File.dirname(self).to_path; end
459
+
460
+ # See File::expand_path
461
+ def expand_path(from = nil); File.expand_path(self, from).to_path; end
462
+
463
+ # See File::extname
464
+ def extname; File.extname(self); end
465
+
466
+ # See File::fnmatch
467
+ def fnmatch?(pat, flags = 0); File.fnmatch(pat, self, flags); end
468
+
469
+ # See File::join
470
+ def join(*parts); self.class.join(self, *parts); end
471
+
472
+ # See File::lchmod
473
+ def lchmod(mode); File.lchmod(mode, self); end
474
+
475
+ # See File::lchown
476
+ def lchown(owner, group); File.lchown(owner, group, self); end
477
+
478
+ # See File::link
479
+ def link(to); File.link(self, to); end
480
+
481
+ # See File::open
482
+ def open(mode = 'r', perm = nil, &blk); File.open(self, mode, perm, &blk); end
483
+
484
+ # See File::readlink
485
+ def readlink; File.readlink(self).to_path; end
486
+
487
+ # See File::rename
488
+ def rename(to); File.rename(self, to); replace(to); end
489
+
490
+ # See File::size
491
+ def size; File.size(self); end
492
+
493
+ # See File::size?
494
+ def size?; File.size?(self); end
495
+
496
+ # See File::split
497
+ def split; File.split(self).map {|part| part.to_path }; end
498
+
499
+ # See File::symlink
500
+ def symlink(to); File.symlink(self, to); end
501
+
502
+ # See File::truncate
503
+ def truncate; File.truncate(self); end
504
+ end
505
+
506
+ class Pathname
507
+ # See FileUtils::mkpath
508
+ def mkpath; FileUtils.mkpath(self); end
509
+
510
+ # See FileUtils::rmtree
511
+ def rmtree; FileUtils.rmtree(self); end
512
+
513
+ # See FileUtils::touch
514
+ def touch; FileUtils.touch(self); end
515
+ end
516
+
517
+ class Pathname
518
+ # See IO::each_line
519
+ def each_line(sep = $/, &blk); IO.foreach(self, sep, &blk); end
520
+
521
+ # See IO::read
522
+ def read(len = nil, off = 0); IO.read(self, len, off); end
523
+
524
+ # See IO::readlines
525
+ def readlines(sep = $/); IO.readlines(self, sep); end
526
+
527
+ # See IO::sysopen
528
+ def sysopen(mode = 'r', perm = nil); IO.sysopen(self, mode, perm); end
529
+ end
530
+
531
+ class Pathname
532
+ # See Find::find
533
+ def find; Find.find(self) {|path| yield path.to_path }; end
534
+ end
535
+
536
+ class Pathname
537
+ class << self
538
+ # Alias for Pathname#pwd
539
+ alias getwd pwd
540
+ end
541
+
542
+ # Alias for Pathname#unlink
543
+ alias delete unlink
544
+
545
+ # Alias for Pathname#exists?
546
+ alias exist? exists?
547
+
548
+ # Alias for Pathname#fnmatch?
549
+ alias fnmatch fnmatch?
550
+ end
551
+
552
+ class String
553
+ #
554
+ # Converts the string directly to a pathname.
555
+ #
556
+ def to_path
557
+ Pathname.new(self)
558
+ end
559
+ end
560
+
561
+ module Kernel
562
+ #
563
+ # Allows construction of a Pathname by using the class name as a method.
564
+ #
565
+ # This really ought to be deprecated due to String#to_path.
566
+ #
567
+ def Pathname(path)
568
+ Pathname.new(path)
569
+ end
570
+ end