pathname 0.4.0 → 0.5.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.
data/lib/pathname.rb CHANGED
@@ -10,549 +10,12 @@
10
10
  # For documentation, see class Pathname.
11
11
  #
12
12
 
13
- require 'pathname.so'
13
+ unless RUBY_VERSION >= '4'
14
+ require 'pathname.so' if RUBY_ENGINE == 'ruby'
14
15
 
15
- class Pathname
16
-
17
- VERSION = "0.4.0"
18
-
19
- # :stopdoc:
20
-
21
- # to_path is implemented so Pathname objects are usable with File.open, etc.
22
- TO_PATH = :to_path
23
-
24
- SAME_PATHS = if File::FNM_SYSCASE.nonzero?
25
- # Avoid #zero? here because #casecmp can return nil.
26
- proc {|a, b| a.casecmp(b) == 0}
27
- else
28
- proc {|a, b| a == b}
29
- end
30
-
31
-
32
- if File::ALT_SEPARATOR
33
- SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}"
34
- SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
35
- else
36
- SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}"
37
- SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
38
- end
39
-
40
- if File.dirname('A:') == 'A:.' # DOSish drive letter
41
- ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/o
42
- else
43
- ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/o
44
- end
45
- private_constant :ABSOLUTE_PATH
46
-
47
- # :startdoc:
48
-
49
- # chop_basename(path) -> [pre-basename, basename] or nil
50
- def chop_basename(path) # :nodoc:
51
- base = File.basename(path)
52
- if /\A#{SEPARATOR_PAT}?\z/o.match?(base)
53
- return nil
54
- else
55
- return path[0, path.rindex(base)], base
56
- end
57
- end
58
- private :chop_basename
59
-
60
- # split_names(path) -> prefix, [name, ...]
61
- def split_names(path) # :nodoc:
62
- names = []
63
- while r = chop_basename(path)
64
- path, basename = r
65
- names.unshift basename
66
- end
67
- return path, names
68
- end
69
- private :split_names
70
-
71
- def prepend_prefix(prefix, relpath) # :nodoc:
72
- if relpath.empty?
73
- File.dirname(prefix)
74
- elsif /#{SEPARATOR_PAT}/o.match?(prefix)
75
- prefix = File.dirname(prefix)
76
- prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
77
- prefix + relpath
78
- else
79
- prefix + relpath
80
- end
81
- end
82
- private :prepend_prefix
83
-
84
- # Returns clean pathname of +self+ with consecutive slashes and useless dots
85
- # removed. The filesystem is not accessed.
86
- #
87
- # If +consider_symlink+ is +true+, then a more conservative algorithm is used
88
- # to avoid breaking symbolic linkages. This may retain more +..+
89
- # entries than absolutely necessary, but without accessing the filesystem,
90
- # this can't be avoided.
91
- #
92
- # See Pathname#realpath.
93
- #
94
- def cleanpath(consider_symlink=false)
95
- if consider_symlink
96
- cleanpath_conservative
97
- else
98
- cleanpath_aggressive
99
- end
100
- end
101
-
102
- #
103
- # Clean the path simply by resolving and removing excess +.+ and +..+ entries.
104
- # Nothing more, nothing less.
105
- #
106
- def cleanpath_aggressive # :nodoc:
107
- path = @path
108
- names = []
109
- pre = path
110
- while r = chop_basename(pre)
111
- pre, base = r
112
- case base
113
- when '.'
114
- when '..'
115
- names.unshift base
116
- else
117
- if names[0] == '..'
118
- names.shift
119
- else
120
- names.unshift base
121
- end
122
- end
123
- end
124
- pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
125
- if /#{SEPARATOR_PAT}/o.match?(File.basename(pre))
126
- names.shift while names[0] == '..'
127
- end
128
- self.class.new(prepend_prefix(pre, File.join(*names)))
129
- end
130
- private :cleanpath_aggressive
131
-
132
- # has_trailing_separator?(path) -> bool
133
- def has_trailing_separator?(path) # :nodoc:
134
- if r = chop_basename(path)
135
- pre, basename = r
136
- pre.length + basename.length < path.length
137
- else
138
- false
139
- end
140
- end
141
- private :has_trailing_separator?
142
-
143
- # add_trailing_separator(path) -> path
144
- def add_trailing_separator(path) # :nodoc:
145
- if File.basename(path + 'a') == 'a'
146
- path
147
- else
148
- File.join(path, "") # xxx: Is File.join is appropriate to add separator?
149
- end
150
- end
151
- private :add_trailing_separator
152
-
153
- def del_trailing_separator(path) # :nodoc:
154
- if r = chop_basename(path)
155
- pre, basename = r
156
- pre + basename
157
- elsif /#{SEPARATOR_PAT}+\z/o =~ path
158
- $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
159
- else
160
- path
161
- end
162
- end
163
- private :del_trailing_separator
164
-
165
- def cleanpath_conservative # :nodoc:
166
- path = @path
167
- names = []
168
- pre = path
169
- while r = chop_basename(pre)
170
- pre, base = r
171
- names.unshift base if base != '.'
172
- end
173
- pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
174
- if /#{SEPARATOR_PAT}/o.match?(File.basename(pre))
175
- names.shift while names[0] == '..'
176
- end
177
- if names.empty?
178
- self.class.new(File.dirname(pre))
179
- else
180
- if names.last != '..' && File.basename(path) == '.'
181
- names << '.'
182
- end
183
- result = prepend_prefix(pre, File.join(*names))
184
- if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
185
- self.class.new(add_trailing_separator(result))
186
- else
187
- self.class.new(result)
188
- end
189
- end
190
- end
191
- private :cleanpath_conservative
192
-
193
- # Returns the parent directory.
194
- #
195
- # This is same as <code>self + '..'</code>.
196
- def parent
197
- self + '..'
198
- end
199
-
200
- # Returns +true+ if +self+ points to a mountpoint.
201
- def mountpoint?
202
- begin
203
- stat1 = self.lstat
204
- stat2 = self.parent.lstat
205
- stat1.dev != stat2.dev || stat1.ino == stat2.ino
206
- rescue Errno::ENOENT
207
- false
208
- end
209
- end
210
-
211
- #
212
- # Predicate method for root directories. Returns +true+ if the
213
- # pathname consists of consecutive slashes.
214
- #
215
- # It doesn't access the filesystem. So it may return +false+ for some
216
- # pathnames which points to roots such as <tt>/usr/..</tt>.
217
- #
218
- def root?
219
- chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o.match?(@path)
220
- end
221
-
222
- # Predicate method for testing whether a path is absolute.
223
- #
224
- # It returns +true+ if the pathname begins with a slash.
225
- #
226
- # p = Pathname.new('/im/sure')
227
- # p.absolute?
228
- # #=> true
229
- #
230
- # p = Pathname.new('not/so/sure')
231
- # p.absolute?
232
- # #=> false
233
- def absolute?
234
- ABSOLUTE_PATH.match? @path
235
- end
236
-
237
- # The opposite of Pathname#absolute?
238
- #
239
- # It returns +false+ if the pathname begins with a slash.
240
- #
241
- # p = Pathname.new('/im/sure')
242
- # p.relative?
243
- # #=> false
244
- #
245
- # p = Pathname.new('not/so/sure')
246
- # p.relative?
247
- # #=> true
248
- def relative?
249
- !absolute?
250
- end
251
-
252
- #
253
- # Iterates over each component of the path.
254
- #
255
- # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
256
- # # yields "usr", "bin", and "ruby".
257
- #
258
- # Returns an Enumerator if no block was given.
259
- #
260
- # enum = Pathname.new("/usr/bin/ruby").each_filename
261
- # # ... do stuff ...
262
- # enum.each { |e| ... }
263
- # # yields "usr", "bin", and "ruby".
264
- #
265
- def each_filename # :yield: filename
266
- return to_enum(__method__) unless block_given?
267
- _, names = split_names(@path)
268
- names.each {|filename| yield filename }
269
- nil
270
- end
271
-
272
- # Iterates over and yields a new Pathname object
273
- # for each element in the given path in descending order.
274
- #
275
- # Pathname.new('/path/to/some/file.rb').descend {|v| p v}
276
- # #<Pathname:/>
277
- # #<Pathname:/path>
278
- # #<Pathname:/path/to>
279
- # #<Pathname:/path/to/some>
280
- # #<Pathname:/path/to/some/file.rb>
281
- #
282
- # Pathname.new('path/to/some/file.rb').descend {|v| p v}
283
- # #<Pathname:path>
284
- # #<Pathname:path/to>
285
- # #<Pathname:path/to/some>
286
- # #<Pathname:path/to/some/file.rb>
287
- #
288
- # Returns an Enumerator if no block was given.
289
- #
290
- # enum = Pathname.new("/usr/bin/ruby").descend
291
- # # ... do stuff ...
292
- # enum.each { |e| ... }
293
- # # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby.
294
- #
295
- # It doesn't access the filesystem.
296
- #
297
- def descend
298
- return to_enum(__method__) unless block_given?
299
- vs = []
300
- ascend {|v| vs << v }
301
- vs.reverse_each {|v| yield v }
302
- nil
303
- end
304
-
305
- # Iterates over and yields a new Pathname object
306
- # for each element in the given path in ascending order.
307
- #
308
- # Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
309
- # #<Pathname:/path/to/some/file.rb>
310
- # #<Pathname:/path/to/some>
311
- # #<Pathname:/path/to>
312
- # #<Pathname:/path>
313
- # #<Pathname:/>
314
- #
315
- # Pathname.new('path/to/some/file.rb').ascend {|v| p v}
316
- # #<Pathname:path/to/some/file.rb>
317
- # #<Pathname:path/to/some>
318
- # #<Pathname:path/to>
319
- # #<Pathname:path>
320
- #
321
- # Returns an Enumerator if no block was given.
322
- #
323
- # enum = Pathname.new("/usr/bin/ruby").ascend
324
- # # ... do stuff ...
325
- # enum.each { |e| ... }
326
- # # yields Pathnames /usr/bin/ruby, /usr/bin, /usr, and /.
327
- #
328
- # It doesn't access the filesystem.
329
- #
330
- def ascend
331
- return to_enum(__method__) unless block_given?
332
- path = @path
333
- yield self
334
- while r = chop_basename(path)
335
- path, = r
336
- break if path.empty?
337
- yield self.class.new(del_trailing_separator(path))
338
- end
339
- end
340
-
341
- #
342
- # Appends a pathname fragment to +self+ to produce a new Pathname object.
343
- # Since +other+ is considered as a path relative to +self+, if +other+ is
344
- # an absolute path, the new Pathname object is created from just +other+.
345
- #
346
- # p1 = Pathname.new("/usr") # Pathname:/usr
347
- # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby
348
- # p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd
349
- #
350
- # # / is aliased to +.
351
- # p4 = p1 / "bin/ruby" # Pathname:/usr/bin/ruby
352
- # p5 = p1 / "/etc/passwd" # Pathname:/etc/passwd
353
- #
354
- # This method doesn't access the file system; it is pure string manipulation.
355
- #
356
- def +(other)
357
- other = Pathname.new(other) unless Pathname === other
358
- Pathname.new(plus(@path, other.to_s))
359
- end
360
- alias / +
361
-
362
- def plus(path1, path2) # -> path # :nodoc:
363
- prefix2 = path2
364
- index_list2 = []
365
- basename_list2 = []
366
- while r2 = chop_basename(prefix2)
367
- prefix2, basename2 = r2
368
- index_list2.unshift prefix2.length
369
- basename_list2.unshift basename2
370
- end
371
- return path2 if prefix2 != ''
372
- prefix1 = path1
373
- while true
374
- while !basename_list2.empty? && basename_list2.first == '.'
375
- index_list2.shift
376
- basename_list2.shift
377
- end
378
- break unless r1 = chop_basename(prefix1)
379
- prefix1, basename1 = r1
380
- next if basename1 == '.'
381
- if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
382
- prefix1 = prefix1 + basename1
383
- break
384
- end
385
- index_list2.shift
386
- basename_list2.shift
387
- end
388
- r1 = chop_basename(prefix1)
389
- if !r1 && (r1 = /#{SEPARATOR_PAT}/o.match?(File.basename(prefix1)))
390
- while !basename_list2.empty? && basename_list2.first == '..'
391
- index_list2.shift
392
- basename_list2.shift
393
- end
394
- end
395
- if !basename_list2.empty?
396
- suffix2 = path2[index_list2.first..-1]
397
- r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
398
- else
399
- r1 ? prefix1 : File.dirname(prefix1)
400
- end
401
- end
402
- private :plus
403
-
404
- #
405
- # Joins the given pathnames onto +self+ to create a new Pathname object.
406
- # This is effectively the same as using Pathname#+ to append +self+ and
407
- # all arguments sequentially.
408
- #
409
- # path0 = Pathname.new("/usr") # Pathname:/usr
410
- # path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby
411
- # # is the same as
412
- # path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby
413
- # path0 == path1
414
- # #=> true
415
- #
416
- def join(*args)
417
- return self if args.empty?
418
- result = args.pop
419
- result = Pathname.new(result) unless Pathname === result
420
- return result if result.absolute?
421
- args.reverse_each {|arg|
422
- arg = Pathname.new(arg) unless Pathname === arg
423
- result = arg + result
424
- return result if result.absolute?
425
- }
426
- self + result
427
- end
428
-
429
- #
430
- # Returns the children of the directory (files and subdirectories, not
431
- # recursive) as an array of Pathname objects.
432
- #
433
- # By default, the returned pathnames will have enough information to access
434
- # the files. If you set +with_directory+ to +false+, then the returned
435
- # pathnames will contain the filename only.
436
- #
437
- # For example:
438
- # pn = Pathname("/usr/lib/ruby/1.8")
439
- # pn.children
440
- # # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
441
- # Pathname:/usr/lib/ruby/1.8/Env.rb,
442
- # Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
443
- # pn.children(false)
444
- # # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ]
445
- #
446
- # Note that the results never contain the entries +.+ and +..+ in
447
- # the directory because they are not children.
448
- #
449
- def children(with_directory=true)
450
- with_directory = false if @path == '.'
451
- result = []
452
- Dir.foreach(@path) {|e|
453
- next if e == '.' || e == '..'
454
- if with_directory
455
- result << self.class.new(File.join(@path, e))
456
- else
457
- result << self.class.new(e)
458
- end
459
- }
460
- result
461
- end
462
-
463
- # Iterates over the children of the directory
464
- # (files and subdirectories, not recursive).
465
- #
466
- # It yields Pathname object for each child.
467
- #
468
- # By default, the yielded pathnames will have enough information to access
469
- # the files.
470
- #
471
- # If you set +with_directory+ to +false+, then the returned pathnames will
472
- # contain the filename only.
473
- #
474
- # Pathname("/usr/local").each_child {|f| p f }
475
- # #=> #<Pathname:/usr/local/share>
476
- # # #<Pathname:/usr/local/bin>
477
- # # #<Pathname:/usr/local/games>
478
- # # #<Pathname:/usr/local/lib>
479
- # # #<Pathname:/usr/local/include>
480
- # # #<Pathname:/usr/local/sbin>
481
- # # #<Pathname:/usr/local/src>
482
- # # #<Pathname:/usr/local/man>
483
- #
484
- # Pathname("/usr/local").each_child(false) {|f| p f }
485
- # #=> #<Pathname:share>
486
- # # #<Pathname:bin>
487
- # # #<Pathname:games>
488
- # # #<Pathname:lib>
489
- # # #<Pathname:include>
490
- # # #<Pathname:sbin>
491
- # # #<Pathname:src>
492
- # # #<Pathname:man>
493
- #
494
- # Note that the results never contain the entries +.+ and +..+ in
495
- # the directory because they are not children.
496
- #
497
- # See Pathname#children
498
- #
499
- def each_child(with_directory=true, &b)
500
- children(with_directory).each(&b)
501
- end
502
-
503
- #
504
- # Returns a relative path from the given +base_directory+ to the receiver.
505
- #
506
- # If +self+ is absolute, then +base_directory+ must be absolute too.
507
- #
508
- # If +self+ is relative, then +base_directory+ must be relative too.
509
- #
510
- # This method doesn't access the filesystem. It assumes no symlinks.
511
- #
512
- # ArgumentError is raised when it cannot find a relative path.
513
- #
514
- # Note that this method does not handle situations where the case sensitivity
515
- # of the filesystem in use differs from the operating system default.
516
- #
517
- def relative_path_from(base_directory)
518
- base_directory = Pathname.new(base_directory) unless base_directory.is_a? Pathname
519
- dest_directory = self.cleanpath.to_s
520
- base_directory = base_directory.cleanpath.to_s
521
- dest_prefix = dest_directory
522
- dest_names = []
523
- while r = chop_basename(dest_prefix)
524
- dest_prefix, basename = r
525
- dest_names.unshift basename if basename != '.'
526
- end
527
- base_prefix = base_directory
528
- base_names = []
529
- while r = chop_basename(base_prefix)
530
- base_prefix, basename = r
531
- base_names.unshift basename if basename != '.'
532
- end
533
- unless SAME_PATHS[dest_prefix, base_prefix]
534
- raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
535
- end
536
- while !dest_names.empty? &&
537
- !base_names.empty? &&
538
- SAME_PATHS[dest_names.first, base_names.first]
539
- dest_names.shift
540
- base_names.shift
541
- end
542
- if base_names.include? '..'
543
- raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
544
- end
545
- base_names.fill('..')
546
- relpath_names = base_names + dest_names
547
- if relpath_names.empty?
548
- Pathname.new('.')
549
- else
550
- Pathname.new(File.join(*relpath_names))
551
- end
552
- end
16
+ require_relative 'pathname_builtin'
553
17
  end
554
18
 
555
-
556
19
  class Pathname # * Find *
557
20
  #
558
21
  # Iterates over the directory tree in a depth first manner, yielding a
@@ -581,16 +44,6 @@ end
581
44
 
582
45
 
583
46
  class Pathname # * FileUtils *
584
- # Creates a full path, including any intermediate directories that don't yet
585
- # exist.
586
- #
587
- # See FileUtils.mkpath and FileUtils.mkdir_p
588
- def mkpath(mode: nil)
589
- require 'fileutils'
590
- FileUtils.mkpath(@path, mode: mode)
591
- self
592
- end
593
-
594
47
  # Recursively deletes a directory, including all directories beneath it.
595
48
  #
596
49
  # See FileUtils.rm_rf