Project

General

Profile

Feature #3478 ยป pathname.diff

Patch to implement faster pathname. - stouset (Stephen Touset), 06/25/2010 05:33 AM

View differences:

lib/pathname.rb
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
#
1
require 'fileutils'
2
require 'find'
13 3

  
14 4
#
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
#   pn = Pathname.new("/usr/bin/ruby")
40
#   size = pn.size              # 27662
41
#   isdir = pn.directory?       # false
42
#   dir  = pn.dirname           # Pathname:/usr/bin
43
#   base = pn.basename          # Pathname:ruby
44
#   dir, base = pn.split        # [Pathname:/usr/bin, Pathname:ruby]
45
#   data = pn.read
46
#   pn.open { |f| _ }
47
#   pn.each_line { |line| _ }
48
#
49
# === Example 2: Using standard Ruby
50
#
51
#   pn = "/usr/bin/ruby"
52
#   size = File.size(pn)        # 27662
53
#   isdir = File.directory?(pn) # false
54
#   dir  = File.dirname(pn)     # "/usr/bin"
55
#   base = File.basename(pn)    # "ruby"
56
#   dir, base = File.split(pn)  # ["/usr/bin", "ruby"]
57
#   data = File.read(pn)
58
#   File.open(pn) { |f| _ }
59
#   File.foreach(pn) { |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
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.
76 8
#
77
# === Core methods
78
#
79
# These methods are effectively manipulating a String, because that's
80
# all a path is.  Except for #mountpoint?, #children, #each_child,
81
# #realdirpath and #realpath, they don't access the 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
# - #realdirpath
94
# - #children
95
# - #each_child
96
# - #mountpoint?
97
#
98
# === File status predicate methods
99
#
100
# These methods are a facade for FileTest:
101
# - #blockdev?
102
# - #chardev?
103
# - #directory?
104
# - #executable?
105
# - #executable_real?
106
# - #exist?
107
# - #file?
108
# - #grpowned?
109
# - #owned?
110
# - #pipe?
111
# - #readable?
112
# - #world_readable?
113
# - #readable_real?
114
# - #setgid?
115
# - #setuid?
116
# - #size
117
# - #size?
118
# - #socket?
119
# - #sticky?
120
# - #symlink?
121
# - #writable?
122
# - #world_writable?
123
# - #writable_real?
124
# - #zero?
125
#
126
# === File property and manipulation methods
127
#
128
# These methods are a facade for File:
129
# - #atime
130
# - #ctime
131
# - #mtime
132
# - #chmod(mode)
133
# - #lchmod(mode)
134
# - #chown(owner, group)
135
# - #lchown(owner, group)
136
# - #fnmatch(pattern, *args)
137
# - #fnmatch?(pattern, *args)
138
# - #ftype
139
# - #make_link(old)
140
# - #open(*args, &block)
141
# - #readlink
142
# - #rename(to)
143
# - #stat
144
# - #lstat
145
# - #make_symlink(old)
146
# - #truncate(length)
147
# - #utime(atime, mtime)
148
# - #basename(*args)
149
# - #dirname
150
# - #extname
151
# - #expand_path(*args)
152
# - #split
153
#
154
# === Directory methods
155
#
156
# These methods are a facade for Dir:
157
# - Pathname.glob(*args)
158
# - Pathname.getwd / Pathname.pwd
159
# - #rmdir
160
# - #entries
161
# - #each_entry(&block)
162
# - #mkdir(*args)
163
# - #opendir(*args)
164
#
165
# === IO
166
#
167
# These methods are a facade for IO:
168
# - #each_line(*args, &block)
169
# - #read(*args)
170
# - #binread(*args)
171
# - #readlines(*args)
172
# - #sysopen(*args)
173
#
174
# === Utilities
175
#
176
# These methods are a mixture of Find, FileUtils, and others:
177
# - #find(&block)
178
# - #mkpath
179
# - #rmtree
180
# - #unlink / #delete
181
#
182
#
183
# == Method documentation
184
#
185
# As the above section shows, most of the methods in Pathname are facades.  The
186
# documentation for these methods generally just says, for instance, "See
187
# FileTest.writable?", as you should be familiar with the original method
188
# anyway, and its documentation (e.g. through +ri+) will contain more
189
# information.  In some cases, a brief description will follow.
190
#
191
class Pathname
192

  
193
  # :stopdoc:
194
  if RUBY_VERSION < "1.9"
195
    TO_PATH = :to_str
196
  else
197
    # to_path is implemented so Pathname objects are usable with File.open, etc.
198
    TO_PATH = :to_path
199
  end
200

  
201
  SAME_PATHS = if File::FNM_SYSCASE.nonzero?
202
    proc {|a, b| a.casecmp(b).zero?}
203
  else
204
    proc {|a, b| a == b}
205
  end
206

  
207
  # :startdoc:
