pathname3 1.2.3

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 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