rubysl-pathname 1.0.0

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