208

  
9
class Pathname < String
10
  SYMLOOP_MAX = 8 # deepest symlink traversal
11
  
12
  ROOT    = '/'.freeze
13
  DOT     = '.'.freeze
14
  DOT_DOT = '..'.freeze
15
  
209 16
  #
210
  # Create a Pathname object from the given String (or String-like object).
211
  # If +path+ contains a NUL character (<tt>\0</tt>), an ArgumentError is raised.
17
  # Creates a new Pathname. Any path with a null is rejected.
212 18
  #
213 19
  def initialize(path)
214
    path = path.__send__(TO_PATH) if path.respond_to? TO_PATH
215
    @path = path.dup
216

  
217
    if /\0/ =~ @path
218
      raise ArgumentError, "pathname contains \\0: #{@path.inspect}"
20
    if path =~ %r{\0}
21
      raise ArgumentError, "path cannot contain ASCII NULLs"
219 22
    end
220

  
221
    self.taint if @path.tainted?
23
    
24
    super(path)
222 25
  end
223

  
224
  def freeze() super; @path.freeze; self end
225
  def taint() super; @path.taint; self end
226
  def untaint() super; @path.untaint; self end
227

  
26
  
228 27
  #
229
  # Compare this pathname with +other+.  The comparison is string-based.
230
  # Be aware that two different paths (<tt>foo.txt</tt> and <tt>./foo.txt</tt>)
231
  # can refer to the same file.
28
  # Compares pathnames, case-sensitively. Sorts directories higher than other
29
  # files named similarly.
232 30
  #
233
  def ==(other)
234
    return false unless Pathname === other
235
    other.to_s == @path
236
  end
237
  alias === ==
238
  alias eql? ==
239

  
240
  # Provides for comparing pathnames, case-sensitively.
241 31
  def <=>(other)
242
    return nil unless Pathname === other
243
    @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
244
  end
245

  
246
  def hash # :nodoc:
247
    @path.hash
248
  end
249

  
250
  # Return the path as a String.
251
  def to_s
252
    @path.dup
253
  end
254

  
255
  # to_path is implemented so Pathname objects are usable with File.open, etc.
256
  alias_method TO_PATH, :to_s
257

  
258
  def inspect # :nodoc:
259
    "#<#{self.class}:#{@path}>"
260
  end
261

  
262
  # Return a pathname which is substituted by String#sub.
263
  def sub(pattern, *rest, &block)
264
    if block
265
      path = @path.sub(pattern, *rest) {|*args|
266
        begin
267
          old = Thread.current[:pathname_sub_matchdata]
268
          Thread.current[:pathname_sub_matchdata] = $~
269
          eval("$~ = Thread.current[:pathname_sub_matchdata]", block.binding)
270
        ensure
271
          Thread.current[:pathname_sub_matchdata] = old
272
        end
273
        yield(*args)
274
      }
275
    else
276
      path = @path.sub(pattern, *rest)
277
    end
278
    self.class.new(path)
279
  end
280

  
281
  if File::ALT_SEPARATOR
282
    SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}"
283
    SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
284
  else
285
    SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}"
286
    SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
287
  end
288

  
289
  # Return a pathname which the extension of the basename is substituted by
290
  # <i>repl</i>.
291
  #
292
  # If self has no extension part, <i>repl</i> is appended.
293
  def sub_ext(repl)
294
    ext = File.extname(@path)
295
    self.class.new(@path.chomp(ext) + repl)
296
  end
297

  
298
  # chop_basename(path) -> [pre-basename, basename] or nil
299
  def chop_basename(path)
300
    base = File.basename(path)
301
    if /\A#{SEPARATOR_PAT}?\z/o =~ base
302
      return nil
303
    else
304
      return path[0, path.rindex(base)], base
305
    end
306
  end
307
  private :chop_basename
308

  
309
  # split_names(path) -> prefix, [name, ...]
310
  def split_names(path)
311
    names = []
312
    while r = chop_basename(path)
313
      path, basename = r
314
      names.unshift basename
315
    end
316
    return path, names
317
  end
318
  private :split_names
319

  
320
  def prepend_prefix(prefix, relpath)
321
    if relpath.empty?
322
      File.dirname(prefix)
323
    elsif /#{SEPARATOR_PAT}/o =~ prefix
324
      prefix = File.dirname(prefix)
325
      prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
326
      prefix + relpath
327
    else
328
      prefix + relpath
329
    end
32
    self.tr('/', "\0").to_s <=> other.to_str.tr('/', "\0")
33
  rescue NoMethodError # doesn't respond to to_str
34
    nil
330 35
  end
331
  private :prepend_prefix
332

  
333
  # Returns clean pathname of +self+ with consecutive slashes and useless dots
334
  # removed.  The filesystem is not accessed.
36
  
335 37
  #
336
  # If +consider_symlink+ is +true+, then a more conservative algorithm is used
