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