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.
- checksums.yaml +4 -4
- data/.document +5 -0
- data/.github/workflows/push_gem.yml +4 -4
- data/.github/workflows/test.yml +28 -2
- data/Rakefile +6 -2
- data/ext/pathname/extconf.rb +7 -1
- data/ext/pathname/pathname.c +14 -1569
- data/lib/pathname.rb +3 -550
- data/lib/pathname_builtin.rb +1219 -0
- data/pathname.gemspec +1 -1
- metadata +5 -6
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# = pathname.rb
|
|
4
|
+
#
|
|
5
|
+
# Object-Oriented Pathname Class
|
|
6
|
+
#
|
|
7
|
+
# Author:: Tanaka Akira <akr@m17n.org>
|
|
8
|
+
# Documentation:: Author and Gavin Sinclair
|
|
9
|
+
#
|
|
10
|
+
# For documentation, see class Pathname.
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Pathname represents the name of a file or directory on the filesystem,
|
|
15
|
+
# but not the file itself.
|
|
16
|
+
#
|
|
17
|
+
# The pathname depends on the Operating System: Unix, Windows, etc.
|
|
18
|
+
# This library works with pathnames of local OS, however non-Unix pathnames
|
|
19
|
+
# are supported experimentally.
|
|
20
|
+
#
|
|
21
|
+
# A Pathname can be relative or absolute. It's not until you try to
|
|
22
|
+
# reference the file that it even matters whether the file exists or not.
|
|
23
|
+
#
|
|
24
|
+
# Pathname is immutable. It has no method for destructive update.
|
|
25
|
+
#
|
|
26
|
+
# The goal of this class is to manipulate file path information in a neater
|
|
27
|
+
# way than standard Ruby provides. The examples below demonstrate the
|
|
28
|
+
# difference.
|
|
29
|
+
#
|
|
30
|
+
# *All* functionality from File, FileTest, and some from Dir and FileUtils is
|
|
31
|
+
# included, in an unsurprising way. It is essentially a facade for all of
|
|
32
|
+
# 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
|
|
76
|
+
#
|
|
77
|
+
# === Core methods
|
|
78
|
+
#
|
|
79
|
+
# These methods are effectively manipulating a String, because that's
|
|
80
|
+
# all a path is. None of these access the file system except for
|
|
81
|
+
# #mountpoint?, #children, #each_child, #realdirpath and #realpath.
|
|
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
|
+
# - #each_line(*args, &block)
|
|
130
|
+
# - #read(*args)
|
|
131
|
+
# - #binread(*args)
|
|
132
|
+
# - #readlines(*args)
|
|
133
|
+
# - #sysopen(*args)
|
|
134
|
+
# - #write(*args)
|
|
135
|
+
# - #binwrite(*args)
|
|
136
|
+
# - #atime
|
|
137
|
+
# - #birthtime
|
|
138
|
+
# - #ctime
|
|
139
|
+
# - #mtime
|
|
140
|
+
# - #chmod(mode)
|
|
141
|
+
# - #lchmod(mode)
|
|
142
|
+
# - #chown(owner, group)
|
|
143
|
+
# - #lchown(owner, group)
|
|
144
|
+
# - #fnmatch(pattern, *args)
|
|
145
|
+
# - #fnmatch?(pattern, *args)
|
|
146
|
+
# - #ftype
|
|
147
|
+
# - #make_link(old)
|
|
148
|
+
# - #open(*args, &block)
|
|
149
|
+
# - #readlink
|
|
150
|
+
# - #rename(to)
|
|
151
|
+
# - #stat
|
|
152
|
+
# - #lstat
|
|
153
|
+
# - #make_symlink(old)
|
|
154
|
+
# - #truncate(length)
|
|
155
|
+
# - #utime(atime, mtime)
|
|
156
|
+
# - #lutime(atime, mtime)
|
|
157
|
+
# - #basename(*args)
|
|
158
|
+
# - #dirname
|
|
159
|
+
# - #extname
|
|
160
|
+
# - #expand_path(*args)
|
|
161
|
+
# - #split
|
|
162
|
+
#
|
|
163
|
+
# === Directory methods
|
|
164
|
+
#
|
|
165
|
+
# These methods are a facade for Dir:
|
|
166
|
+
# - Pathname.glob(*args)
|
|
167
|
+
# - Pathname.getwd / Pathname.pwd
|
|
168
|
+
# - #rmdir
|
|
169
|
+
# - #entries
|
|
170
|
+
# - #each_entry(&block)
|
|
171
|
+
# - #mkdir(*args)
|
|
172
|
+
# - #opendir(*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
|
+
# The version string.
|
|
194
|
+
VERSION = "0.5.0"
|
|
195
|
+
|
|
196
|
+
# :stopdoc:
|
|
197
|
+
|
|
198
|
+
if File::FNM_SYSCASE.nonzero?
|
|
199
|
+
# Avoid #zero? here because #casecmp can return nil.
|
|
200
|
+
private def same_paths?(a, b) a.casecmp(b) == 0 end
|
|
201
|
+
else
|
|
202
|
+
private def same_paths?(a, b) a == b end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
attr_reader :path
|
|
206
|
+
protected :path
|
|
207
|
+
|
|
208
|
+
# :startdoc:
|
|
209
|
+
|
|
210
|
+
#
|
|
211
|
+
# Create a Pathname object from the given String (or String-like object).
|
|
212
|
+
# If +path+ contains a NUL character (<tt>\0</tt>), an ArgumentError is raised.
|
|
213
|
+
#
|
|
214
|
+
def initialize(path)
|
|
215
|
+
@path = File.path(path).dup
|
|
216
|
+
rescue TypeError => e
|
|
217
|
+
raise e.class, "Pathname.new requires a String, #to_path or #to_str", cause: nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
#
|
|
221
|
+
# Freze self.
|
|
222
|
+
#
|
|
223
|
+
def freeze
|
|
224
|
+
super
|
|
225
|
+
@path.freeze
|
|
226
|
+
self
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
#
|
|
230
|
+
# Compare this pathname with +other+. The comparison is string-based.
|
|
231
|
+
# Be aware that two different paths (<tt>foo.txt</tt> and <tt>./foo.txt</tt>)
|
|
232
|
+
# can refer to the same file.
|
|
233
|
+
#
|
|
234
|
+
def ==(other)
|
|
235
|
+
return false unless Pathname === other
|
|
236
|
+
other.path == @path
|
|
237
|
+
end
|
|
238
|
+
alias === ==
|
|
239
|
+
alias eql? ==
|
|
240
|
+
|
|
241
|
+
unless method_defined?(:<=>, false)
|
|
242
|
+
# Provides for comparing pathnames, case-sensitively.
|
|
243
|
+
def <=>(other)
|
|
244
|
+
return nil unless Pathname === other
|
|
245
|
+
@path.tr('/', "\0") <=> other.path.tr('/', "\0")
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def hash # :nodoc:
|
|
250
|
+
@path.hash
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Return the path as a String.
|
|
254
|
+
def to_s
|
|
255
|
+
@path.dup
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# to_path is implemented so Pathname objects are usable with File.open, etc.
|
|
259
|
+
alias to_path to_s
|
|
260
|
+
|
|
261
|
+
def inspect # :nodoc:
|
|
262
|
+
"#<#{self.class}:#{@path}>"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
unless method_defined?(:sub, false)
|
|
266
|
+
# Return a pathname which is substituted by String#sub.
|
|
267
|
+
def sub(pattern, *args, **kwargs, &block)
|
|
268
|
+
if block
|
|
269
|
+
path = @path.sub(pattern, *args, **kwargs) {|*sub_args|
|
|
270
|
+
begin
|
|
271
|
+
old = Thread.current[:pathname_sub_matchdata]
|
|
272
|
+
Thread.current[:pathname_sub_matchdata] = $~
|
|
273
|
+
eval("$~ = Thread.current[:pathname_sub_matchdata]", block.binding)
|
|
274
|
+
ensure
|
|
275
|
+
Thread.current[:pathname_sub_matchdata] = old
|
|
276
|
+
end
|
|
277
|
+
yield(*sub_args)
|
|
278
|
+
}
|
|
279
|
+
else
|
|
280
|
+
path = @path.sub(pattern, *args, **kwargs)
|
|
281
|
+
end
|
|
282
|
+
self.class.new(path)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Return a pathname with +repl+ added as a suffix to the basename.
|
|
287
|
+
#
|
|
288
|
+
# If self has no extension part, +repl+ is appended.
|
|
289
|
+
#
|
|
290
|
+
# Pathname.new('/usr/bin/shutdown').sub_ext('.rb')
|
|
291
|
+
# #=> #<Pathname:/usr/bin/shutdown.rb>
|
|
292
|
+
def sub_ext(repl)
|
|
293
|
+
ext = File.extname(@path)
|
|
294
|
+
|
|
295
|
+
# File.extname("foo.bar:stream") returns ".bar" on NTFS and not ".bar:stream"
|
|
296
|
+
# (see ruby_enc_find_extname()).
|
|
297
|
+
# The behavior of Pathname#sub_ext is to replace everything
|
|
298
|
+
# from the start of the extname until the end of the path with repl.
|
|
299
|
+
unless @path.end_with?(ext)
|
|
300
|
+
ext = @path[@path.rindex(ext)..]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
self.class.new(@path.chomp(ext) + repl)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
if File::ALT_SEPARATOR
|
|
307
|
+
# Separator list string.
|
|
308
|
+
SEPARATOR_LIST = Regexp.quote "#{File::ALT_SEPARATOR}#{File::SEPARATOR}"
|
|
309
|
+
# Regexp that matches a separator.
|
|
310
|
+
SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
|
|
311
|
+
else
|
|
312
|
+
SEPARATOR_LIST = Regexp.quote File::SEPARATOR
|
|
313
|
+
SEPARATOR_PAT = /#{SEPARATOR_LIST}/
|
|
314
|
+
end
|
|
315
|
+
SEPARATOR_LIST.freeze
|
|
316
|
+
SEPARATOR_PAT.freeze
|
|
317
|
+
private_constant :SEPARATOR_LIST, :SEPARATOR_LIST
|
|
318
|
+
|
|
319
|
+
if File.dirname('A:') == 'A:.' # DOSish drive letter
|
|
320
|
+
# Regexp that matches an absolute path.
|
|
321
|
+
ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/
|
|
322
|
+
else
|
|
323
|
+
ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/
|
|
324
|
+
end
|
|
325
|
+
ABSOLUTE_PATH.freeze
|
|
326
|
+
private_constant :ABSOLUTE_PATH
|
|
327
|
+
|
|
328
|
+
# :startdoc:
|
|
329
|
+
|
|
330
|
+
# Creates a full path, including any intermediate directories that don't yet
|
|
331
|
+
# exist.
|
|
332
|
+
#
|
|
333
|
+
# See FileUtils.mkpath and FileUtils.mkdir_p
|
|
334
|
+
def mkpath(mode: nil)
|
|
335
|
+
path = @path == '/' ? @path : @path.chomp('/')
|
|
336
|
+
|
|
337
|
+
stack = []
|
|
338
|
+
until File.directory?(path) || File.dirname(path) == path
|
|
339
|
+
stack.push path
|
|
340
|
+
path = File.dirname(path)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
stack.reverse_each do |dir|
|
|
344
|
+
dir = dir == '/' ? dir : dir.chomp('/')
|
|
345
|
+
if mode
|
|
346
|
+
Dir.mkdir dir, mode
|
|
347
|
+
File.chmod mode, dir
|
|
348
|
+
else
|
|
349
|
+
Dir.mkdir dir
|
|
350
|
+
end
|
|
351
|
+
rescue SystemCallError
|
|
352
|
+
raise unless File.directory?(dir)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
self
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# chop_basename(path) -> [pre-basename, basename] or nil
|
|
359
|
+
def chop_basename(path) # :nodoc:
|
|
360
|
+
base = File.basename(path)
|
|
361
|
+
if /\A#{SEPARATOR_PAT}?\z/o.match?(base)
|
|
362
|
+
return nil
|
|
363
|
+
else
|
|
364
|
+
return path[0, path.rindex(base)], base
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
private :chop_basename
|
|
368
|
+
|
|
369
|
+
# split_names(path) -> prefix, [name, ...]
|
|
370
|
+
def split_names(path) # :nodoc:
|
|
371
|
+
names = []
|
|
372
|
+
while r = chop_basename(path)
|
|
373
|
+
path, basename = r
|
|
374
|
+
names.unshift basename
|
|
375
|
+
end
|
|
376
|
+
return path, names
|
|
377
|
+
end
|
|
378
|
+
private :split_names
|
|
379
|
+
|
|
380
|
+
def prepend_prefix(prefix, relpath) # :nodoc:
|
|
381
|
+
if relpath.empty?
|
|
382
|
+
File.dirname(prefix)
|
|
383
|
+
elsif SEPARATOR_PAT.match?(prefix)
|
|
384
|
+
prefix = File.dirname(prefix)
|
|
385
|
+
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
|
|
386
|
+
prefix + relpath
|
|
387
|
+
else
|
|
388
|
+
prefix + relpath
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
private :prepend_prefix
|
|
392
|
+
|
|
393
|
+
# Returns clean pathname of +self+ with consecutive slashes and useless dots
|
|
394
|
+
# removed. The filesystem is not accessed.
|
|
395
|
+
#
|
|
396
|
+
# If +consider_symlink+ is +true+, then a more conservative algorithm is used
|
|
397
|
+
# to avoid breaking symbolic linkages. This may retain more +..+
|
|
398
|
+
# entries than absolutely necessary, but without accessing the filesystem,
|
|
399
|
+
# this can't be avoided.
|
|
400
|
+
#
|
|
401
|
+
# See Pathname#realpath.
|
|
402
|
+
#
|
|
403
|
+
def cleanpath(consider_symlink=false)
|
|
404
|
+
if consider_symlink
|
|
405
|
+
cleanpath_conservative
|
|
406
|
+
else
|
|
407
|
+
cleanpath_aggressive
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
#
|
|
412
|
+
# Clean the path simply by resolving and removing excess +.+ and +..+ entries.
|
|
413
|
+
# Nothing more, nothing less.
|
|
414
|
+
#
|
|
415
|
+
def cleanpath_aggressive # :nodoc:
|
|
416
|
+
path = @path
|
|
417
|
+
names = []
|
|
418
|
+
pre = path
|
|
419
|
+
while r = chop_basename(pre)
|
|
420
|
+
pre, base = r
|
|
421
|
+
case base
|
|
422
|
+
when '.'
|
|
423
|
+
when '..'
|
|
424
|
+
names.unshift base
|
|
425
|
+
else
|
|
426
|
+
if names[0] == '..'
|
|
427
|
+
names.shift
|
|
428
|
+
else
|
|
429
|
+
names.unshift base
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
|
434
|
+
if SEPARATOR_PAT.match?(File.basename(pre))
|
|
435
|
+
names.shift while names[0] == '..'
|
|
436
|
+
end
|
|
437
|
+
self.class.new(prepend_prefix(pre, File.join(*names)))
|
|
438
|
+
end
|
|
439
|
+
private :cleanpath_aggressive
|
|
440
|
+
|
|
441
|
+
# has_trailing_separator?(path) -> bool
|
|
442
|
+
def has_trailing_separator?(path) # :nodoc:
|
|
443
|
+
if r = chop_basename(path)
|
|
444
|
+
pre, basename = r
|
|
445
|
+
pre.length + basename.length < path.length
|
|
446
|
+
else
|
|
447
|
+
false
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
private :has_trailing_separator?
|
|
451
|
+
|
|
452
|
+
# add_trailing_separator(path) -> path
|
|
453
|
+
def add_trailing_separator(path) # :nodoc:
|
|
454
|
+
if File.basename(path + 'a') == 'a'
|
|
455
|
+
path
|
|
456
|
+
else
|
|
457
|
+
File.join(path, "") # xxx: Is File.join is appropriate to add separator?
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
private :add_trailing_separator
|
|
461
|
+
|
|
462
|
+
def del_trailing_separator(path) # :nodoc:
|
|
463
|
+
if r = chop_basename(path)
|
|
464
|
+
pre, basename = r
|
|
465
|
+
pre + basename
|
|
466
|
+
elsif /#{SEPARATOR_PAT}+\z/o =~ path
|
|
467
|
+
$` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
|
|
468
|
+
else
|
|
469
|
+
path
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
private :del_trailing_separator
|
|
473
|
+
|
|
474
|
+
def cleanpath_conservative # :nodoc:
|
|
475
|
+
path = @path
|
|
476
|
+
names = []
|
|
477
|
+
pre = path
|
|
478
|
+
while r = chop_basename(pre)
|
|
479
|
+
pre, base = r
|
|
480
|
+
names.unshift base if base != '.'
|
|
481
|
+
end
|
|
482
|
+
pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
|
483
|
+
if SEPARATOR_PAT.match?(File.basename(pre))
|
|
484
|
+
names.shift while names[0] == '..'
|
|
485
|
+
end
|
|
486
|
+
if names.empty?
|
|
487
|
+
self.class.new(File.dirname(pre))
|
|
488
|
+
else
|
|
489
|
+
if names.last != '..' && File.basename(path) == '.'
|
|
490
|
+
names << '.'
|
|
491
|
+
end
|
|
492
|
+
result = prepend_prefix(pre, File.join(*names))
|
|
493
|
+
if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
|
|
494
|
+
self.class.new(add_trailing_separator(result))
|
|
495
|
+
else
|
|
496
|
+
self.class.new(result)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
private :cleanpath_conservative
|
|
501
|
+
|
|
502
|
+
# Returns the parent directory.
|
|
503
|
+
#
|
|
504
|
+
# This is same as <code>self + '..'</code>.
|
|
505
|
+
def parent
|
|
506
|
+
self + '..'
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Returns +true+ if +self+ points to a mountpoint.
|
|
510
|
+
def mountpoint?
|
|
511
|
+
begin
|
|
512
|
+
stat1 = self.lstat
|
|
513
|
+
stat2 = self.parent.lstat
|
|
514
|
+
stat1.dev != stat2.dev || stat1.ino == stat2.ino
|
|
515
|
+
rescue Errno::ENOENT
|
|
516
|
+
false
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
#
|
|
521
|
+
# Predicate method for root directories. Returns +true+ if the
|
|
522
|
+
# pathname consists of consecutive slashes.
|
|
523
|
+
#
|
|
524
|
+
# It doesn't access the filesystem. So it may return +false+ for some
|
|
525
|
+
# pathnames which points to roots such as <tt>/usr/..</tt>.
|
|
526
|
+
#
|
|
527
|
+
def root?
|
|
528
|
+
chop_basename(@path) == nil && SEPARATOR_PAT.match?(@path)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Predicate method for testing whether a path is absolute.
|
|
532
|
+
#
|
|
533
|
+
# It returns +true+ if the pathname begins with a slash.
|
|
534
|
+
#
|
|
535
|
+
# p = Pathname.new('/im/sure')
|
|
536
|
+
# p.absolute?
|
|
537
|
+
# #=> true
|
|
538
|
+
#
|
|
539
|
+
# p = Pathname.new('not/so/sure')
|
|
540
|
+
# p.absolute?
|
|
541
|
+
# #=> false
|
|
542
|
+
def absolute?
|
|
543
|
+
ABSOLUTE_PATH.match? @path
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# The opposite of Pathname#absolute?
|
|
547
|
+
#
|
|
548
|
+
# It returns +false+ if the pathname begins with a slash.
|
|
549
|
+
#
|
|
550
|
+
# p = Pathname.new('/im/sure')
|
|
551
|
+
# p.relative?
|
|
552
|
+
# #=> false
|
|
553
|
+
#
|
|
554
|
+
# p = Pathname.new('not/so/sure')
|
|
555
|
+
# p.relative?
|
|
556
|
+
# #=> true
|
|
557
|
+
def relative?
|
|
558
|
+
!absolute?
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
#
|
|
562
|
+
# Iterates over each component of the path.
|
|
563
|
+
#
|
|
564
|
+
# Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
|
|
565
|
+
# # yields "usr", "bin", and "ruby".
|
|
566
|
+
#
|
|
567
|
+
# Returns an Enumerator if no block was given.
|
|
568
|
+
#
|
|
569
|
+
# enum = Pathname.new("/usr/bin/ruby").each_filename
|
|
570
|
+
# # ... do stuff ...
|
|
571
|
+
# enum.each { |e| ... }
|
|
572
|
+
# # yields "usr", "bin", and "ruby".
|
|
573
|
+
#
|
|
574
|
+
def each_filename # :yield: filename
|
|
575
|
+
return to_enum(__method__) unless block_given?
|
|
576
|
+
_, names = split_names(@path)
|
|
577
|
+
names.each {|filename| yield filename }
|
|
578
|
+
nil
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# Iterates over and yields a new Pathname object
|
|
582
|
+
# for each element in the given path in descending order.
|
|
583
|
+
#
|
|
584
|
+
# Pathname.new('/path/to/some/file.rb').descend {|v| p v}
|
|
585
|
+
# #<Pathname:/>
|
|
586
|
+
# #<Pathname:/path>
|
|
587
|
+
# #<Pathname:/path/to>
|
|
588
|
+
# #<Pathname:/path/to/some>
|
|
589
|
+
# #<Pathname:/path/to/some/file.rb>
|
|
590
|
+
#
|
|
591
|
+
# Pathname.new('path/to/some/file.rb').descend {|v| p v}
|
|
592
|
+
# #<Pathname:path>
|
|
593
|
+
# #<Pathname:path/to>
|
|
594
|
+
# #<Pathname:path/to/some>
|
|
595
|
+
# #<Pathname:path/to/some/file.rb>
|
|
596
|
+
#
|
|
597
|
+
# Returns an Enumerator if no block was given.
|
|
598
|
+
#
|
|
599
|
+
# enum = Pathname.new("/usr/bin/ruby").descend
|
|
600
|
+
# # ... do stuff ...
|
|
601
|
+
# enum.each { |e| ... }
|
|
602
|
+
# # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby.
|
|
603
|
+
#
|
|
604
|
+
# It doesn't access the filesystem.
|
|
605
|
+
#
|
|
606
|
+
def descend
|
|
607
|
+
return to_enum(__method__) unless block_given?
|
|
608
|
+
vs = []
|
|
609
|
+
ascend {|v| vs << v }
|
|
610
|
+
vs.reverse_each {|v| yield v }
|
|
611
|
+
nil
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Iterates over and yields a new Pathname object
|
|
615
|
+
# for each element in the given path in ascending order.
|
|
616
|
+
#
|
|
617
|
+
# Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
|
|
618
|
+
# #<Pathname:/path/to/some/file.rb>
|
|
619
|
+
# #<Pathname:/path/to/some>
|
|
620
|
+
# #<Pathname:/path/to>
|
|
621
|
+
# #<Pathname:/path>
|
|
622
|
+
# #<Pathname:/>
|
|
623
|
+
#
|
|
624
|
+
# Pathname.new('path/to/some/file.rb').ascend {|v| p v}
|
|
625
|
+
# #<Pathname:path/to/some/file.rb>
|
|
626
|
+
# #<Pathname:path/to/some>
|
|
627
|
+
# #<Pathname:path/to>
|
|
628
|
+
# #<Pathname:path>
|
|
629
|
+
#
|
|
630
|
+
# Returns an Enumerator if no block was given.
|
|
631
|
+
#
|
|
632
|
+
# enum = Pathname.new("/usr/bin/ruby").ascend
|
|
633
|
+
# # ... do stuff ...
|
|
634
|
+
# enum.each { |e| ... }
|
|
635
|
+
# # yields Pathnames /usr/bin/ruby, /usr/bin, /usr, and /.
|
|
636
|
+
#
|
|
637
|
+
# It doesn't access the filesystem.
|
|
638
|
+
#
|
|
639
|
+
def ascend
|
|
640
|
+
return to_enum(__method__) unless block_given?
|
|
641
|
+
path = @path
|
|
642
|
+
yield self
|
|
643
|
+
while r = chop_basename(path)
|
|
644
|
+
path, = r
|
|
645
|
+
break if path.empty?
|
|
646
|
+
yield self.class.new(del_trailing_separator(path))
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# call-seq:
|
|
651
|
+
# self + other -> new_pathname
|
|
652
|
+
#
|
|
653
|
+
# Returns a new \Pathname object;
|
|
654
|
+
# argument +other+ may be a string or another pathname.
|
|
655
|
+
#
|
|
656
|
+
# When +other+ specifies a relative path (see #relative?),
|
|
657
|
+
# it is combined with +self+ to form a new pathname:
|
|
658
|
+
#
|
|
659
|
+
# Pathname.new('/a/b') + 'c' # => #<Pathname:/a/b/c>
|
|
660
|
+
#
|
|
661
|
+
# Extra component separators (<tt>'/'</tt>) are removed:
|
|
662
|
+
#
|
|
663
|
+
# Pathname.new('/a/b/') + 'c' # => #<Pathname:/a/b/c>
|
|
664
|
+
#
|
|
665
|
+
# Extra current-directory components (<tt>'.'</tt>) are removed:
|
|
666
|
+
#
|
|
667
|
+
# Pathname.new('a') + '.' # => #<Pathname:a>
|
|
668
|
+
# Pathname.new('.') + 'a' # => #<Pathname:a>
|
|
669
|
+
# Pathname.new('.') + '.' # => #<Pathname:.>
|
|
670
|
+
#
|
|
671
|
+
# Parent-directory components (<tt>'..'</tt>) are:
|
|
672
|
+
#
|
|
673
|
+
# - Resolved, when possible:
|
|
674
|
+
#
|
|
675
|
+
# Pathname.new('a') + '..' # => #<Pathname:.>
|
|
676
|
+
# Pathname.new('a/b') + '..' # => #<Pathname:a>
|
|
677
|
+
# Pathname.new('/') + '../a' # => #<Pathname:/a>
|
|
678
|
+
# Pathname.new('a') + '../b' # => #<Pathname:b>
|
|
679
|
+
# Pathname.new('a/b') + '../c' # => #<Pathname:a/c>
|
|
680
|
+
# Pathname.new('a//b/c') + '../d//e' # => #<Pathname:a//b/d//e>
|
|
681
|
+
#
|
|
682
|
+
# - Removed, when not needed:
|
|
683
|
+
#
|
|
684
|
+
# Pathname.new('/') + '..' # => #<Pathname:/>
|
|
685
|
+
#
|
|
686
|
+
# - Retained, when needed:
|
|
687
|
+
#
|
|
688
|
+
# Pathname.new('..') + '..' # => #<Pathname:../..>
|
|
689
|
+
# Pathname.new('..') + '../a' # => #<Pathname:../../a>
|
|
690
|
+
#
|
|
691
|
+
# When +other+ specifies an absolute path (see #absolute?),
|
|
692
|
+
# equivalent to <tt>Pathname.new(other.to_s)</tt>:
|
|
693
|
+
#
|
|
694
|
+
# Pathname.new('/a') + '/b/c' # => #<Pathname:/b/c>
|
|
695
|
+
#
|
|
696
|
+
# Occurrences of <tt>'/'</tt>, <tt>'.'</tt>, and <tt>'..'</tt> are preserved:
|
|
697
|
+
#
|
|
698
|
+
# Pathname.new('/a') + '//b//c/./../d' # => #<Pathname://b//c/./../d>
|
|
699
|
+
#
|
|
700
|
+
# This method does not access the file system, so +other+ need not represent
|
|
701
|
+
# an existing (or even a valid) file or directory path:
|
|
702
|
+
#
|
|
703
|
+
# Pathname.new('/var') + 'nosuch:ever' # => #<Pathname:/var/nosuch:ever>
|
|
704
|
+
#
|
|
705
|
+
def +(other)
|
|
706
|
+
other = Pathname.new(other) unless Pathname === other
|
|
707
|
+
Pathname.new(plus(@path, other.path))
|
|
708
|
+
end
|
|
709
|
+
alias / +
|
|
710
|
+
|
|
711
|
+
def plus(path1, path2) # -> path # :nodoc:
|
|
712
|
+
prefix2 = path2
|
|
713
|
+
index_list2 = []
|
|
714
|
+
basename_list2 = []
|
|
715
|
+
while r2 = chop_basename(prefix2)
|
|
716
|
+
prefix2, basename2 = r2
|
|
717
|
+
index_list2.unshift prefix2.length
|
|
718
|
+
basename_list2.unshift basename2
|
|
719
|
+
end
|
|
720
|
+
return path2 if prefix2 != ''
|
|
721
|
+
prefix1 = path1
|
|
722
|
+
while true
|
|
723
|
+
while !basename_list2.empty? && basename_list2.first == '.'
|
|
724
|
+
index_list2.shift
|
|
725
|
+
basename_list2.shift
|
|
726
|
+
end
|
|
727
|
+
break unless r1 = chop_basename(prefix1)
|
|
728
|
+
prefix1, basename1 = r1
|
|
729
|
+
next if basename1 == '.'
|
|
730
|
+
if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
|
|
731
|
+
prefix1 = prefix1 + basename1
|
|
732
|
+
break
|
|
733
|
+
end
|
|
734
|
+
index_list2.shift
|
|
735
|
+
basename_list2.shift
|
|
736
|
+
end
|
|
737
|
+
r1 = chop_basename(prefix1)
|
|
738
|
+
if !r1 && (r1 = SEPARATOR_PAT.match?(File.basename(prefix1)))
|
|
739
|
+
while !basename_list2.empty? && basename_list2.first == '..'
|
|
740
|
+
index_list2.shift
|
|
741
|
+
basename_list2.shift
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
if !basename_list2.empty?
|
|
745
|
+
suffix2 = path2[index_list2.first..-1]
|
|
746
|
+
r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
|
|
747
|
+
else
|
|
748
|
+
r1 ? prefix1 : File.dirname(prefix1)
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
private :plus
|
|
752
|
+
|
|
753
|
+
#
|
|
754
|
+
# Joins the given pathnames onto +self+ to create a new Pathname object.
|
|
755
|
+
# This is effectively the same as using Pathname#+ to append +self+ and
|
|
756
|
+
# all arguments sequentially.
|
|
757
|
+
#
|
|
758
|
+
# path0 = Pathname.new("/usr") # Pathname:/usr
|
|
759
|
+
# path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby
|
|
760
|
+
# # is the same as
|
|
761
|
+
# path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby
|
|
762
|
+
# path0 == path1
|
|
763
|
+
# #=> true
|
|
764
|
+
#
|
|
765
|
+
def join(*args)
|
|
766
|
+
return self if args.empty?
|
|
767
|
+
result = args.pop
|
|
768
|
+
result = Pathname.new(result) unless Pathname === result
|
|
769
|
+
return result if result.absolute?
|
|
770
|
+
args.reverse_each {|arg|
|
|
771
|
+
arg = Pathname.new(arg) unless Pathname === arg
|
|
772
|
+
result = arg + result
|
|
773
|
+
return result if result.absolute?
|
|
774
|
+
}
|
|
775
|
+
self + result
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
#
|
|
779
|
+
# Returns the children of the directory (files and subdirectories, not
|
|
780
|
+
# recursive) as an array of Pathname objects.
|
|
781
|
+
#
|
|
782
|
+
# By default, the returned pathnames will have enough information to access
|
|
783
|
+
# the files. If you set +with_directory+ to +false+, then the returned
|
|
784
|
+
# pathnames will contain the filename only.
|
|
785
|
+
#
|
|
786
|
+
# For example:
|
|
787
|
+
# pn = Pathname("/usr/lib/ruby/1.8")
|
|
788
|
+
# pn.children
|
|
789
|
+
# # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
|
|
790
|
+
# Pathname:/usr/lib/ruby/1.8/Env.rb,
|
|
791
|
+
# Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
|
|
792
|
+
# pn.children(false)
|
|
793
|
+
# # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ]
|
|
794
|
+
#
|
|
795
|
+
# Note that the results never contain the entries +.+ and +..+ in
|
|
796
|
+
# the directory because they are not children.
|
|
797
|
+
#
|
|
798
|
+
def children(with_directory=true)
|
|
799
|
+
with_directory = false if @path == '.'
|
|
800
|
+
result = []
|
|
801
|
+
Dir.foreach(@path) {|e|
|
|
802
|
+
next if e == '.' || e == '..'
|
|
803
|
+
if with_directory
|
|
804
|
+
result << self.class.new(File.join(@path, e))
|
|
805
|
+
else
|
|
806
|
+
result << self.class.new(e)
|
|
807
|
+
end
|
|
808
|
+
}
|
|
809
|
+
result
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
# Iterates over the children of the directory
|
|
813
|
+
# (files and subdirectories, not recursive).
|
|
814
|
+
#
|
|
815
|
+
# It yields Pathname object for each child.
|
|
816
|
+
#
|
|
817
|
+
# By default, the yielded pathnames will have enough information to access
|
|
818
|
+
# the files.
|
|
819
|
+
#
|
|
820
|
+
# If you set +with_directory+ to +false+, then the returned pathnames will
|
|
821
|
+
# contain the filename only.
|
|
822
|
+
#
|
|
823
|
+
# Pathname("/usr/local").each_child {|f| p f }
|
|
824
|
+
# #=> #<Pathname:/usr/local/share>
|
|
825
|
+
# # #<Pathname:/usr/local/bin>
|
|
826
|
+
# # #<Pathname:/usr/local/games>
|
|
827
|
+
# # #<Pathname:/usr/local/lib>
|
|
828
|
+
# # #<Pathname:/usr/local/include>
|
|
829
|
+
# # #<Pathname:/usr/local/sbin>
|
|
830
|
+
# # #<Pathname:/usr/local/src>
|
|
831
|
+
# # #<Pathname:/usr/local/man>
|
|
832
|
+
#
|
|
833
|
+
# Pathname("/usr/local").each_child(false) {|f| p f }
|
|
834
|
+
# #=> #<Pathname:share>
|
|
835
|
+
# # #<Pathname:bin>
|
|
836
|
+
# # #<Pathname:games>
|
|
837
|
+
# # #<Pathname:lib>
|
|
838
|
+
# # #<Pathname:include>
|
|
839
|
+
# # #<Pathname:sbin>
|
|
840
|
+
# # #<Pathname:src>
|
|
841
|
+
# # #<Pathname:man>
|
|
842
|
+
#
|
|
843
|
+
# Note that the results never contain the entries +.+ and +..+ in
|
|
844
|
+
# the directory because they are not children.
|
|
845
|
+
#
|
|
846
|
+
# See Pathname#children
|
|
847
|
+
#
|
|
848
|
+
def each_child(with_directory=true, &b)
|
|
849
|
+
children(with_directory).each(&b)
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
#
|
|
853
|
+
# Returns a relative path from the given +base_directory+ to the receiver.
|
|
854
|
+
#
|
|
855
|
+
# If +self+ is absolute, then +base_directory+ must be absolute too.
|
|
856
|
+
#
|
|
857
|
+
# If +self+ is relative, then +base_directory+ must be relative too.
|
|
858
|
+
#
|
|
859
|
+
# This method doesn't access the filesystem. It assumes no symlinks.
|
|
860
|
+
#
|
|
861
|
+
# ArgumentError is raised when it cannot find a relative path.
|
|
862
|
+
#
|
|
863
|
+
# Note that this method does not handle situations where the case sensitivity
|
|
864
|
+
# of the filesystem in use differs from the operating system default.
|
|
865
|
+
#
|
|
866
|
+
def relative_path_from(base_directory)
|
|
867
|
+
base_directory = Pathname.new(base_directory) unless base_directory.is_a? Pathname
|
|
868
|
+
dest_directory = self.cleanpath.path
|
|
869
|
+
base_directory = base_directory.cleanpath.path
|
|
870
|
+
dest_prefix = dest_directory
|
|
871
|
+
dest_names = []
|
|
872
|
+
while r = chop_basename(dest_prefix)
|
|
873
|
+
dest_prefix, basename = r
|
|
874
|
+
dest_names.unshift basename if basename != '.'
|
|
875
|
+
end
|
|
876
|
+
base_prefix = base_directory
|
|
877
|
+
base_names = []
|
|
878
|
+
while r = chop_basename(base_prefix)
|
|
879
|
+
base_prefix, basename = r
|
|
880
|
+
base_names.unshift basename if basename != '.'
|
|
881
|
+
end
|
|
882
|
+
unless same_paths?(dest_prefix, base_prefix)
|
|
883
|
+
raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
|
|
884
|
+
end
|
|
885
|
+
while !dest_names.empty? &&
|
|
886
|
+
!base_names.empty? &&
|
|
887
|
+
same_paths?(dest_names.first, base_names.first)
|
|
888
|
+
dest_names.shift
|
|
889
|
+
base_names.shift
|
|
890
|
+
end
|
|
891
|
+
if base_names.include? '..'
|
|
892
|
+
raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
|
|
893
|
+
end
|
|
894
|
+
base_names.fill('..')
|
|
895
|
+
relpath_names = base_names + dest_names
|
|
896
|
+
if relpath_names.empty?
|
|
897
|
+
Pathname.new('.')
|
|
898
|
+
else
|
|
899
|
+
Pathname.new(File.join(*relpath_names))
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
class Pathname # * File *
|
|
905
|
+
#
|
|
906
|
+
# #each_line iterates over the line in the file. It yields a String object
|
|
907
|
+
# for each line.
|
|
908
|
+
#
|
|
909
|
+
# This method has existed since 1.8.1.
|
|
910
|
+
#
|
|
911
|
+
def each_line(...) # :yield: line
|
|
912
|
+
File.foreach(@path, ...)
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
# See <tt>File.read</tt>. Returns all data from the file, or the first +N+ bytes
|
|
916
|
+
# if specified.
|
|
917
|
+
def read(...) File.read(@path, ...) end
|
|
918
|
+
|
|
919
|
+
# See <tt>File.binread</tt>. Returns all the bytes from the file, or the first +N+
|
|
920
|
+
# if specified.
|
|
921
|
+
def binread(...) File.binread(@path, ...) end
|
|
922
|
+
|
|
923
|
+
# See <tt>File.readlines</tt>. Returns all the lines from the file.
|
|
924
|
+
def readlines(...) File.readlines(@path, ...) end
|
|
925
|
+
|
|
926
|
+
# See <tt>File.sysopen</tt>.
|
|
927
|
+
def sysopen(...) File.sysopen(@path, ...) end
|
|
928
|
+
|
|
929
|
+
# Writes +contents+ to the file. See <tt>File.write</tt>.
|
|
930
|
+
def write(...) File.write(@path, ...) end
|
|
931
|
+
|
|
932
|
+
# Writes +contents+ to the file, opening it in binary mode.
|
|
933
|
+
#
|
|
934
|
+
# See File.binwrite.
|
|
935
|
+
def binwrite(...) File.binwrite(@path, ...) end
|
|
936
|
+
|
|
937
|
+
# See <tt>File.atime</tt>. Returns last access time.
|
|
938
|
+
def atime() File.atime(@path) end
|
|
939
|
+
|
|
940
|
+
# Returns the birth time for the file.
|
|
941
|
+
# If the platform doesn't have birthtime, raises NotImplementedError.
|
|
942
|
+
#
|
|
943
|
+
# See File.birthtime.
|
|
944
|
+
def birthtime() File.birthtime(@path) end
|
|
945
|
+
|
|
946
|
+
# See <tt>File.ctime</tt>. Returns last (directory entry, not file) change time.
|
|
947
|
+
def ctime() File.ctime(@path) end
|
|
948
|
+
|
|
949
|
+
# See <tt>File.mtime</tt>. Returns last modification time.
|
|
950
|
+
def mtime() File.mtime(@path) end
|
|
951
|
+
|
|
952
|
+
# See <tt>File.chmod</tt>. Changes permissions.
|
|
953
|
+
def chmod(mode) File.chmod(mode, @path) end
|
|
954
|
+
|
|
955
|
+
# See <tt>File.lchmod</tt>.
|
|
956
|
+
def lchmod(mode) File.lchmod(mode, @path) end
|
|
957
|
+
|
|
958
|
+
# See <tt>File.chown</tt>. Change owner and group of file.
|
|
959
|
+
def chown(owner, group) File.chown(owner, group, @path) end
|
|
960
|
+
|
|
961
|
+
# See <tt>File.lchown</tt>.
|
|
962
|
+
def lchown(owner, group) File.lchown(owner, group, @path) end
|
|
963
|
+
|
|
964
|
+
# See <tt>File.fnmatch</tt>. Return +true+ if the receiver matches the given
|
|
965
|
+
# pattern.
|
|
966
|
+
def fnmatch(pattern, ...) File.fnmatch(pattern, @path, ...) end
|
|
967
|
+
|
|
968
|
+
# See <tt>File.fnmatch?</tt> (same as #fnmatch).
|
|
969
|
+
def fnmatch?(pattern, ...) File.fnmatch?(pattern, @path, ...) end
|
|
970
|
+
|
|
971
|
+
# See <tt>File.ftype</tt>. Returns "type" of file ("file", "directory",
|
|
972
|
+
# etc).
|
|
973
|
+
def ftype() File.ftype(@path) end
|
|
974
|
+
|
|
975
|
+
# See <tt>File.link</tt>. Creates a hard link.
|
|
976
|
+
def make_link(old) File.link(old, @path) end
|
|
977
|
+
|
|
978
|
+
# See <tt>File.open</tt>. Opens the file for reading or writing.
|
|
979
|
+
def open(...) # :yield: file
|
|
980
|
+
File.open(@path, ...)
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
# See <tt>File.readlink</tt>. Read symbolic link.
|
|
984
|
+
def readlink() self.class.new(File.readlink(@path)) end
|
|
985
|
+
|
|
986
|
+
# See <tt>File.rename</tt>. Rename the file.
|
|
987
|
+
def rename(to) File.rename(@path, to) end
|
|
988
|
+
|
|
989
|
+
# See <tt>File.stat</tt>. Returns a <tt>File::Stat</tt> object.
|
|
990
|
+
def stat() File.stat(@path) end
|
|
991
|
+
|
|
992
|
+
# See <tt>File.lstat</tt>.
|
|
993
|
+
def lstat() File.lstat(@path) end
|
|
994
|
+
|
|
995
|
+
# See <tt>File.symlink</tt>. Creates a symbolic link.
|
|
996
|
+
def make_symlink(old) File.symlink(old, @path) end
|
|
997
|
+
|
|
998
|
+
# See <tt>File.truncate</tt>. Truncate the file to +length+ bytes.
|
|
999
|
+
def truncate(length) File.truncate(@path, length) end
|
|
1000
|
+
|
|
1001
|
+
# See <tt>File.utime</tt>. Update the access and modification times.
|
|
1002
|
+
def utime(atime, mtime) File.utime(atime, mtime, @path) end
|
|
1003
|
+
|
|
1004
|
+
# Update the access and modification times of the file.
|
|
1005
|
+
#
|
|
1006
|
+
# Same as Pathname#utime, but does not follow symbolic links.
|
|
1007
|
+
#
|
|
1008
|
+
# See File.lutime.
|
|
1009
|
+
def lutime(atime, mtime) File.lutime(atime, mtime, @path) end
|
|
1010
|
+
|
|
1011
|
+
# See <tt>File.basename</tt>. Returns the last component of the path.
|
|
1012
|
+
def basename(...) self.class.new(File.basename(@path, ...)) end
|
|
1013
|
+
|
|
1014
|
+
# See <tt>File.dirname</tt>. Returns all but the last component of the path.
|
|
1015
|
+
def dirname() self.class.new(File.dirname(@path)) end
|
|
1016
|
+
|
|
1017
|
+
# See <tt>File.extname</tt>. Returns the file's extension.
|
|
1018
|
+
def extname() File.extname(@path) end
|
|
1019
|
+
|
|
1020
|
+
# See <tt>File.expand_path</tt>.
|
|
1021
|
+
def expand_path(...) self.class.new(File.expand_path(@path, ...)) end
|
|
1022
|
+
|
|
1023
|
+
# See <tt>File.split</tt>. Returns the #dirname and the #basename in an
|
|
1024
|
+
# Array.
|
|
1025
|
+
def split()
|
|
1026
|
+
array = File.split(@path)
|
|
1027
|
+
raise TypeError, 'wrong argument type nil (expected Array)' unless Array === array
|
|
1028
|
+
array.map {|f| self.class.new(f) }
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
# Returns the real (absolute) pathname for +self+ in the actual filesystem.
|
|
1032
|
+
#
|
|
1033
|
+
# Does not contain symlinks or useless dots, +..+ and +.+.
|
|
1034
|
+
#
|
|
1035
|
+
# All components of the pathname must exist when this method is called.
|
|
1036
|
+
def realpath(...) self.class.new(File.realpath(@path, ...)) end
|
|
1037
|
+
|
|
1038
|
+
# Returns the real (absolute) pathname of +self+ in the actual filesystem.
|
|
1039
|
+
#
|
|
1040
|
+
# Does not contain symlinks or useless dots, +..+ and +.+.
|
|
1041
|
+
#
|
|
1042
|
+
# The last component of the real pathname can be nonexistent.
|
|
1043
|
+
def realdirpath(...) self.class.new(File.realdirpath(@path, ...)) end
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
class Pathname # * FileTest *
|
|
1048
|
+
|
|
1049
|
+
# See <tt>FileTest.blockdev?</tt>.
|
|
1050
|
+
def blockdev?() FileTest.blockdev?(@path) end
|
|
1051
|
+
|
|
1052
|
+
# See <tt>FileTest.chardev?</tt>.
|
|
1053
|
+
def chardev?() FileTest.chardev?(@path) end
|
|
1054
|
+
|
|
1055
|
+
# Tests the file is empty.
|
|
1056
|
+
#
|
|
1057
|
+
# See Dir#empty? and FileTest.empty?.
|
|
1058
|
+
def empty?
|
|
1059
|
+
if FileTest.directory?(@path)
|
|
1060
|
+
Dir.empty?(@path)
|
|
1061
|
+
else
|
|
1062
|
+
File.empty?(@path)
|
|
1063
|
+
end
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
# See <tt>FileTest.executable?</tt>.
|
|
1067
|
+
def executable?() FileTest.executable?(@path) end
|
|
1068
|
+
|
|
1069
|
+
# See <tt>FileTest.executable_real?</tt>.
|
|
1070
|
+
def executable_real?() FileTest.executable_real?(@path) end
|
|
1071
|
+
|
|
1072
|
+
# See <tt>FileTest.exist?</tt>.
|
|
1073
|
+
def exist?() FileTest.exist?(@path) end
|
|
1074
|
+
|
|
1075
|
+
# See <tt>FileTest.grpowned?</tt>.
|
|
1076
|
+
def grpowned?() FileTest.grpowned?(@path) end
|
|
1077
|
+
|
|
1078
|
+
# See <tt>FileTest.directory?</tt>.
|
|
1079
|
+
def directory?() FileTest.directory?(@path) end
|
|
1080
|
+
|
|
1081
|
+
# See <tt>FileTest.file?</tt>.
|
|
1082
|
+
def file?() FileTest.file?(@path) end
|
|
1083
|
+
|
|
1084
|
+
# See <tt>FileTest.pipe?</tt>.
|
|
1085
|
+
def pipe?() FileTest.pipe?(@path) end
|
|
1086
|
+
|
|
1087
|
+
# See <tt>FileTest.socket?</tt>.
|
|
1088
|
+
def socket?() FileTest.socket?(@path) end
|
|
1089
|
+
|
|
1090
|
+
# See <tt>FileTest.owned?</tt>.
|
|
1091
|
+
def owned?() FileTest.owned?(@path) end
|
|
1092
|
+
|
|
1093
|
+
# See <tt>FileTest.readable?</tt>.
|
|
1094
|
+
def readable?() FileTest.readable?(@path) end
|
|
1095
|
+
|
|
1096
|
+
# See <tt>FileTest.world_readable?</tt>.
|
|
1097
|
+
def world_readable?() File.world_readable?(@path) end
|
|
1098
|
+
|
|
1099
|
+
# See <tt>FileTest.readable_real?</tt>.
|
|
1100
|
+
def readable_real?() FileTest.readable_real?(@path) end
|
|
1101
|
+
|
|
1102
|
+
# See <tt>FileTest.setuid?</tt>.
|
|
1103
|
+
def setuid?() FileTest.setuid?(@path) end
|
|
1104
|
+
|
|
1105
|
+
# See <tt>FileTest.setgid?</tt>.
|
|
1106
|
+
def setgid?() FileTest.setgid?(@path) end
|
|
1107
|
+
|
|
1108
|
+
# See <tt>FileTest.size</tt>.
|
|
1109
|
+
def size() FileTest.size(@path) end
|
|
1110
|
+
|
|
1111
|
+
# See <tt>FileTest.size?</tt>.
|
|
1112
|
+
def size?() FileTest.size?(@path) end
|
|
1113
|
+
|
|
1114
|
+
# See <tt>FileTest.sticky?</tt>.
|
|
1115
|
+
def sticky?() FileTest.sticky?(@path) end
|
|
1116
|
+
|
|
1117
|
+
# See <tt>FileTest.symlink?</tt>.
|
|
1118
|
+
def symlink?() FileTest.symlink?(@path) end
|
|
1119
|
+
|
|
1120
|
+
# See <tt>FileTest.writable?</tt>.
|
|
1121
|
+
def writable?() FileTest.writable?(@path) end
|
|
1122
|
+
|
|
1123
|
+
# See <tt>FileTest.world_writable?</tt>.
|
|
1124
|
+
def world_writable?() File.world_writable?(@path) end
|
|
1125
|
+
|
|
1126
|
+
# See <tt>FileTest.writable_real?</tt>.
|
|
1127
|
+
def writable_real?() FileTest.writable_real?(@path) end
|
|
1128
|
+
|
|
1129
|
+
# See <tt>FileTest.zero?</tt>.
|
|
1130
|
+
def zero?() FileTest.zero?(@path) end
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
class Pathname # * Dir *
|
|
1135
|
+
# See <tt>Dir.glob</tt>. Returns or yields Pathname objects.
|
|
1136
|
+
def Pathname.glob(*args, **kwargs) # :yield: pathname
|
|
1137
|
+
if block_given?
|
|
1138
|
+
Dir.glob(*args, **kwargs) {|f| yield self.new(f) }
|
|
1139
|
+
else
|
|
1140
|
+
Dir.glob(*args, **kwargs).map {|f| self.new(f) }
|
|
1141
|
+
end
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
# Returns or yields Pathname objects.
|
|
1145
|
+
#
|
|
1146
|
+
# Pathname("ruby-2.4.2").glob("R*.md")
|
|
1147
|
+
# #=> [#<Pathname:ruby-2.4.2/README.md>, #<Pathname:ruby-2.4.2/README.ja.md>]
|
|
1148
|
+
#
|
|
1149
|
+
# See Dir.glob.
|
|
1150
|
+
# This method uses the +base+ keyword argument of Dir.glob.
|
|
1151
|
+
def glob(*args, **kwargs) # :yield: pathname
|
|
1152
|
+
if block_given?
|
|
1153
|
+
Dir.glob(*args, **kwargs, base: @path) {|f| yield self + f }
|
|
1154
|
+
else
|
|
1155
|
+
Dir.glob(*args, **kwargs, base: @path).map {|f| self + f }
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
# call-seq:
|
|
1160
|
+
# Pathname.getwd -> new_pathname
|
|
1161
|
+
#
|
|
1162
|
+
# Returns a new \Pathname object containing the path to the current working directory
|
|
1163
|
+
# (equivalent to <tt>Pathname.new(Dir.getwd)</tt>):
|
|
1164
|
+
#
|
|
1165
|
+
# Pathname.getwd # => #<Pathname:/home>
|
|
1166
|
+
#
|
|
1167
|
+
def Pathname.getwd() self.new(Dir.getwd) end
|
|
1168
|
+
class << self
|
|
1169
|
+
alias pwd getwd
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# Return the entries (files and subdirectories) in the directory, each as a
|
|
1173
|
+
# Pathname object.
|
|
1174
|
+
def entries() Dir.entries(@path).map {|f| self.class.new(f) } end
|
|
1175
|
+
|
|
1176
|
+
# Iterates over the entries (files and subdirectories) in the directory. It
|
|
1177
|
+
# yields a Pathname object for each entry.
|
|
1178
|
+
#
|
|
1179
|
+
# This method has existed since 1.8.1.
|
|
1180
|
+
def each_entry(&block) # :yield: pathname
|
|
1181
|
+
return to_enum(__method__) unless block_given?
|
|
1182
|
+
Dir.foreach(@path) {|f| yield self.class.new(f) }
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
# See <tt>Dir.mkdir</tt>. Create the referenced directory.
|
|
1186
|
+
def mkdir(...) Dir.mkdir(@path, ...) end
|
|
1187
|
+
|
|
1188
|
+
# See <tt>Dir.rmdir</tt>. Remove the referenced directory.
|
|
1189
|
+
def rmdir() Dir.rmdir(@path) end
|
|
1190
|
+
|
|
1191
|
+
# See <tt>Dir.open</tt>.
|
|
1192
|
+
def opendir(&block) # :yield: dir
|
|
1193
|
+
Dir.open(@path, &block)
|
|
1194
|
+
end
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
class Pathname # * mixed *
|
|
1198
|
+
# Removes a file or directory, using <tt>File.unlink</tt> or
|
|
1199
|
+
# <tt>Dir.unlink</tt> as necessary.
|
|
1200
|
+
def unlink()
|
|
1201
|
+
Dir.unlink @path
|
|
1202
|
+
rescue Errno::ENOTDIR
|
|
1203
|
+
File.unlink @path
|
|
1204
|
+
end
|
|
1205
|
+
alias delete unlink
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
class Pathname
|
|
1209
|
+
undef =~ if Kernel.method_defined?(:=~)
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
module Kernel
|
|
1213
|
+
# Creates a Pathname object.
|
|
1214
|
+
def Pathname(path) # :doc:
|
|
1215
|
+
return path if Pathname === path
|
|
1216
|
+
Pathname.new(path)
|
|
1217
|
+
end
|
|
1218
|
+
module_function :Pathname
|
|
1219
|
+
end
|