337
  # to avoid breaking symbolic linkages.  This may retain more <tt>..</tt>
338
  # entries than absolutely necessary, but without accessing the filesystem,
339
  # this can't be avoided.  See #realpath.
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.
340 40
  #
341
  def cleanpath(consider_symlink=false)
342
    if consider_symlink
343
      cleanpath_conservative
344
    else
345
      cleanpath_aggressive
346
    end
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
347 48
  end
348

  
49
  
349 50
  #
350
  # Clean the path simply by resolving and removing excess "." and ".." entries.
351
  # Nothing more, nothing less.
51
  # Appends a component of a path to self. Returns a Pathname to the combined
52
  # path. Cleans any redundant components of the path.
352 53
  #
353
  def cleanpath_aggressive
354
    path = @path
355
    names = []
356
    pre = path
357
    while r = chop_basename(pre)
358
      pre, base = r
359
      case base
360
      when '.'
361
      when '..'
362
        names.unshift base
363
      else
364
        if names[0] == '..'
365
          names.shift
366
        else
367
          names.unshift base
368
        end
369
      end
370
    end
371
    if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
372
      names.shift while names[0] == '..'
373
    end
374
    self.class.new(prepend_prefix(pre, File.join(*names)))
375
  end
376
  private :cleanpath_aggressive
377

  
378
  # has_trailing_separator?(path) -> bool
379
  def has_trailing_separator?(path)
380
    if r = chop_basename(path)
381
      pre, basename = r
382
      pre.length + basename.length < path.length
383
    else
384
      false
385
    end
54
  def +(path)
55
    dup << path
386 56
  end
387
  private :has_trailing_separator?
388

  
389
  # add_trailing_separator(path) -> path
390
  def add_trailing_separator(path)
391
    if File.basename(path + 'a') == 'a'
392
      path
393
    else
394
      File.join(path, "") # xxx: Is File.join is appropriate to add separator?
395
    end
396
  end
397
  private :add_trailing_separator
398

  
399
  def del_trailing_separator(path)
400
    if r = chop_basename(path)
401
      pre, basename = r
402
      pre + basename
403
    elsif /#{SEPARATOR_PAT}+\z/o =~ path
404
      $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
405
    else
406
      path
407
    end
408
  end
409
  private :del_trailing_separator
410

  
411
  def cleanpath_conservative
412
    path = @path
413
    names = []
414
    pre = path
415
    while r = chop_basename(pre)
416
      pre, base = r
417
      names.unshift base if base != '.'
418
    end
419
    if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
420
      names.shift while names[0] == '..'
421
    end
422
    if names.empty?
423
      self.class.new(File.dirname(pre))
424
    else
425
      if names.last != '..' && File.basename(path) == '.'
426
        names << '.'
427
      end
428
      result = prepend_prefix(pre, File.join(*names))
429
      if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
430
        self.class.new(add_trailing_separator(result))
431
      else
432
        self.class.new(result)
433
      end
434
    end
435
  end
436
  private :cleanpath_conservative
437

  
57
  
438 58
  #
439
  # Returns the real (absolute) pathname of +self+ in the actual
440
  # filesystem not containing symlinks or useless dots.
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.
441 62
  #
442
  # All components of the pathname must exist when this method is
443
  # called.
444
  #
445
  def realpath(basedir=nil)
446
    self.class.new(File.realpath(@path, basedir))
63
  def <<(path)
64
    replace( join(path).cleanpath! )
447 65
  end
448

  
449
  #
450
  # Returns the real (absolute) pathname of +self+ in the actual filesystem.
451
  # The real pathname doesn't contain symlinks or useless dots.
66
  
452 67
  #
453
  # The last component of the real pathname can be nonexistent.
68
  # Returns true if this is an absolute path.
454 69
  #
455
  def realdirpath(basedir=nil)
456
    self.class.new(File.realdirpath(@path, basedir))
70
  def absolute?
71
    self[0, 1].to_s == ROOT
457 72
  end
458

  
459
  # #parent returns the parent directory.
73
  
460 74
  #
461
  # This is same as <tt>self + '..'</tt>.
462
  def parent
463
    self + '..'
464
  end
465

  
466
  # #mountpoint? returns +true+ if <tt>self</tt> points to a mountpoint.
467
  def mountpoint?
468
    begin
469
      stat1 = self.lstat
470
      stat2 = self.parent.lstat
471
      stat1.dev == stat2.dev && stat1.ino == stat2.ino ||
472
        stat1.dev != stat2.dev
473
    rescue Errno::ENOENT
474
      false
475
    end
476
  end
477

  
75
  # Yields to each component of the path, going up to the root.
478 76
  #
479
  # #root? is a predicate for root directories.  I.e. it returns +true+ if the
480
  # pathname consists of consecutive slashes.
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
  #     "/"
481 83
  #
482
  # It doesn't access actual filesystem.  So it may return +false+ for some
