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,45 @@
1
+ = pathname3 Changelog
2
+
3
+ == Version 1.2.4
4
+
5
+ * Usage examples in the README
6
+ * Rakefile for docs/testing
7
+ * Fixes bugs where return values were not Pathnames
8
+ * Adds Pathname#glob
9
+
10
+ == Version 1.2.3
11
+
12
+ * Fixes gem building on GitHub
13
+
14
+ == Version 1.2.2
15
+
16
+ * Fixes a bug where split didn't return Pathnames
17
+ * Fixes a bug where empty components of a path were joined
18
+ * Fixes bugs where methods were called on the wrong class
19
+ * Documentation improvements
20
+
21
+ == Version 1.2.1
22
+
23
+ * Fix a bug where Pathname#open was accidentally redefined due to misnamed
24
+ method
25
+
26
+ == Version 1.2.0
27
+
28
+ * == compares paths semantically
29
+ * Better compatibility with Pathname
30
+ * Full documentation
31
+
32
+ == Version 1.1.0
33
+
34
+ * More compliant with original Pathname.
35
+ * Specs testing compliance from Rubinius
36
+
37
+ == Version 1.0.1
38
+
39
+ * Included the MIT license. Whoops.
40
+
41
+ == Version 1.0.0
42
+
43
+ * First release
44
+ * Mostly-compatible support with pathname
45
+ * 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,62 @@
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
+ == Usage
30
+
31
+ require 'pathname3'
32
+
33
+ p = '~/foo'.to_path # => "~/foo"
34
+ p.absolute? # => false
35
+ p.relative? # => true
36
+ p.exists? # => false
37
+
38
+ p = p.absolute # => "/Users/stouset/foo"
39
+ p.absolute? # => true
40
+ p.relative? # => false
41
+ p.relative_path_from('/Users') # => "stouset/foo"
42
+ p.relative_path_from('/Library') # => "../Users/stouset/foo"
43
+ p.split # => ["/Users/stouset", "foo"]
44
+ p.touch # => "/Users/stouset/foo"
45
+ p.ctime # => Thu Jun 05 14:00:01 -0400 2008
46
+ p.delete # => true
47
+
48
+ == Contribution
49
+
50
+ The pathname3 project is hosted on GitHub.
51
+
52
+ http://github.com/stouset/pathname3/
53
+
54
+ To clone the repository, simply run:
55
+
56
+ git clone git://github.com/stouset/pathname3.git
57
+
58
+ == License
59
+
60
+ pathname3 is available under the MIT license.
61
+
62
+ :include: LICENSE
data/lib/pathname3.rb ADDED
@@ -0,0 +1,573 @@
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
+ # Pathname.new('a/relative/path').ascend {|path| p path }
149
+ # "a"
150
+ # "a/relative"
151
+ # "a/relative/path"
152
+ #
153
+ # Does not actually access the filesystem.
154
+ #
155
+ def descend
156
+ parts = to_a
157
+ 1.upto(parts.length) do |i|
158
+ yield self.class.join(parts[0, i])
159
+ end
160
+ end
161
+
162
+ #
163
+ # Returns true if this path is simply a '.'.
164
+ #
165
+ def dot?
166
+ self == DOT
167
+ end
168
+
169
+ #
170
+ # Returns true if this path is simply a '..'.
171
+ #
172
+ def dot_dot?
173
+ self == DOT_DOT
174
+ end
175
+
176
+ #
177
+ # Iterates over every component of the path.
178
+ #
179
+ # Pathname.new('/path/to/some/file').ascend {|path| p path }
180
+ # "/"
181
+ # "path"
182
+ # "to"
183
+ # "some"
184
+ # "file"
185
+ #
186
+ # Pathname.new('a/relative/path').each_filename {|part| p part }
187
+ # "a"
188
+ # "relative"
189
+ # "path"
190
+ #
191
+ def each_filename(&blk)
192
+ to_a.each(&blk)
193
+ end
194
+
195
+ #
196
+ # Returns true if the path is a mountpoint.
197
+ #
198
+ def mountpoint?
199
+ stat1 = self.lstat
200
+ stat2 = self.parent.lstat
201
+
202
+ stat1.dev != stat2.dev || stat1.ino == stat2.ino
203
+ rescue Errno::ENOENT
204
+ false
205
+ end
206
+
207
+ #
208
+ # Returns a path to the parent directory. Simply appends a "..".
209
+ #
210
+ def parent
211
+ self + '..'
212
+ end
213
+
214
+ #
215
+ # Resolves a path to locate a real location on the filesystem. Resolves
216
+ # symlinks up to a deptho of SYMLOOP_MAX.
217
+ #
218
+ def realpath
219
+ path = self
220
+
221
+ SYMLOOP_MAX.times do
222
+ link = path.readlink
223
+ link = path.dirname + link if link.relative?
224
+ path = link
225
+ end
226
+
227
+ raise Errno::ELOOP, self
228
+ rescue Errno::EINVAL
229
+ path.expand_path
230
+ end
231
+
232
+ #
233
+ # Returns true if this is a relative path.
234
+ #
235
+ def relative?
236
+ !absolute?
237
+ end
238
+
239
+ #
240
+ # Returns this path as a relative location from +base+. The path and +base+
241
+ # must both be relative or both be absolute. An ArgumentError is raised if
242
+ # a relative path can't be generated between the two locations.
243
+ #
244
+ # Does not access the filesystem.
245
+ #
246
+ def relative_path_from(base)
247
+ base = base.to_path
248
+
249
+ # both must be relative, or both must be absolute
250
+ if self.absolute? != base.absolute?
251
+ raise ArgumentError, 'no relative path between a relative and absolute'
252
+ end
253
+
254
+ return self if base.dot?
255
+ return DOT.to_path if self == base
256
+
257
+ base = base.cleanpath.to_a
258
+ dest = self.cleanpath.to_a
259
+
260
+ while !dest.empty? && !base.empty? && dest[0] == base[0]
261
+ base.shift
262
+ dest.shift
263
+ end
264
+
265
+ base.shift if base[0] == DOT
266
+ dest.shift if dest[0] == DOT
267
+
268
+ if base.include?(DOT_DOT)
269
+ raise ArgumentError, "base directory may not contain '#{DOT_DOT}'"
270
+ end
271
+
272
+ path = base.fill(DOT_DOT) + dest
273
+ path = self.class.join(*path)
274
+ path = DOT.to_path if path.empty?
275
+
276
+ path
277
+ end
278
+
279
+ #
280
+ # Returns true if this path points to the root of the filesystem.
281
+ #
282
+ def root?
283
+ !!(self =~ %r{^#{ROOT}+$})
284
+ end
285
+
286
+ #
287
+ # Splits the path into an array of its components.
288
+ #
289
+ def to_a
290
+ array = to_s.split(File::SEPARATOR)
291
+ array.delete('')
292
+ array.insert(0, ROOT) if absolute?
293
+ array
294
+ end
295
+
296
+ #
297
+ # Returns self.
298
+ #
299
+ def to_path
300
+ self
301
+ end
302
+
303
+ #
304
+ # Unlinks the file or directory at the path.
305
+ #
306
+ def unlink
307
+ Dir.unlink(self)
308
+ true
309
+ rescue Errno::ENOTDIR
310
+ File.unlink(self)
311
+ true
312
+ end
313
+ end
314
+
315
+ class Pathname
316
+ # See Dir::[]
317
+ def self.[](pattern); Dir[pattern].map! {|d| d.to_path }; end
318
+
319
+ # See Dir::pwd
320
+ def self.pwd; Dir.pwd.to_path; end
321
+
322
+ # See Dir::entries
323
+ def entries; Dir.entries(self).map! {|e| e.to_path }; end
324
+
325
+ # See Dir::mkdir
326
+ def mkdir(mode = 0777); Dir.mkdir(self, mode); end
327
+
328
+ # See Dir::open
329
+ def opendir(&blk); Dir.open(self, &blk); end
330
+
331
+ # See Dir::rmdir
332
+ def rmdir; Dir.rmdir(self); end
333
+
334
+ # See Dir::glob
335
+ def self.glob(pattern, flags = 0)
336
+ dirs = Dir.glob(pattern, flags)
337
+ dirs.map! {|path| path.to_path }
338
+
339
+ if block_given?
340
+ dirs.each {|dir| yield dir }
341
+ nil
342
+ else
343
+ dirs
344
+ end
345
+ end
346
+
347
+ # See Dir::glob
348
+ def glob(pattern, flags = 0, &block)
349
+ patterns = [pattern].flatten
350
+ patterns.map! {|p| self.class.glob(self.to_s + p, flags, &block) }
351
+ patterns.flatten
352
+ end
353
+
354
+ # See Dir::chdir
355
+ def chdir
356
+ blk = lambda { yield self } if block_given?
357
+ Dir.chdir(self, &blk)
358
+ end
359
+ end
360
+
361
+ class Pathname
362
+ # See FileTest::blockdev?
363
+ def blockdev?; FileTest.blockdev?(self); end
364
+
365
+ # See FileTest::chardev?
366
+ def chardev?; FileTest.chardev?(self); end
367
+
368
+ # See FileTest::directory?
369
+ def directory?; FileTest.directory?(self); end
370
+
371
+ # See FileTest::executable?
372
+ def executable?; FileTest.executable?(self); end
373
+
374
+ # See FileTest::executable_real?
375
+ def executable_real?; FileTest.executable_real?(self); end
376
+
377
+ # See FileTest::exists?
378
+ def exists?; FileTest.exists?(self); end
379
+
380
+ # See FileTest::file?
381
+ def file?; FileTest.file?(self); end
382
+
383
+ # See FileTest::grpowned?
384
+ def grpowned?; FileTest.grpowned?(self); end
385
+
386
+ # See FileTest::owned?
387
+ def owned?; FileTest.owned?(self); end
388
+
389
+ # See FileTest::pipe?
390
+ def pipe?; FileTest.pipe?(self); end
391
+
392
+ # See FileTest::readable?
393
+ def readable?; FileTest.readable?(self); end
394
+
395
+ # See FileTest::readable_real?
396
+ def readable_real?; FileTest.readable_real?(self); end
397
+
398
+ # See FileTest::setgid?
399
+ def setgid?; FileTest.setgit?(self); end
400
+
401
+ # See FileTest::setuid?
402
+ def setuid?; FileTest.setuid?(self); end
403
+
404
+ # See FileTest::socket?
405
+ def socket?; FileTest.socket?(self); end
406
+
407
+ # See FileTest::sticky?
408
+ def sticky?; FileTest.sticky?(self); end
409
+
410
+ # See FileTest::symlink?
411
+ def symlink?; FileTest.symlink?(self); end
412
+
413
+ # See FileTest::world_readable?
414
+ def world_readable?; FileTest.world_readable?(self); end
415
+
416
+ # See FileTest::world_writable?
417
+ def world_writable?; FileTest.world_writable?(self); end
418
+
419
+ # See FileTest::writable?
420
+ def writable?; FileTest.writable?(self); end
421
+
422
+ # See FileTest::writable_real?
423
+ def writable_real?; FileTest.writable_real?(self); end
424
+
425
+ # See FileTest::zero?
426
+ def zero?; FileTest.zero?(self); end
427
+ end
428
+
429
+ class Pathname
430
+ # See File::atime
431
+ def atime; File.atime(self); end
432
+
433
+ # See File::ctime
434
+ def ctime; File.ctime(self); end
435
+
436
+ # See File::ftype
437
+ def ftype; File.ftype(self); end
438
+
439
+ # See File::lstat
440
+ def lstat; File.lstat(self); end
441
+
442
+ # See File::mtime
443
+ def mtime; File.mtime(self); end
444
+
445
+ # See File::stat
446
+ def stat; File.stat(self); end
447
+
448
+ # See File::utime
449
+ def utime(atime, mtime); File.utime(self, atime, mtime); end
450
+ end
451
+
452
+ class Pathname
453
+ # See File::join
454
+ def self.join(*parts); File.join(*parts.reject {|p| p.empty? }).to_path; end
455
+
456
+ # See File::basename
457
+ def basename; File.basename(self).to_path; end
458
+
459
+ # See File::chmod
460
+ def chmod(mode); File.chmod(mode, self); end
461
+
462
+ # See File::chown
463
+ def chown(owner, group); File.chown(owner, group, self); end
464
+
465
+ # See File::dirname
466
+ def dirname; File.dirname(self).to_path; end
467
+
468
+ # See File::expand_path
469
+ def expand_path(from = nil); File.expand_path(self, from).to_path; end
470
+
471
+ # See File::extname
472
+ def extname; File.extname(self); end
473
+
474
+ # See File::fnmatch
475
+ def fnmatch?(pat, flags = 0); File.fnmatch(pat, self, flags); end
476
+
477
+ # See File::join
478
+ def join(*parts); self.class.join(self, *parts); end
479
+
480
+ # See File::lchmod
481
+ def lchmod(mode); File.lchmod(mode, self); end
482
+
483
+ # See File::lchown
484
+ def lchown(owner, group); File.lchown(owner, group, self); end
485
+
486
+ # See File::link
487
+ def link(to); File.link(self, to); end
488
+
489
+ # See File::open
490
+ def open(mode = 'r', perm = nil, &blk); File.open(self, mode, perm, &blk); end
491
+
492
+ # See File::readlink
493
+ def readlink; File.readlink(self).to_path; end
494
+
495
+ # See File::rename
496
+ def rename(to); File.rename(self, to); replace(to); end
497
+
498
+ # See File::size
499
+ def size; File.size(self); end
500
+
501
+ # See File::size?
502
+ def size?; File.size?(self); end
503
+
504
+ # See File::split
505
+ def split; File.split(self).map {|part| part.to_path }; end
506
+
507
+ # See File::symlink
508
+ def symlink(to); File.symlink(self, to); end
509
+
510
+ # See File::truncate
511
+ def truncate; File.truncate(self); end
512
+ end
513
+
514
+ class Pathname
515
+ # See FileUtils::mkpath
516
+ def mkpath; FileUtils.mkpath(self).to_path; end
517
+
518
+ # See FileUtils::rmtree
519
+ def rmtree; FileUtils.rmtree(self).first.to_path; end
520
+
521
+ # See FileUtils::touch
522
+ def touch; FileUtils.touch(self).first.to_path; end
523
+ end
524
+
525
+ class Pathname
526
+ # See IO::each_line
527
+ def each_line(sep = $/, &blk); IO.foreach(self, sep, &blk); end
528
+
529
+ # See IO::read
530
+ def read(len = nil, off = 0); IO.read(self, len, off); end
531
+
532
+ # See IO::readlines
533
+ def readlines(sep = $/); IO.readlines(self, sep); end
534
+
535
+ # See IO::sysopen
536
+ def sysopen(mode = 'r', perm = nil); IO.sysopen(self, mode, perm); end
537
+ end
538
+
539
+ class Pathname
540
+ # See Find::find
541
+ def find; Find.find(self) {|path| yield path.to_path }; end
542
+ end
543
+
544
+ class Pathname
545
+ class << self
546
+ alias getwd pwd
547
+ end
548
+
549
+ alias absolute expand_path
550
+ alias delete unlink
551
+ alias exist? exists?
552
+ alias fnmatch fnmatch?
553
+ end
554
+
555
+ class String
556
+ #
557
+ # Converts the string directly to a pathname.
558
+ #
559
+ def to_path
560
+ Pathname.new(self)
561
+ end
562
+ end
563
+
564
+ module Kernel
565
+ #
566
+ # Allows construction of a Pathname by using the class name as a method.
567
+ #
568
+ # This really ought to be deprecated due to String#to_path.
569
+ #
570
+ def Pathname(path)
571
+ Pathname.new(path)
572
+ end
573
+ end