rubysl-pathname 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8f18649e8f93aac59bb0550a713693ba6fbff5cc
4
+ data.tar.gz: 3b1d2a5bd815c6c0b319fd8d23262aaaa7081043
5
+ SHA512:
6
+ metadata.gz: 158aa06fe4756074097832ce6090a2156b60d8ed5431e14a722eff594c2527bbf8e4ae63224666ee2fbe8cb39eec4d3953c96e99b97f80c2c6ff64f0565a21f2
7
+ data.tar.gz: 2823c67acff7ceefcb2e7e824d05cbc626470251026643db6008b514e688c7a8f6f1954fd80a297557359aba068111b076e377c32c2892daa0b2c9fd1cc60ae0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem update --system
4
+ - gem --version
5
+ - gem install rubysl-bundler
6
+ script: bundle exec mspec spec
7
+ rvm:
8
+ - rbx-nightly-18mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubysl-pathname.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2013, Brian Shirai
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ 3. Neither the name of the library nor the names of its contributors may be
13
+ used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
20
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
25
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Rubysl::Pathname
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rubysl-pathname'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rubysl-pathname
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/pathname.rb ADDED
@@ -0,0 +1 @@
1
+ require "rubysl/pathname"
@@ -0,0 +1,2 @@
1
+ require "rubysl/pathname/pathname"
2
+ require "rubysl/pathname/version"
@@ -0,0 +1,1062 @@
1
+ #
2
+ # = pathname.rb
3
+ #
4
+ # Object-Oriented Pathname Class
5
+ #
6
+ # Author:: Tanaka Akira <akr@m17n.org>
7
+ # Documentation:: Author and Gavin Sinclair
8
+ #
9
+ # For documentation, see class Pathname.
10
+ #
11
+ # <tt>pathname.rb</tt> is distributed with Ruby since 1.8.0.
12
+ #
13
+
14
+ #
15
+ # == Pathname
16
+ #
17
+ # Pathname represents a pathname which locates a file in a filesystem.
18
+ # The pathname depends on OS: Unix, Windows, etc.
19
+ # Pathname library works with pathnames of local OS.
20
+ # However non-Unix pathnames are supported experimentally.
21
+ #
22
+ # It does not represent the file itself.
23
+ # A Pathname can be relative or absolute. It's not until you try to
24
+ # reference the file that it even matters whether the file exists or not.
25
+ #
26
+ # Pathname is immutable. It has no method for destructive update.
27
+ #
28
+ # The value of this class is to manipulate file path information in a neater
29
+ # way than standard Ruby provides. The examples below demonstrate the
30
+ # difference. *All* functionality from File, FileTest, and some from Dir and
31
+ # FileUtils is included, in an unsurprising way. It is essentially a facade for
32
+ # all of these, and more.
33
+ #
34
+ # == Examples
35
+ #
36
+ # === Example 1: Using Pathname
37
+ #
38
+ # require 'pathname'
39
+ # p = Pathname.new("/usr/bin/ruby")
40
+ # size = p.size # 27662
41
+ # isdir = p.directory? # false
42
+ # dir = p.dirname # Pathname:/usr/bin
43
+ # base = p.basename # Pathname:ruby
44
+ # dir, base = p.split # [Pathname:/usr/bin, Pathname:ruby]
45
+ # data = p.read
46
+ # p.open { |f| _ }
47
+ # p.each_line { |line| _ }
48
+ #
49
+ # === Example 2: Using standard Ruby
50
+ #
51
+ # p = "/usr/bin/ruby"
52
+ # size = File.size(p) # 27662
53
+ # isdir = File.directory?(p) # false
54
+ # dir = File.dirname(p) # "/usr/bin"
55
+ # base = File.basename(p) # "ruby"
56
+ # dir, base = File.split(p) # ["/usr/bin", "ruby"]
57
+ # data = File.read(p)
58
+ # File.open(p) { |f| _ }
59
+ # File.foreach(p) { |line| _ }
60
+ #
61
+ # === Example 3: Special features
62
+ #
63
+ # p1 = Pathname.new("/usr/lib") # Pathname:/usr/lib
64
+ # p2 = p1 + "ruby/1.8" # Pathname:/usr/lib/ruby/1.8
65
+ # p3 = p1.parent # Pathname:/usr
66
+ # p4 = p2.relative_path_from(p3) # Pathname:lib/ruby/1.8
67
+ # pwd = Pathname.pwd # Pathname:/home/gavin
68
+ # pwd.absolute? # true
69
+ # p5 = Pathname.new "." # Pathname:.
70
+ # p5 = p5 + "music/../articles" # Pathname:music/../articles
71
+ # p5.cleanpath # Pathname:articles
72
+ # p5.realpath # Pathname:/home/gavin/articles
73
+ # p5.children # [Pathname:/home/gavin/articles/linux, ...]
74
+ #
75
+ # == Breakdown of functionality
76
+ #
77
+ # === Core methods
78
+ #
79
+ # These methods are effectively manipulating a String, because that's all a path
80
+ # is. Except for #mountpoint?, #children, and #realpath, they don't access the
81
+ # filesystem.
82
+ #
83
+ # - +
84
+ # - #join
85
+ # - #parent
86
+ # - #root?
87
+ # - #absolute?
88
+ # - #relative?
89
+ # - #relative_path_from
90
+ # - #each_filename
91
+ # - #cleanpath
92
+ # - #realpath
93
+ # - #children
94
+ # - #mountpoint?
95
+ #
96
+ # === File status predicate methods
97
+ #
98
+ # These methods are a facade for FileTest:
99
+ # - #blockdev?
100
+ # - #chardev?
101
+ # - #directory?
102
+ # - #executable?
103
+ # - #executable_real?
104
+ # - #exist?
105
+ # - #file?
106
+ # - #grpowned?
107
+ # - #owned?
108
+ # - #pipe?
109
+ # - #readable?
110
+ # - #world_readable?
111
+ # - #readable_real?
112
+ # - #setgid?
113
+ # - #setuid?
114
+ # - #size
115
+ # - #size?
116
+ # - #socket?
117
+ # - #sticky?
118
+ # - #symlink?
119
+ # - #writable?
120
+ # - #world_writable?
121
+ # - #writable_real?
122
+ # - #zero?
123
+ #
124
+ # === File property and manipulation methods
125
+ #
126
+ # These methods are a facade for File:
127
+ # - #atime
128
+ # - #ctime
129
+ # - #mtime
130
+ # - #chmod(mode)
131
+ # - #lchmod(mode)
132
+ # - #chown(owner, group)
133
+ # - #lchown(owner, group)
134
+ # - #fnmatch(pattern, *args)
135
+ # - #fnmatch?(pattern, *args)
136
+ # - #ftype
137
+ # - #make_link(old)
138
+ # - #open(*args, &block)
139
+ # - #readlink
140
+ # - #rename(to)
141
+ # - #stat
142
+ # - #lstat
143
+ # - #make_symlink(old)
144
+ # - #truncate(length)
145
+ # - #utime(atime, mtime)
146
+ # - #basename(*args)
147
+ # - #dirname
148
+ # - #extname
149
+ # - #expand_path(*args)
150
+ # - #split
151
+ #
152
+ # === Directory methods
153
+ #
154
+ # These methods are a facade for Dir:
155
+ # - Pathname.glob(*args)
156
+ # - Pathname.getwd / Pathname.pwd
157
+ # - #rmdir
158
+ # - #entries
159
+ # - #each_entry(&block)
160
+ # - #mkdir(*args)
161
+ # - #opendir(*args)
162
+ #
163
+ # === IO
164
+ #
165
+ # These methods are a facade for IO:
166
+ # - #each_line(*args, &block)
167
+ # - #read(*args)
168
+ # - #readlines(*args)
169
+ # - #sysopen(*args)
170
+ #
171
+ # === Utilities
172
+ #
173
+ # These methods are a mixture of Find, FileUtils, and others:
174
+ # - #find(&block)
175
+ # - #mkpath
176
+ # - #rmtree
177
+ # - #unlink / #delete
178
+ #
179
+ #
180
+ # == Method documentation
181
+ #
182
+ # As the above section shows, most of the methods in Pathname are facades. The
183
+ # documentation for these methods generally just says, for instance, "See
184
+ # FileTest.writable?", as you should be familiar with the original method
185
+ # anyway, and its documentation (e.g. through +ri+) will contain more
186
+ # information. In some cases, a brief description will follow.
187
+ #
188
+ class Pathname
189
+
190
+ # :stopdoc:
191
+ if RUBY_VERSION < "1.9"
192
+ TO_PATH = :to_str
193
+ else
194
+ # to_path is implemented so Pathname objects are usable with File.open, etc.
195
+ TO_PATH = :to_path
196
+ end
197
+ # :startdoc:
198
+
199
+ #
200
+ # Create a Pathname object from the given String (or String-like object).
201
+ # If +path+ contains a NUL character (<tt>\0</tt>), an ArgumentError is raised.
202
+ #
203
+ def initialize(path)
204
+ path = path.__send__(TO_PATH) if path.respond_to? TO_PATH
205
+ @path = path.dup
206
+
207
+ if /\0/ =~ @path
208
+ raise ArgumentError, "pathname contains \\0: #{@path.inspect}"
209
+ end
210
+
211
+ self.taint if @path.tainted?
212
+ end
213
+
214
+ def freeze() super; @path.freeze; self end
215
+ def taint() super; @path.taint; self end
216
+ def untaint() super; @path.untaint; self end
217
+
218
+ #
219
+ # Compare this pathname with +other+. The comparison is string-based.
220
+ # Be aware that two different paths (<tt>foo.txt</tt> and <tt>./foo.txt</tt>)
221
+ # can refer to the same file.
222
+ #
223
+ def ==(other)
224
+ return false unless Pathname === other
225
+ other.to_s == @path
226
+ end
227
+ alias === ==
228
+ alias eql? ==
229
+
230
+ # Provides for comparing pathnames, case-sensitively.
231
+ def <=>(other)
232
+ return nil unless Pathname === other
233
+ @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
234
+ end
235
+
236
+ def hash # :nodoc:
237
+ @path.hash
238
+ end
239
+
240
+ # Return the path as a String.
241
+ def to_s
242
+ @path.dup
243
+ end
244
+
245
+ # to_path is implemented so Pathname objects are usable with File.open, etc.
246
+ alias_method TO_PATH, :to_s
247
+
248
+ def inspect # :nodoc:
249
+ "#<#{self.class}:#{@path}>"
250
+ end
251
+
252
+ # Return a pathname which is substituted by String#sub.
253
+ def sub(pattern, *rest, &block)
254
+ self.class.new(@path.sub(pattern, *rest, &block))
255
+ end
256
+
257
+ if File::ALT_SEPARATOR
258
+ SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/
259
+ else
260
+ SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
261
+ end
262
+
263
+ # chop_basename(path) -> [pre-basename, basename] or nil
264
+ def chop_basename(path)
265
+ base = File.basename(path)
266
+ if /\A#{SEPARATOR_PAT}?\z/o =~ base
267
+ return nil
268
+ else
269
+ return path[0, path.rindex(base)], base
270
+ end
271
+ end
272
+ private :chop_basename
273
+
274
+ # split_names(path) -> prefix, [name, ...]
275
+ def split_names(path)
276
+ names = []
277
+ while r = chop_basename(path)
278
+ path, basename = r
279
+ names.unshift basename
280
+ end
281
+ return path, names
282
+ end
283
+ private :split_names
284
+
285
+ def prepend_prefix(prefix, relpath)
286
+ if relpath.empty?
287
+ File.dirname(prefix)
288
+ elsif /#{SEPARATOR_PAT}/o =~ prefix
289
+ prefix = File.dirname(prefix)
290
+ prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
291
+ prefix + relpath
292
+ else
293
+ prefix + relpath
294
+ end
295
+ end
296
+ private :prepend_prefix
297
+
298
+ # Returns clean pathname of +self+ with consecutive slashes and useless dots
299
+ # removed. The filesystem is not accessed.
300
+ #
301
+ # If +consider_symlink+ is +true+, then a more conservative algorithm is used
302
+ # to avoid breaking symbolic linkages. This may retain more <tt>..</tt>
303
+ # entries than absolutely necessary, but without accessing the filesystem,
304
+ # this can't be avoided. See #realpath.
305
+ #
306
+ def cleanpath(consider_symlink=false)
307
+ if consider_symlink
308
+ cleanpath_conservative
309
+ else
310
+ cleanpath_aggressive
311
+ end
312
+ end
313
+
314
+ #
315
+ # Clean the path simply by resolving and removing excess "." and ".." entries.
316
+ # Nothing more, nothing less.
317
+ #
318
+ def cleanpath_aggressive
319
+ path = @path
320
+ names = []
321
+ pre = path
322
+ while r = chop_basename(pre)
323
+ pre, base = r
324
+ case base
325
+ when '.'
326
+ when '..'
327
+ names.unshift base
328
+ else
329
+ if names[0] == '..'
330
+ names.shift
331
+ else
332
+ names.unshift base
333
+ end
334
+ end
335
+ end
336
+ if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
337
+ names.shift while names[0] == '..'
338
+ end
339
+ self.class.new(prepend_prefix(pre, File.join(*names)))
340
+ end
341
+ private :cleanpath_aggressive
342
+
343
+ # has_trailing_separator?(path) -> bool
344
+ def has_trailing_separator?(path)
345
+ if r = chop_basename(path)
346
+ pre, basename = r
347
+ pre.length + basename.length < path.length
348
+ else
349
+ false
350
+ end
351
+ end
352
+ private :has_trailing_separator?
353
+
354
+ # add_trailing_separator(path) -> path
355
+ def add_trailing_separator(path)
356
+ if File.basename(path + 'a') == 'a'
357
+ path
358
+ else
359
+ File.join(path, "") # xxx: Is File.join is appropriate to add separator?
360
+ end
361
+ end
362
+ private :add_trailing_separator
363
+
364
+ def del_trailing_separator(path)
365
+ if r = chop_basename(path)
366
+ pre, basename = r
367
+ pre + basename
368
+ elsif /#{SEPARATOR_PAT}+\z/o =~ path
369
+ $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
370
+ else
371
+ path
372
+ end
373
+ end
374
+ private :del_trailing_separator
375
+
376
+ def cleanpath_conservative
377
+ path = @path
378
+ names = []
379
+ pre = path
380
+ while r = chop_basename(pre)
381
+ pre, base = r
382
+ names.unshift base if base != '.'
383
+ end
384
+ if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
385
+ names.shift while names[0] == '..'
386
+ end
387
+ if names.empty?
388
+ self.class.new(File.dirname(pre))
389
+ else
390
+ if names.last != '..' && File.basename(path) == '.'
391
+ names << '.'
392
+ end
393
+ result = prepend_prefix(pre, File.join(*names))
394
+ if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
395
+ self.class.new(add_trailing_separator(result))
396
+ else
397
+ self.class.new(result)
398
+ end
399
+ end
400
+ end
401
+ private :cleanpath_conservative
402
+
403
+ def realpath_rec(prefix, unresolved, h)
404
+ resolved = []
405
+ until unresolved.empty?
406
+ n = unresolved.shift
407
+ if n == '.'
408
+ next
409
+ elsif n == '..'
410
+ resolved.pop
411
+ else
412
+ path = prepend_prefix(prefix, File.join(*(resolved + [n])))
413
+ if h.include? path
414
+ if h[path] == :resolving
415
+ raise Errno::ELOOP.new(path)
416
+ else
417
+ prefix, *resolved = h[path]
418
+ end
419
+ else
420
+ s = File.lstat(path)
421
+ if s.symlink?
422
+ h[path] = :resolving
423
+ link_prefix, link_names = split_names(File.readlink(path))
424
+ if link_prefix == ''
425
+ prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h)
426
+ else
427
+ prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h)
428
+ end
429
+ else
430
+ resolved << n
431
+ h[path] = [prefix, *resolved]
432
+ end
433
+ end
434
+ end
435
+ end
436
+ return prefix, *resolved
437
+ end
438
+ private :realpath_rec
439
+
440
+ #
441
+ # Returns a real (absolute) pathname of +self+ in the actual filesystem.
442
+ # The real pathname doesn't contain symlinks or useless dots.
443
+ #
444
+ # No arguments should be given; the old behaviour is *obsoleted*.
445
+ #
446
+ def realpath
447
+ path = @path
448
+ prefix, names = split_names(path)
449
+ if prefix == ''
450
+ prefix, names2 = split_names(Dir.pwd)
451
+ names = names2 + names
452
+ end
453
+ prefix, *names = realpath_rec(prefix, names, {})
454
+ self.class.new(prepend_prefix(prefix, File.join(*names)))
455
+ end
456
+
457
+ # #parent returns the parent directory.
458
+ #
459
+ # This is same as <tt>self + '..'</tt>.
460
+ def parent
461
+ self + '..'
462
+ end
463
+
464
+ # #mountpoint? returns +true+ if <tt>self</tt> points to a mountpoint.
465
+ def mountpoint?
466
+ begin
467
+ stat1 = self.lstat
468
+ stat2 = self.parent.lstat
469
+ stat1.dev == stat2.dev && stat1.ino == stat2.ino ||
470
+ stat1.dev != stat2.dev
471
+ rescue Errno::ENOENT
472
+ false
473
+ end
474
+ end
475
+
476
+ #
477
+ # #root? is a predicate for root directories. I.e. it returns +true+ if the
478
+ # pathname consists of consecutive slashes.
479
+ #
480
+ # It doesn't access actual filesystem. So it may return +false+ for some
481
+ # pathnames which points to roots such as <tt>/usr/..</tt>.
482
+ #
483
+ def root?
484
+ !!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path)
485
+ end
486
+
487
+ # Predicate method for testing whether a path is absolute.
488
+ # It returns +true+ if the pathname begins with a slash.
489
+ def absolute?
490
+ !relative?
491
+ end
492
+
493
+ # The opposite of #absolute?
494
+ def relative?
495
+ path = @path
496
+ while r = chop_basename(path)
497
+ path, basename = r
498
+ end
499
+ path == ''
500
+ end
501
+
502
+ #
503
+ # Iterates over each component of the path.
504
+ #
505
+ # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
506
+ # # yields "usr", "bin", and "ruby".
507
+ #
508
+ def each_filename # :yield: filename
509
+ prefix, names = split_names(@path)
510
+ names.each {|filename| yield filename }
511
+ nil
512
+ end
513
+
514
+ # Iterates over and yields a new Pathname object
515
+ # for each element in the given path in descending order.
516
+ #
517
+ # Pathname.new('/path/to/some/file.rb').descend {|v| p v}
518
+ # #<Pathname:/>
519
+ # #<Pathname:/path>
520
+ # #<Pathname:/path/to>
521
+ # #<Pathname:/path/to/some>
522
+ # #<Pathname:/path/to/some/file.rb>
523
+ #
524
+ # Pathname.new('path/to/some/file.rb').descend {|v| p v}
525
+ # #<Pathname:path>
526
+ # #<Pathname:path/to>
527
+ # #<Pathname:path/to/some>
528
+ # #<Pathname:path/to/some/file.rb>
529
+ #
530
+ # It doesn't access actual filesystem.
531
+ #
532
+ # This method is available since 1.8.5.
533
+ #
534
+ def descend
535
+ vs = []
536
+ ascend {|v| vs << v }
537
+ vs.reverse_each {|v| yield v }
538
+ nil
539
+ end
540
+
541
+ # Iterates over and yields a new Pathname object
542
+ # for each element in the given path in ascending order.
543
+ #
544
+ # Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
545
+ # #<Pathname:/path/to/some/file.rb>
546
+ # #<Pathname:/path/to/some>
547
+ # #<Pathname:/path/to>
548
+ # #<Pathname:/path>
549
+ # #<Pathname:/>
550
+ #
551
+ # Pathname.new('path/to/some/file.rb').ascend {|v| p v}
552
+ # #<Pathname:path/to/some/file.rb>
553
+ # #<Pathname:path/to/some>
554
+ # #<Pathname:path/to>
555
+ # #<Pathname:path>
556
+ #
557
+ # It doesn't access actual filesystem.
558
+ #
559
+ # This method is available since 1.8.5.
560
+ #
561
+ def ascend
562
+ path = @path
563
+ yield self
564
+ while r = chop_basename(path)
565
+ path, name = r
566
+ break if path.empty?
567
+ yield self.class.new(del_trailing_separator(path))
568
+ end
569
+ end
570
+
571
+ #
572
+ # Pathname#+ appends a pathname fragment to this one to produce a new Pathname
573
+ # object.
574
+ #
575
+ # p1 = Pathname.new("/usr") # Pathname:/usr
576
+ # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby
577
+ # p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd
578
+ #
579
+ # This method doesn't access the file system; it is pure string manipulation.
580
+ #
581
+ def +(other)
582
+ other = Pathname.new(other) unless Pathname === other
583
+ Pathname.new(plus(@path, other.to_s))
584
+ end
585
+
586
+ def plus(path1, path2) # -> path
587
+ prefix2 = path2
588
+ index_list2 = []
589
+ basename_list2 = []
590
+ while r2 = chop_basename(prefix2)
591
+ prefix2, basename2 = r2
592
+ index_list2.unshift prefix2.length
593
+ basename_list2.unshift basename2
594
+ end
595
+ return path2 if prefix2 != ''
596
+ prefix1 = path1
597
+ while true
598
+ while !basename_list2.empty? && basename_list2.first == '.'
599
+ index_list2.shift
600
+ basename_list2.shift
601
+ end
602
+ break unless r1 = chop_basename(prefix1)
603
+ prefix1, basename1 = r1
604
+ next if basename1 == '.'
605
+ if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
606
+ prefix1 = prefix1 + basename1
607
+ break
608
+ end
609
+ index_list2.shift
610
+ basename_list2.shift
611
+ end
612
+ r1 = chop_basename(prefix1)
613
+ if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)
614
+ while !basename_list2.empty? && basename_list2.first == '..'
615
+ index_list2.shift
616
+ basename_list2.shift
617
+ end
618
+ end
619
+ if !basename_list2.empty?
620
+ suffix2 = path2[index_list2.first..-1]
621
+ r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
622
+ else
623
+ r1 ? prefix1 : File.dirname(prefix1)
624
+ end
625
+ end
626
+ private :plus
627
+
628
+ #
629
+ # Pathname#join joins pathnames.
630
+ #
631
+ # <tt>path0.join(path1, ..., pathN)</tt> is the same as
632
+ # <tt>path0 + path1 + ... + pathN</tt>.
633
+ #
634
+ def join(*args)
635
+ args.unshift self
636
+ result = args.pop
637
+ result = Pathname.new(result) unless Pathname === result
638
+ return result if result.absolute?
639
+ args.reverse_each {|arg|
640
+ arg = Pathname.new(arg) unless Pathname === arg
641
+ result = arg + result
642
+ return result if result.absolute?
643
+ }
644
+ result
645
+ end
646
+
647
+ #
648
+ # Returns the children of the directory (files and subdirectories, not
649
+ # recursive) as an array of Pathname objects. By default, the returned
650
+ # pathnames will have enough information to access the files. If you set
651
+ # +with_directory+ to +false+, then the returned pathnames will contain the
652
+ # filename only.
653
+ #
654
+ # For example:
655
+ # p = Pathname("/usr/lib/ruby/1.8")
656
+ # p.children
657
+ # # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
658
+ # Pathname:/usr/lib/ruby/1.8/Env.rb,
659
+ # Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
660
+ # p.children(false)
661
+ # # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ]
662
+ #
663
+ # Note that the result never contain the entries <tt>.</tt> and <tt>..</tt> in
664
+ # the directory because they are not children.
665
+ #
666
+ # This method has existed since 1.8.1.
667
+ #
668
+ def children(with_directory=true)
669
+ with_directory = false if @path == '.'
670
+ result = []
671
+ Dir.foreach(@path) {|e|
672
+ next if e == '.' || e == '..'
673
+ if with_directory
674
+ result << self.class.new(File.join(@path, e))
675
+ else
676
+ result << self.class.new(e)
677
+ end
678
+ }
679
+ result
680
+ end
681
+
682
+ #
683
+ # #relative_path_from returns a relative path from the argument to the
684
+ # receiver. If +self+ is absolute, the argument must be absolute too. If
685
+ # +self+ is relative, the argument must be relative too.
686
+ #
687
+ # #relative_path_from doesn't access the filesystem. It assumes no symlinks.
688
+ #
689
+ # ArgumentError is raised when it cannot find a relative path.
690
+ #
691
+ # This method has existed since 1.8.1.
692
+ #
693
+ def relative_path_from(base_directory)
694
+ dest_directory = self.cleanpath.to_s
695
+ base_directory = base_directory.cleanpath.to_s
696
+ dest_prefix = dest_directory
697
+ dest_names = []
698
+ while r = chop_basename(dest_prefix)
699
+ dest_prefix, basename = r
700
+ dest_names.unshift basename if basename != '.'
701
+ end
702
+ base_prefix = base_directory
703
+ base_names = []
704
+ while r = chop_basename(base_prefix)
705
+ base_prefix, basename = r
706
+ base_names.unshift basename if basename != '.'
707
+ end
708
+ if dest_prefix != base_prefix
709
+ raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
710
+ end
711
+ while !dest_names.empty? &&
712
+ !base_names.empty? &&
713
+ dest_names.first == base_names.first
714
+ dest_names.shift
715
+ base_names.shift
716
+ end
717
+ if base_names.include? '..'
718
+ raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
719
+ end
720
+ base_names.fill('..')
721
+ relpath_names = base_names + dest_names
722
+ if relpath_names.empty?
723
+ Pathname.new('.')
724
+ else
725
+ Pathname.new(File.join(*relpath_names))
726
+ end
727
+ end
728
+ end
729
+
730
+ class Pathname # * IO *
731
+ #
732
+ # #each_line iterates over the line in the file. It yields a String object
733
+ # for each line.
734
+ #
735
+ # This method has existed since 1.8.1.
736
+ #
737
+ def each_line(*args, &block) # :yield: line
738
+ IO.foreach(@path, *args, &block)
739
+ end
740
+
741
+ # Pathname#foreachline is *obsoleted* at 1.8.1. Use #each_line.
742
+ def foreachline(*args, &block)
743
+ warn "Pathname#foreachline is obsoleted. Use Pathname#each_line."
744
+ each_line(*args, &block)
745
+ end
746
+
747
+ # See <tt>IO.read</tt>. Returns all the bytes from the file, or the first +N+
748
+ # if specified.
749
+ def read(*args) IO.read(@path, *args) end
750
+
751
+ # See <tt>IO.readlines</tt>. Returns all the lines from the file.
752
+ def readlines(*args) IO.readlines(@path, *args) end
753
+
754
+ # See <tt>IO.sysopen</tt>.
755
+ def sysopen(*args) IO.sysopen(@path, *args) end
756
+ end
757
+
758
+
759
+ class Pathname # * File *
760
+
761
+ # See <tt>File.atime</tt>. Returns last access time.
762
+ def atime() File.atime(@path) end
763
+
764
+ # See <tt>File.ctime</tt>. Returns last (directory entry, not file) change time.
765
+ def ctime() File.ctime(@path) end
766
+
767
+ # See <tt>File.mtime</tt>. Returns last modification time.
768
+ def mtime() File.mtime(@path) end
769
+
770
+ # See <tt>File.chmod</tt>. Changes permissions.
771
+ def chmod(mode) File.chmod(mode, @path) end
772
+
773
+ # See <tt>File.lchmod</tt>.
774
+ def lchmod(mode) File.lchmod(mode, @path) end
775
+
776
+ # See <tt>File.chown</tt>. Change owner and group of file.
777
+ def chown(owner, group) File.chown(owner, group, @path) end
778
+
779
+ # See <tt>File.lchown</tt>.
780
+ def lchown(owner, group) File.lchown(owner, group, @path) end
781
+
782
+ # See <tt>File.fnmatch</tt>. Return +true+ if the receiver matches the given
783
+ # pattern.
784
+ def fnmatch(pattern, *args) File.fnmatch(pattern, @path, *args) end
785
+
786
+ # See <tt>File.fnmatch?</tt> (same as #fnmatch).
787
+ def fnmatch?(pattern, *args) File.fnmatch?(pattern, @path, *args) end
788
+
789
+ # See <tt>File.ftype</tt>. Returns "type" of file ("file", "directory",
790
+ # etc).
791
+ def ftype() File.ftype(@path) end
792
+
793
+ # See <tt>File.link</tt>. Creates a hard link.
794
+ def make_link(old) File.link(old, @path) end
795
+
796
+ # See <tt>File.open</tt>. Opens the file for reading or writing.
797
+ def open(*args, &block) # :yield: file
798
+ File.open(@path, *args, &block)
799
+ end
800
+
801
+ # See <tt>File.readlink</tt>. Read symbolic link.
802
+ def readlink() self.class.new(File.readlink(@path)) end
803
+
804
+ # See <tt>File.rename</tt>. Rename the file.
805
+ def rename(to) File.rename(@path, to) end
806
+
807
+ # See <tt>File.stat</tt>. Returns a <tt>File::Stat</tt> object.
808
+ def stat() File.stat(@path) end
809
+
810
+ # See <tt>File.lstat</tt>.
811
+ def lstat() File.lstat(@path) end
812
+
813
+ # See <tt>File.symlink</tt>. Creates a symbolic link.
814
+ def make_symlink(old) File.symlink(old, @path) end
815
+
816
+ # See <tt>File.truncate</tt>. Truncate the file to +length+ bytes.
817
+ def truncate(length) File.truncate(@path, length) end
818
+
819
+ # See <tt>File.utime</tt>. Update the access and modification times.
820
+ def utime(atime, mtime) File.utime(atime, mtime, @path) end
821
+
822
+ # See <tt>File.basename</tt>. Returns the last component of the path.
823
+ def basename(*args) self.class.new(File.basename(@path, *args)) end
824
+
825
+ # See <tt>File.dirname</tt>. Returns all but the last component of the path.
826
+ def dirname() self.class.new(File.dirname(@path)) end
827
+
828
+ # See <tt>File.extname</tt>. Returns the file's extension.
829
+ def extname() File.extname(@path) end
830
+
831
+ # See <tt>File.expand_path</tt>.
832
+ def expand_path(*args) self.class.new(File.expand_path(@path, *args)) end
833
+
834
+ # See <tt>File.split</tt>. Returns the #dirname and the #basename in an
835
+ # Array.
836
+ def split() File.split(@path).map {|f| self.class.new(f) } end
837
+
838
+ # Pathname#link is confusing and *obsoleted* because the receiver/argument
839
+ # order is inverted to corresponding system call.
840
+ def link(old)
841
+ warn 'Pathname#link is obsoleted. Use Pathname#make_link.'
842
+ File.link(old, @path)
843
+ end
844
+
845
+ # Pathname#symlink is confusing and *obsoleted* because the receiver/argument
846
+ # order is inverted to corresponding system call.
847
+ def symlink(old)
848
+ warn 'Pathname#symlink is obsoleted. Use Pathname#make_symlink.'
849
+ File.symlink(old, @path)
850
+ end
851
+ end
852
+
853
+
854
+ class Pathname # * FileTest *
855
+
856
+ # See <tt>FileTest.blockdev?</tt>.
857
+ def blockdev?() FileTest.blockdev?(@path) end
858
+
859
+ # See <tt>FileTest.chardev?</tt>.
860
+ def chardev?() FileTest.chardev?(@path) end
861
+
862
+ # See <tt>FileTest.executable?</tt>.
863
+ def executable?() FileTest.executable?(@path) end
864
+
865
+ # See <tt>FileTest.executable_real?</tt>.
866
+ def executable_real?() FileTest.executable_real?(@path) end
867
+
868
+ # See <tt>FileTest.exist?</tt>.
869
+ def exist?() FileTest.exist?(@path) end
870
+
871
+ # See <tt>FileTest.grpowned?</tt>.
872
+ def grpowned?() FileTest.grpowned?(@path) end
873
+
874
+ # See <tt>FileTest.directory?</tt>.
875
+ def directory?() FileTest.directory?(@path) end
876
+
877
+ # See <tt>FileTest.file?</tt>.
878
+ def file?() FileTest.file?(@path) end
879
+
880
+ # See <tt>FileTest.pipe?</tt>.
881
+ def pipe?() FileTest.pipe?(@path) end
882
+
883
+ # See <tt>FileTest.socket?</tt>.
884
+ def socket?() FileTest.socket?(@path) end
885
+
886
+ # See <tt>FileTest.owned?</tt>.
887
+ def owned?() FileTest.owned?(@path) end
888
+
889
+ # See <tt>FileTest.readable?</tt>.
890
+ def readable?() FileTest.readable?(@path) end
891
+
892
+ # See <tt>FileTest.world_readable?</tt>.
893
+ def world_readable?() FileTest.world_readable?(@path) end
894
+
895
+ # See <tt>FileTest.readable_real?</tt>.
896
+ def readable_real?() FileTest.readable_real?(@path) end
897
+
898
+ # See <tt>FileTest.setuid?</tt>.
899
+ def setuid?() FileTest.setuid?(@path) end
900
+
901
+ # See <tt>FileTest.setgid?</tt>.
902
+ def setgid?() FileTest.setgid?(@path) end
903
+
904
+ # See <tt>FileTest.size</tt>.
905
+ def size() FileTest.size(@path) end
906
+
907
+ # See <tt>FileTest.size?</tt>.
908
+ def size?() FileTest.size?(@path) end
909
+
910
+ # See <tt>FileTest.sticky?</tt>.
911
+ def sticky?() FileTest.sticky?(@path) end
912
+
913
+ # See <tt>FileTest.symlink?</tt>.
914
+ def symlink?() FileTest.symlink?(@path) end
915
+
916
+ # See <tt>FileTest.writable?</tt>.
917
+ def writable?() FileTest.writable?(@path) end
918
+
919
+ # See <tt>FileTest.world_writable?</tt>.
920
+ def world_writable?() FileTest.world_writable?(@path) end
921
+
922
+ # See <tt>FileTest.writable_real?</tt>.
923
+ def writable_real?() FileTest.writable_real?(@path) end
924
+
925
+ # See <tt>FileTest.zero?</tt>.
926
+ def zero?() FileTest.zero?(@path) end
927
+ end
928
+
929
+
930
+ class Pathname # * Dir *
931
+ # See <tt>Dir.glob</tt>. Returns or yields Pathname objects.
932
+ def Pathname.glob(*args) # :yield: p
933
+ if block_given?
934
+ Dir.glob(*args) {|f| yield self.new(f) }
935
+ else
936
+ Dir.glob(*args).map {|f| self.new(f) }
937
+ end
938
+ end
939
+
940
+ # See <tt>Dir.getwd</tt>. Returns the current working directory as a Pathname.
941
+ def Pathname.getwd() self.new(Dir.getwd) end
942
+ class << self; alias pwd getwd end
943
+
944
+ # Pathname#chdir is *obsoleted* at 1.8.1.
945
+ def chdir(&block)
946
+ warn "Pathname#chdir is obsoleted. Use Dir.chdir."
947
+ Dir.chdir(@path, &block)
948
+ end
949
+
950
+ # Pathname#chroot is *obsoleted* at 1.8.1.
951
+ def chroot
952
+ warn "Pathname#chroot is obsoleted. Use Dir.chroot."
953
+ Dir.chroot(@path)
954
+ end
955
+
956
+ # Return the entries (files and subdirectories) in the directory, each as a
957
+ # Pathname object.
958
+ def entries() Dir.entries(@path).map {|f| self.class.new(f) } end
959
+
960
+ # Iterates over the entries (files and subdirectories) in the directory. It
961
+ # yields a Pathname object for each entry.
962
+ #
963
+ # This method has existed since 1.8.1.
964
+ def each_entry(&block) # :yield: p
965
+ Dir.foreach(@path) {|f| yield self.class.new(f) }
966
+ end
967
+
968
+ # Pathname#dir_foreach is *obsoleted* at 1.8.1.
969
+ def dir_foreach(*args, &block)
970
+ warn "Pathname#dir_foreach is obsoleted. Use Pathname#each_entry."
971
+ each_entry(*args, &block)
972
+ end
973
+
974
+ # See <tt>Dir.mkdir</tt>. Create the referenced directory.
975
+ def mkdir(*args) Dir.mkdir(@path, *args) end
976
+
977
+ # See <tt>Dir.rmdir</tt>. Remove the referenced directory.
978
+ def rmdir() Dir.rmdir(@path) end
979
+
980
+ # See <tt>Dir.open</tt>.
981
+ def opendir(&block) # :yield: dir
982
+ Dir.open(@path, &block)
983
+ end
984
+ end
985
+
986
+
987
+ class Pathname # * Find *
988
+ #
989
+ # Pathname#find is an iterator to traverse a directory tree in a depth first
990
+ # manner. It yields a Pathname for each file under "this" directory.
991
+ #
992
+ # Since it is implemented by <tt>find.rb</tt>, <tt>Find.prune</tt> can be used
993
+ # to control the traverse.
994
+ #
995
+ # If +self+ is <tt>.</tt>, yielded pathnames begin with a filename in the
996
+ # current directory, not <tt>./</tt>.
997
+ #
998
+ def find(&block) # :yield: p
999
+ require 'find'
1000
+ if @path == '.'
1001
+ Find.find(@path) {|f| yield self.class.new(f.sub(%r{\A\./}, '')) }
1002
+ else
1003
+ Find.find(@path) {|f| yield self.class.new(f) }
1004
+ end
1005
+ end
1006
+ end
1007
+
1008
+
1009
+ class Pathname # * FileUtils *
1010
+ # See <tt>FileUtils.mkpath</tt>. Creates a full path, including any
1011
+ # intermediate directories that don't yet exist.
1012
+ def mkpath
1013
+ require 'fileutils'
1014
+ FileUtils.mkpath(@path)
1015
+ nil
1016
+ end
1017
+
1018
+ # See <tt>FileUtils.rm_r</tt>. Deletes a directory and all beneath it.
1019
+ def rmtree
1020
+ # The name "rmtree" is borrowed from File::Path of Perl.
1021
+ # File::Path provides "mkpath" and "rmtree".
1022
+ require 'fileutils'
1023
+ FileUtils.rm_r(@path)
1024
+ nil
1025
+ end
1026
+ end
1027
+
1028
+
1029
+ class Pathname # * mixed *
1030
+ # Removes a file or directory, using <tt>File.unlink</tt> or
1031
+ # <tt>Dir.unlink</tt> as necessary.
1032
+ def unlink()
1033
+ begin
1034
+ Dir.unlink @path
1035
+ rescue Errno::ENOTDIR
1036
+ File.unlink @path
1037
+ end
1038
+ end
1039
+ alias delete unlink
1040
+
1041
+ # This method is *obsoleted* at 1.8.1. Use #each_line or #each_entry.
1042
+ def foreach(*args, &block)
1043
+ warn "Pathname#foreach is obsoleted. Use each_line or each_entry."
1044
+ if FileTest.directory? @path
1045
+ # For polymorphism between Dir.foreach and IO.foreach,
1046
+ # Pathname#foreach doesn't yield Pathname object.
1047
+ Dir.foreach(@path, *args, &block)
1048
+ else
1049
+ IO.foreach(@path, *args, &block)
1050
+ end
1051
+ end
1052
+ end
1053
+
1054
+ module Kernel
1055
+ # create a pathname object.
1056
+ #
1057
+ # This method is available since 1.8.5.
1058
+ def Pathname(path) # :doc:
1059
+ Pathname.new(path)
1060
+ end
1061
+ private :Pathname
1062
+ end