483
  # pathnames which points to roots such as <tt>/usr/..</tt>.
84
  #   Pathname.new('a/relative/path').ascend {|path| p path }
85
  #     "a/relative/path"
86
  #     "a/relative"
87
  #     "a"
484 88
  #
485
  def root?
486
    !!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path)
487
  end
488

  
489
  # Predicate method for testing whether a path is absolute.
490
  # It returns +true+ if the pathname begins with a slash.
491
  def absolute?
492
    !relative?
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
493 96
  end
494

  
495
  # The opposite of #absolute?
496
  def relative?
497
    path = @path
498
    while r = chop_basename(path)
499
      path, basename = r
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
500 126
    end
501
    path == ''
127
    
128
    replace(final.empty? ? DOT : self.class.join(*final))
502 129
  end
503

  
130
  
504 131
  #
505
  # Iterates over each component of the path.
132
  # Cleans the path by removing consecutive slashes, and useless dots.
506 133
  #
507
  #   Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
508
  #     # yields "usr", "bin", and "ruby".
509
  #
510
  def each_filename # :yield: filename
511
    return to_enum(__method__) unless block_given?
512
    prefix, names = split_names(@path)
513
    names.each {|filename| yield filename }
514
    nil
134
  def cleanpath
135
    dup.cleanpath!
515 136
  end
516

  
517
  # Iterates over and yields a new Pathname object
518
  # for each element in the given path in descending order.
137
  
519 138
  #
520
  #  Pathname.new('/path/to/some/file.rb').descend {|v| p v}
521
  #     #<Pathname:/>
522
  #     #<Pathname:/path>
523
  #     #<Pathname:/path/to>
524
  #     #<Pathname:/path/to/some>
525
  #     #<Pathname:/path/to/some/file.rb>
139
  # Yields to each component of the path, going down from the root.
526 140
  #
527
  #  Pathname.new('path/to/some/file.rb').descend {|v| p v}
528
  #     #<Pathname:path>
529
  #     #<Pathname:path/to>
530
  #     #<Pathname:path/to/some>
531
  #     #<Pathname:path/to/some/file.rb>
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"
532 147
  #
533
  # It doesn't access actual filesystem.
148
  #   Pathname.new('a/relative/path').ascend {|path| p path }
149
  #     "a"
150
  #     "a/relative"
151
  #     "a/relative/path"
534 152
  #
535
  # This method is available since 1.8.5.
153
  #  Does not actually access the filesystem.
536 154
  #
537 155
  def descend
538
    vs = []
539
    ascend {|v| vs << v }
540
    vs.reverse_each {|v| yield v }
541
    nil
156
    parts = to_a
157
    1.upto(parts.length) do |i|
158
      yield self.class.join(parts[0, i])
159
    end
542 160
  end
543

  
544
  # Iterates over and yields a new Pathname object
545
  # for each element in the given path in ascending order.
161
  
546 162
  #
547
  #  Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
548
  #     #<Pathname:/path/to/some/file.rb>
549
  #     #<Pathname:/path/to/some>
550
  #     #<Pathname:/path/to>
551
  #     #<Pathname:/path>
552
  #     #<Pathname:/>
163
  # Returns true if this path is simply a '.'.
553 164
  #
554
  #  Pathname.new('path/to/some/file.rb').ascend {|v| p v}
555
  #     #<Pathname:path/to/some/file.rb>
556
  #     #<Pathname:path/to/some>
557
  #     #<Pathname:path/to>
558
  #     #<Pathname:path>
559
  #
560
  # It doesn't access actual filesystem.
165
  def dot?
166
    self == DOT
167
  end
168
  
561 169
  #
562
  # This method is available since 1.8.5.
170
  # Returns true if this path is simply a '..'.
563 171
  #
564
  def ascend
565
    path = @path
566
    yield self
567
    while r = chop_basename(path)
568
      path, name = r
569
      break if path.empty?
570
      yield self.class.new(del_trailing_separator(path))
571
    end
172
  def dot_dot?
173
    self == DOT_DOT
572 174
  end
573

  
175
  
574 176
  #
575
  # Pathname#+ appends a pathname fragment to this one to produce a new Pathname
576
  # object.
177
  # Iterates over every component of the path.
577 178
  #
578
  #   p1 = Pathname.new("/usr")      # Pathname:/usr
579
  #   p2 = p1 + "bin/ruby"           # Pathname:/usr/bin/ruby
580
  #   p3 = p1 + "/etc/passwd"        # Pathname:/etc/passwd
179
  #   Pathname.new('/path/to/some/file').ascend {|path| p path }
180
  #     "/"
181
  #     "path"
182
  #     "to"
183
  #     "some"
184
  #     "file"
581 185
  #
582
  # This method doesn't access the file system; it is pure string manipulation.
186
  #   Pathname.new('a/relative/path').each_filename {|part| p part }
187
  #     "a"
188
  #     "relative"
189
  #     "path"
583 190
  #
584
  def +(other)
585
    other = Pathname.new(other) unless Pathname === other
586
    Pathname.new(plus(@path, other.to_s))
587
  end
588

  
589
  def plus(path1, path2) # -> path
590
    prefix2 = path2
591
    index_list2 = []
592
    basename_list2 = []
593
    while r2 = chop_basename(prefix2)
594
      prefix2, basename2 = r2
595
      index_list2.unshift prefix2.length
596
      basename_list2.unshift basename2
597
    end
598
    return path2 if prefix2 != ''
599
    prefix1 = path1
600
    while true
601
      while !basename_list2.empty? && basename_list2.first == '.'
602
        index_list2.shift
603
        basename_list2.shift
604
      end
605
      break unless r1 = chop_basename(prefix1)
606
      prefix1, basename1 = r1
607
      next if basename1 == '.'
608
      if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
609
        prefix1 = prefix1 + basename1
610
        break
611
      end
612
      index_list2.shift
613
      basename_list2.shift
614
    end
615
    r1 = chop_basename(prefix1)
616
    if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)
617
      while !basename_list2.empty? && basename_list2.first == '..'
618
        index_list2.shift
619
        basename_list2.shift
620
      end
621
    end
622
    if !basename_list2.empty?
623
      suffix2 = path2[index_list2.first..-1]
624
      r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
625
    else
626
      r1 ? prefix1 : File.dirname(prefix1)
627
    end
191
  def each_filename(&blk)
192
    to_a.each(&blk)
628 193
  end
629
  private :plus
630

  
194
  
631 195
  #
632
  # Pathname#join joins pathnames.
196
  # Returns true if the path is a mountpoint.
633 197
  #
634
  # <tt>path0.join(path1, ..., pathN)</tt> is the same as
635
  # <tt>path0 + path1 + ... + pathN</tt>.
636
  #
637
  def join(*args)
638
    args.unshift self
639
    result = args.pop
640
    result = Pathname.new(result) unless Pathname === result
641
    return result if result.absolute?
642
    args.reverse_each {|arg|
643
      arg = Pathname.new(arg) unless Pathname === arg
644
      result = arg + result
645
      return result if result.absolute?
646
    }
647
    result
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
648 205
  end
649

  
650
  #
651
  # Returns the children of the directory (files and subdirectories, not
652
  # recursive) as an array of Pathname objects.  By default, the returned
653
  # pathnames will have enough information to access the files.  If you set
654
  # +with_directory+ to +false+, then the returned pathnames will contain the
655
  # filename only.
656
  #
657
  # For example:
658
  #   pn = Pathname("/usr/lib/ruby/1.8")
659
  #   pn.children
660
  #       # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
661
  #              Pathname:/usr/lib/ruby/1.8/Env.rb,
662
  #              Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
663
  #   pn.children(false)
664
  #       # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ]
665
  #
666
  # Note that the result never contain the entries <tt>.</tt> and <tt>..</tt> in
667
  # the directory because they are not children.
206
  
668 207
  #
669
  # This method has existed since 1.8.1.
208
  # Returns a path to the parent directory. Simply appends a "..".
670 209
  #
671
  def children(with_directory=true)
672
    with_directory = false if @path == '.'
673
    result = []
674
    Dir.foreach(@path) {|e|
675
      next if e == '.' || e == '..'
676
      if with_directory
677
        result << self.class.new(File.join(@path, e))
678
      else
679
        result << self.class.new(e)
680
      end
681
    }
682
    result
210
  def parent
211
    self + '..'
683 212
  end
684

  
685
  # Iterates over the children of the directory
686
  # (files and subdirectories, not recursive).
687
  # It yields Pathname object for each child.
688
  # By default, the yielded pathnames will have enough information to access the files.
689
  # If you set +with_directory+ to +false+, then the returned pathnames will contain the filename only.
690
  #
691
  #   Pathname("/usr/local").each_child {|f| p f }
692
  #   #=> #<Pathname:/usr/local/share>
693
  #   #   #<Pathname:/usr/local/bin>
694
  #   #   #<Pathname:/usr/local/games>
695
  #   #   #<Pathname:/usr/local/lib>
696
  #   #   #<Pathname:/usr/local/include>
697
  #   #   #<Pathname:/usr/local/sbin>
698
  #   #   #<Pathname:/usr/local/src>
699
  #   #   #<Pathname:/usr/local/man>
213
  
700 214
  #
701
  #   Pathname("/usr/local").each_child(false) {|f| p f }
702
  #   #=> #<Pathname:share>
703
  #   #   #<Pathname:bin>
704
  #   #   #<Pathname:games>
705
  #   #   #<Pathname:lib>
706
  #   #   #<Pathname:include>
707
  #   #   #<Pathname:sbin>
708
  #   #   #<Pathname:src>
709
  #   #   #<Pathname:man>
215
  # Resolves a path to locate a real location on the filesystem. Resolves
216
  # symlinks up to a deptho of SYMLOOP_MAX.
710 217
  #
711
  def each_child(with_directory=true, &b)
712
    children(with_directory).each(&b)
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
713 230
  end
714

  
231
  
715 232
  #
716
  # #relative_path_from returns a relative path from the argument to the
717
  # receiver.  If +self+ is absolute, the argument must be absolute too.  If
718
  # +self+ is relative, the argument must be relative too.
233
  # Returns true if this is a relative path.
719 234
  #
720
  # #relative_path_from doesn't access the filesystem.  It assumes no symlinks.
235
  def relative?
236
    !absolute?
237
  end
238
  
721 239
  #
722
  # ArgumentError is raised when it cannot find a relative path.
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.
723 243
  #
724
  # This method has existed since 1.8.1.
244
  # Does not access the filesystem.
725 245
  #
726
  def relative_path_from(base_directory)
727
    dest_directory = self.cleanpath.to_s
728
    base_directory = base_directory.cleanpath.to_s
729
    dest_prefix = dest_directory
730
    dest_names = []
731
    while r = chop_basename(dest_prefix)
732
      dest_prefix, basename = r
733
      dest_names.unshift basename if basename != '.'
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'
734 252
    end
735
    base_prefix = base_directory
736
    base_names = []
737
    while r = chop_basename(base_prefix)
738
      base_prefix, basename = r
739
      base_names.unshift basename if basename != '.'
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
740 263
    end
741
    unless SAME_PATHS[dest_prefix, base_prefix]
742
      raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
743
    end
744
    while !dest_names.empty? &&
745
          !base_names.empty? &&
746
          SAME_PATHS[dest_names.first, base_names.first]
747
      dest_names.shift
748
      base_names.shift
749
    end
750
    if base_names.include? '..'
751
      raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
752
    end
753
    base_names.fill('..')
754
    relpath_names = base_names + dest_names
755
    if relpath_names.empty?
756
      Pathname.new('.')
757
    else
758
      Pathname.new(File.join(*relpath_names))
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}'"
759 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
760 277
  end
761
end
762

  
763
class Pathname    # * IO *
278
  
764 279
  #
765
  # #each_line iterates over the line in the file.  It yields a String object
766
  # for each line.
280
  # Returns true if this path points to the root of the filesystem.
767 281
  #
768
  # This method has existed since 1.8.1.
282
  def root?
283
    !!(self =~ %r{^#{ROOT}+$})
284
  end
285
  
769 286
  #
770
  def each_line(*args, &block) # :yield: line
771
    IO.foreach(@path, *args, &block)
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
772 294
  end
773

  
774
  # See <tt>IO.read</tt>.  Returns all data from the file, or the first +N+ bytes
775
  # if specified.
776
  def read(*args) IO.read(@path, *args) end
777

  
778
  # See <tt>IO.binread</tt>.  Returns all the bytes from the file, or the first +N+
779
  # if specified.
780
  def binread(*args) IO.binread(@path, *args) end
781

  
782
  # See <tt>IO.readlines</tt>.  Returns all the lines from the file.
783
  def readlines(*args) IO.readlines(@path, *args) end
784

  
785
  # See <tt>IO.sysopen</tt>.
786
  def sysopen(*args) IO.sysopen(@path, *args) end
787
end
788

  
789

  
790
class Pathname    # * File *
791

  
792
  # See <tt>File.atime</tt>.  Returns last access time.
793
  def atime() File.atime(@path) end
794

  
795
  # See <tt>File.ctime</tt>.  Returns last (directory entry, not file) change time.
796
  def ctime() File.ctime(@path) end
797

  
798
  # See <tt>File.mtime</tt>.  Returns last modification time.
799
  def mtime() File.mtime(@path) end
800

  
801
  # See <tt>File.chmod</tt>.  Changes permissions.
802
  def chmod(mode) File.chmod(mode, @path) end
803

  
804
  # See <tt>File.lchmod</tt>.
805
  def lchmod(mode) File.lchmod(mode, @path) end
806

  
807
  # See <tt>File.chown</tt>.  Change owner and group of file.
808
  def chown(owner, group) File.chown(owner, group, @path) end
809

  
810
  # See <tt>File.lchown</tt>.
811
  def lchown(owner, group) File.lchown(owner, group, @path) end
812

  
813
  # See <tt>File.fnmatch</tt>.  Return +true+ if the receiver matches the given
814
  # pattern.
815
  def fnmatch(pattern, *args) File.fnmatch(pattern, @path, *args) end
816

  
817
  # See <tt>File.fnmatch?</tt> (same as #fnmatch).
818
  def fnmatch?(pattern, *args) File.fnmatch?(pattern, @path, *args) end
819

  
820
  # See <tt>File.ftype</tt>.  Returns "type" of file ("file", "directory",
821
  # etc).
822
  def ftype() File.ftype(@path) end
823

  
824
  # See <tt>File.link</tt>.  Creates a hard link.
825
  def make_link(old) File.link(old, @path) end
826

  
827
  # See <tt>File.open</tt>.  Opens the file for reading or writing.
828
  def open(*args, &block) # :yield: file
829
    File.open(@path, *args, &block)
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
830 312
  end
831

  
832
  # See <tt>File.readlink</tt>.  Read symbolic link.
833
  def readlink() self.class.new(File.readlink(@path)) end
834

  
835
  # See <tt>File.rename</tt>.  Rename the file.
836
  def rename(to) File.rename(@path, to) end
837

  
838
  # See <tt>File.stat</tt>.  Returns a <tt>File::Stat</tt> object.
839
  def stat() File.stat(@path) end
840

  
841
  # See <tt>File.lstat</tt>.
842
  def lstat() File.lstat(@path) end
843

  
844
  # See <tt>File.symlink</tt>.  Creates a symbolic link.
845
  def make_symlink(old) File.symlink(old, @path) end
846

  
847
  # See <tt>File.truncate</tt>.  Truncate the file to +length+ bytes.
848
  def truncate(length) File.truncate(@path, length) end
849

  
850
  # See <tt>File.utime</tt>.  Update the access and modification times.
851
  def utime(atime, mtime) File.utime(atime, mtime, @path) end
852

  
853
  # See <tt>File.basename</tt>.  Returns the last component of the path.
854
  def basename(*args) self.class.new(File.basename(@path, *args)) end
855

  
856
  # See <tt>File.dirname</tt>.  Returns all but the last component of the path.
857
  def dirname() self.class.new(File.dirname(@path)) end
858

  
859
  # See <tt>File.extname</tt>.  Returns the file's extension.
860
  def extname() File.extname(@path) end
861

  
862
  # See <tt>File.expand_path</tt>.
863
  def expand_path(*args) self.class.new(File.expand_path(@path, *args)) end
864

  
865
  # See <tt>File.split</tt>.  Returns the #dirname and the #basename in an
866
  # Array.
867
  def split() File.split(@path).map {|f| self.class.new(f) } end
868
end
869

  
870

  
871
class Pathname    # * FileTest *
872

  
873
  # See <tt>FileTest.blockdev?</tt>.
874
  def blockdev?() FileTest.blockdev?(@path) end
875

  
876
  # See <tt>FileTest.chardev?</tt>.
877
  def chardev?() FileTest.chardev?(@path) end
878

  
879
  # See <tt>FileTest.executable?</tt>.
880
  def executable?() FileTest.executable?(@path) end
881

  
882
  # See <tt>FileTest.executable_real?</tt>.
883
  def executable_real?() FileTest.executable_real?(@path) end
884

  
885
  # See <tt>FileTest.exist?</tt>.
886
  def exist?() FileTest.exist?(@path) end
887

  
888
  # See <tt>FileTest.grpowned?</tt>.
889
  def grpowned?() FileTest.grpowned?(@path) end
890

  
891
  # See <tt>FileTest.directory?</tt>.
892
  def directory?() FileTest.directory?(@path) end
893

  
894
  # See <tt>FileTest.file?</tt>.
895
  def file?() FileTest.file?(@path) end
896

  
897
  # See <tt>FileTest.pipe?</tt>.
898
  def pipe?() FileTest.pipe?(@path) end
899

  
900
  # See <tt>FileTest.socket?</tt>.
901
  def socket?() FileTest.socket?(@path) end
902

  
903
  # See <tt>FileTest.owned?</tt>.
904
  def owned?() FileTest.owned?(@path) end
905

  
906
  # See <tt>FileTest.readable?</tt>.
907
  def readable?() FileTest.readable?(@path) end
908

  
909
  # See <tt>FileTest.world_readable?</tt>.
910
  def world_readable?() FileTest.world_readable?(@path) end
911

  
912
  # See <tt>FileTest.readable_real?</tt>.
913
  def readable_real?() FileTest.readable_real?(@path) end
914

  
915
  # See <tt>FileTest.setuid?</tt>.
916
  def setuid?() FileTest.setuid?(@path) end
917

  
918
  # See <tt>FileTest.setgid?</tt>.
919
  def setgid?() FileTest.setgid?(@path) end
920

  
921
  # See <tt>FileTest.size</tt>.
922
  def size() FileTest.size(@path) end
923

  
924
  # See <tt>FileTest.size?</tt>.
925
  def size?() FileTest.size?(@path) end
926

  
927
  # See <tt>FileTest.sticky?</tt>.
928
  def sticky?() FileTest.sticky?(@path) end
929

  
930
  # See <tt>FileTest.symlink?</tt>.
931
  def symlink?() FileTest.symlink?(@path) end
932

  
933
  # See <tt>FileTest.writable?</tt>.
934
  def writable?() FileTest.writable?(@path) end
935

  
936
  # See <tt>FileTest.world_writable?</tt>.
937
  def world_writable?() FileTest.world_writable?(@path) end
938

  
939
  # See <tt>FileTest.writable_real?</tt>.
940
  def writable_real?() FileTest.writable_real?(@path) end
941

  
942
  # See <tt>FileTest.zero?</tt>.
943
  def zero?() FileTest.zero?(@path) end
944 313
end
945 314

  
946

  
947
class Pathname    # * Dir *
948
  # See <tt>Dir.glob</tt>.  Returns or yields Pathname objects.
949
  def Pathname.glob(*args) # :yield: pathname
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
    
950 339
    if block_given?
951
      Dir.glob(*args) {|f| yield self.new(f) }
340
      dirs.each {|dir| yield dir }
341
      nil
952 342
    else
953
      Dir.glob(*args).map {|f| self.new(f) }
343
      dirs
954 344
    end
955 345
  end
956

  
957
  # See <tt>Dir.getwd</tt>.  Returns the current working directory as a Pathname.
958
  def Pathname.getwd() self.new(Dir.getwd) end
959
  class << self; alias pwd getwd end
960

  
961
  # Return the entries (files and subdirectories) in the directory, each as a
962
  # Pathname object.
963
  def entries() Dir.entries(@path).map {|f| self.class.new(f) } end
964

  
965
  # Iterates over the entries (files and subdirectories) in the directory.  It
966
  # yields a Pathname object for each entry.
967
  #
968
  # This method has existed since 1.8.1.
969
  def each_entry(&block) # :yield: pathname
970
    Dir.foreach(@path) {|f| yield self.class.new(f) }
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
971 352
  end
972

  
973
  # See <tt>Dir.mkdir</tt>.  Create the referenced directory.
974
  def mkdir(*args) Dir.mkdir(@path, *args) end
975

  
976
  # See <tt>Dir.rmdir</tt>.  Remove the referenced directory.
977
  def rmdir() Dir.rmdir(@path) end
978

  
979
  # See <tt>Dir.open</tt>.
980
  def opendir(&block) # :yield: dir
981
    Dir.open(@path, &block)
353
  
354
  # See Dir::chdir
355
  def chdir 
356
    blk = lambda { yield self } if block_given?
357
    Dir.chdir(self, &blk)
982 358
  end
983 359
end
984 360

  
985

  
986
class Pathname    # * Find *
987
  #
988
  # Pathname#find is an iterator to traverse a directory tree in a depth first
989
  # manner.  It yields a Pathname for each file under "this" directory.
990
  #
991
  # Since it is implemented by <tt>find.rb</tt>, <tt>Find.prune</tt> can be used
992
  # to control the traverse.
993
  #
994
  # If +self+ is <tt>.</tt>, yielded pathnames begin with a filename in the
995
  # current directory, not <tt>./</tt>.
996
  #
997
  def find(&block) # :yield: pathname
998
    require 'find'
999
    if @path == '.'
1000
      Find.find(@path) {|f| yield self.class.new(f.sub(%r{\A\./}, '')) }
1001
    else
1002
      Find.find(@path) {|f| yield self.class.new(f) }
1003
    end
1004
  end
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
1005 427
end
1006 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
1007 451

  
1008
class Pathname    # * FileUtils *
1009
  # See <tt>FileUtils.mkpath</tt>.  Creates a full path, including any
1010
  # intermediate directories that don't yet exist.
1011
  def mkpath
1012
    require 'fileutils'
1013
    FileUtils.mkpath(@path)
1014
    nil
1015
  end
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
1016 513

  
1017
  # See <tt>FileUtils.rm_r</tt>.  Deletes a directory and all beneath it.
1018
  def rmtree
1019
    # The name "rmtree" is borrowed from File::Path of Perl.
1020
    # File::Path provides "mkpath" and "rmtree".
1021
    require 'fileutils'
1022
    FileUtils.rm_r(@path)
1023
    nil
1024
  end
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
1025 523
end
1026 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
1027 538

  
1028
class Pathname    # * mixed *
1029
  # Removes a file or directory, using <tt>File.unlink</tt> or
1030
  # <tt>Dir.unlink</tt> as necessary.
1031
  def unlink()
1032
    begin
1033
      Dir.unlink @path
1034
    rescue Errno::ENOTDIR
1035
      File.unlink @path
1036
    end
1037
  end
1038
  alias delete unlink
539
class Pathname
540
  # See Find::find
541
  def find; Find.find(self) {|path| yield path.to_path }; end
1039 542
end
1040 543

  
1041 544
class Pathname
1042
  undef =~
545
  class << self
546
    alias getwd pwd
547
  end
548
  
... This diff was truncated because it exceeds the maximum size that can be displayed.