path 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.md +183 -0
- data/lib/epath.rb +2 -0
- data/lib/path.rb +159 -0
- data/lib/path/compatibility.rb +60 -0
- data/lib/path/dir.rb +147 -0
- data/lib/path/file.rb +132 -0
- data/lib/path/file_predicates.rb +151 -0
- data/lib/path/fileutils.rb +109 -0
- data/lib/path/find.rb +23 -0
- data/lib/path/identity.rb +163 -0
- data/lib/path/implementation.rb +294 -0
- data/lib/path/io.rb +104 -0
- data/lib/path/load.rb +29 -0
- data/lib/path/parts.rb +136 -0
- data/lib/path/predicates.rb +33 -0
- data/lib/path/require_tree.rb +42 -0
- data/lib/path/version.rb +5 -0
- data/path.gemspec +14 -0
- metadata +80 -0
@@ -0,0 +1,294 @@
|
|
1
|
+
# Path's low-level implementation based on Pathname
|
2
|
+
|
3
|
+
class Path
|
4
|
+
# @private
|
5
|
+
SAME_PATHS = if File::FNM_SYSCASE.nonzero?
|
6
|
+
lambda { |a,b| a.casecmp(b).zero? }
|
7
|
+
else
|
8
|
+
lambda { |a,b| a == b }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns a cleaned version of +self+ with consecutive slashes and useless dots removed.
|
12
|
+
# The filesystem is not accessed.
|
13
|
+
#
|
14
|
+
# If +consider_symlink+ is +true+, then a more conservative algorithm is used
|
15
|
+
# to avoid breaking symbolic linkages. This may retain more +..+
|
16
|
+
# entries than absolutely necessary, but without accessing the filesystem,
|
17
|
+
# this can't be avoided. See {#realpath}.
|
18
|
+
def clean(consider_symlink = false)
|
19
|
+
consider_symlink ? cleanpath_conservative : cleanpath_aggressive
|
20
|
+
end
|
21
|
+
alias :cleanpath :clean
|
22
|
+
|
23
|
+
# #parent returns the parent directory.
|
24
|
+
# This can be chained.
|
25
|
+
def parent
|
26
|
+
self / '..'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Path#/ appends a path fragment to this one to produce a new Path.
|
30
|
+
#
|
31
|
+
# p = Path.new("/usr") # => #<Path /usr>
|
32
|
+
# p / "bin/ruby" # => #<Path /usr/bin/ruby>
|
33
|
+
# p / "/etc/passwd" # => #<Path /etc/passwd>
|
34
|
+
#
|
35
|
+
# This method doesn't access the file system, it is pure string manipulation.
|
36
|
+
def /(other)
|
37
|
+
Path.new(plus(@path, other.to_s))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Configures the behavior of {Path#+}. The default is +:warning+.
|
41
|
+
#
|
42
|
+
# Path + :defined # aliased to Path#/
|
43
|
+
# Path + :warning # calls Path#/ but warns
|
44
|
+
# Path + :error # not defined
|
45
|
+
# Path + :string # like String#+. Warns if $VERBOSE (-w)
|
46
|
+
#
|
47
|
+
# @param config [:defined, :warning, :error, :string] the configuration value
|
48
|
+
def Path.+(config)
|
49
|
+
unless [:defined, :warning, :error, :string].include? config
|
50
|
+
raise ArgumentError, "Invalid configuration: #{config.inspect}"
|
51
|
+
end
|
52
|
+
if @plus_configured
|
53
|
+
raise "Path.+ has already been called: #{@plus_configured}"
|
54
|
+
end
|
55
|
+
remove_method :+ if method_defined? :+
|
56
|
+
case config
|
57
|
+
when :defined
|
58
|
+
alias :+ :/
|
59
|
+
when :warning
|
60
|
+
def +(other)
|
61
|
+
warn 'Warning: use of deprecated Path#+ as Path#/: ' <<
|
62
|
+
"#{inspect} + #{other.inspect}\n#{caller.first}"
|
63
|
+
self / other
|
64
|
+
end
|
65
|
+
when :error
|
66
|
+
# nothing to do, the method has been removed
|
67
|
+
when :string
|
68
|
+
def +(other)
|
69
|
+
warn 'Warning: use of deprecated Path#+ as String#+: ' <<
|
70
|
+
"#{inspect} + #{other.inspect}\n#{caller.first}" if $VERBOSE
|
71
|
+
Path(to_s + other.to_s)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@plus_configured = caller.first
|
75
|
+
end
|
76
|
+
|
77
|
+
@plus_configured = nil # Initialization
|
78
|
+
Path + :warning
|
79
|
+
@plus_configured = nil # Let the user overrides this default configuration
|
80
|
+
|
81
|
+
# @!method +(other)
|
82
|
+
# The behavior depends on the configuration with Path.{Path.+}.
|
83
|
+
# It might behave as {Path#/}, String#+, give warnings,
|
84
|
+
# or not be defined at all.
|
85
|
+
|
86
|
+
# Joins paths.
|
87
|
+
#
|
88
|
+
# path0.join(path1, ..., pathN)
|
89
|
+
# # is the same as
|
90
|
+
# path0 / path1 / ... / pathN
|
91
|
+
def join(*paths)
|
92
|
+
result = nil
|
93
|
+
paths.reverse_each { |path|
|
94
|
+
result = Path.new(path) / result
|
95
|
+
return result if result.absolute?
|
96
|
+
}
|
97
|
+
self / result
|
98
|
+
end
|
99
|
+
|
100
|
+
# #relative_path_from returns a relative path from the argument to the
|
101
|
+
# receiver. They must be both relative or both absolute.
|
102
|
+
#
|
103
|
+
# #relative_path_from doesn't access the filesystem. It assumes no symlinks.
|
104
|
+
#
|
105
|
+
# @raise [ArgumentError] if it cannot find a relative path:
|
106
|
+
# Either the base is relative and contains '..' (in that case you can expand
|
107
|
+
# both paths) or the paths are absolutes and on different drives (Windows).
|
108
|
+
def relative_path_from(base_directory)
|
109
|
+
dest = clean.path
|
110
|
+
base = Path.new(base_directory).clean.path
|
111
|
+
dest_prefix, dest_names = split_names(dest)
|
112
|
+
base_prefix, base_names = split_names(base)
|
113
|
+
|
114
|
+
unless SAME_PATHS[dest_prefix, base_prefix]
|
115
|
+
raise ArgumentError, "different prefix: #{self.inspect} and #{base_directory.inspect}"
|
116
|
+
end
|
117
|
+
while d = dest_names.first and b = base_names.first and SAME_PATHS[d, b]
|
118
|
+
dest_names.shift
|
119
|
+
base_names.shift
|
120
|
+
end
|
121
|
+
raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" if base_names.include? '..'
|
122
|
+
# the number of names left in base is the ones we have to climb
|
123
|
+
names = base_names.fill('..').concat(dest_names)
|
124
|
+
return Path.new('.') if names.empty?
|
125
|
+
Path.new(*names)
|
126
|
+
end
|
127
|
+
alias :relative_to :relative_path_from
|
128
|
+
alias :% :relative_path_from
|
129
|
+
|
130
|
+
# @private
|
131
|
+
module Helpers
|
132
|
+
private
|
133
|
+
|
134
|
+
# remove the leading . of +ext+ if present.
|
135
|
+
def pure_ext(ext)
|
136
|
+
ext = ext.to_s and ext.start_with?('.') ? ext[1..-1] : ext
|
137
|
+
end
|
138
|
+
|
139
|
+
# add a leading . to +ext+ if missing. Returns '' if +ext+ is empty.
|
140
|
+
def dotted_ext(ext)
|
141
|
+
ext = ext.to_s and (ext.empty? or ext.start_with?('.')) ? ext : ".#{ext}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
include Helpers
|
146
|
+
extend Helpers
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def init
|
151
|
+
@path = validate(@path)
|
152
|
+
|
153
|
+
taint if @path.tainted?
|
154
|
+
@path.freeze
|
155
|
+
freeze
|
156
|
+
end
|
157
|
+
|
158
|
+
def validate(path)
|
159
|
+
raise ArgumentError, "path contains a null byte: #{path.inspect}" if path.include? "\0"
|
160
|
+
path.gsub!(File::ALT_SEPARATOR, '/') if File::ALT_SEPARATOR
|
161
|
+
path = File.expand_path(path) if path.start_with? '~'
|
162
|
+
path
|
163
|
+
end
|
164
|
+
|
165
|
+
# chop_basename(path) -> [pre-basename, basename] or nil
|
166
|
+
def chop_basename(path)
|
167
|
+
base = File.basename(path)
|
168
|
+
if base.empty? or base == '/'
|
169
|
+
return nil
|
170
|
+
else
|
171
|
+
return path[0, path.rindex(base)], base
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def is_absolute?(path)
|
176
|
+
path.start_with?('/') or (path =~ /\A[a-zA-Z]:\// and is_root?($&))
|
177
|
+
end
|
178
|
+
|
179
|
+
def is_root?(path)
|
180
|
+
chop_basename(path) == nil and path.include?('/')
|
181
|
+
end
|
182
|
+
|
183
|
+
# split_names(path) -> prefix, [name, ...]
|
184
|
+
def split_names(path)
|
185
|
+
names = []
|
186
|
+
while r = chop_basename(path)
|
187
|
+
path, basename = r
|
188
|
+
names.unshift basename if basename != '.'
|
189
|
+
end
|
190
|
+
return path, names
|
191
|
+
end
|
192
|
+
|
193
|
+
def prepend_prefix(prefix, relnames)
|
194
|
+
relpath = File.join(*relnames)
|
195
|
+
if relpath.empty?
|
196
|
+
File.dirname(prefix)
|
197
|
+
elsif prefix.include? '/'
|
198
|
+
# safe because File.dirname returns a new String
|
199
|
+
add_trailing_separator(File.dirname(prefix)) << relpath
|
200
|
+
else
|
201
|
+
prefix + relpath
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def has_trailing_separator?(path)
|
206
|
+
!is_root?(path) and path.end_with?('/')
|
207
|
+
end
|
208
|
+
|
209
|
+
def add_trailing_separator(path) # mutates path
|
210
|
+
path << '/' unless path.end_with? '/'
|
211
|
+
path
|
212
|
+
end
|
213
|
+
|
214
|
+
def del_trailing_separator(path)
|
215
|
+
if r = chop_basename(path)
|
216
|
+
pre, basename = r
|
217
|
+
pre + basename
|
218
|
+
elsif %r{/+\z} =~ path
|
219
|
+
$` + File.dirname(path)[%r{/*\z}]
|
220
|
+
else
|
221
|
+
path
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# remove '..' segments since root's parent is root
|
226
|
+
def remove_root_parents(prefix, names)
|
227
|
+
names.shift while names.first == '..' if is_root?(prefix)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Clean the path simply by resolving and removing excess "." and ".." entries.
|
231
|
+
# Nothing more, nothing less.
|
232
|
+
def cleanpath_aggressive
|
233
|
+
pre = @path
|
234
|
+
names = []
|
235
|
+
while r = chop_basename(pre)
|
236
|
+
pre, base = r
|
237
|
+
if base == '.'
|
238
|
+
# do nothing, it can be ignored
|
239
|
+
elsif names.first == '..' and base != '..'
|
240
|
+
# base can be ignored as we go back to its parent
|
241
|
+
names.shift
|
242
|
+
else
|
243
|
+
names.unshift base
|
244
|
+
end
|
245
|
+
end
|
246
|
+
remove_root_parents(pre, names)
|
247
|
+
Path.new(prepend_prefix(pre, names))
|
248
|
+
end
|
249
|
+
|
250
|
+
def cleanpath_conservative
|
251
|
+
path = @path
|
252
|
+
pre, names = split_names(path)
|
253
|
+
remove_root_parents(pre, names)
|
254
|
+
if names.empty?
|
255
|
+
Path.new(File.dirname(pre))
|
256
|
+
else
|
257
|
+
names << '.' if names.last != '..' and File.basename(path) == '.'
|
258
|
+
result = prepend_prefix(pre, names)
|
259
|
+
if names.last != '.' and names.last != '..' and has_trailing_separator?(path)
|
260
|
+
Path.new(add_trailing_separator(result))
|
261
|
+
else
|
262
|
+
Path.new(result)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def plus(prefix, rel)
|
268
|
+
return rel if is_absolute?(rel)
|
269
|
+
_, names = split_names(rel)
|
270
|
+
|
271
|
+
loop do
|
272
|
+
# break if that was the last segment
|
273
|
+
break unless r = chop_basename(prefix)
|
274
|
+
prefix, name = r
|
275
|
+
next if name == '.'
|
276
|
+
|
277
|
+
# break if we can't resolve anymore
|
278
|
+
if name == '..' or names.first != '..'
|
279
|
+
prefix << name
|
280
|
+
break
|
281
|
+
end
|
282
|
+
names.shift
|
283
|
+
end
|
284
|
+
|
285
|
+
remove_root_parents(prefix, names)
|
286
|
+
has_prefix = chop_basename(prefix)
|
287
|
+
if names.empty?
|
288
|
+
has_prefix ? prefix : File.dirname(prefix)
|
289
|
+
else
|
290
|
+
suffix = File.join(*names)
|
291
|
+
has_prefix ? File.join(prefix, suffix) : prefix + suffix
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
data/lib/path/io.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group IO
|
3
|
+
|
4
|
+
# Opens the file for reading or writing. See +File.open+.
|
5
|
+
# @yieldparam [File] file
|
6
|
+
def open(*args, &block)
|
7
|
+
File.open(@path, *args, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Iterates over the lines in the file. See +IO.foreach+.
|
11
|
+
# @yieldparam [String] line
|
12
|
+
def each_line(*args, &block)
|
13
|
+
IO.foreach(@path, *args, &block)
|
14
|
+
end
|
15
|
+
alias :lines :each_line
|
16
|
+
|
17
|
+
# Returns all data from the file, or the first +bytes+ bytes if specified.
|
18
|
+
# See +IO.read+.
|
19
|
+
def read(*args)
|
20
|
+
IO.read(@path, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
if IO.respond_to? :binread
|
24
|
+
# Returns all the bytes from the file, or the first +N+ if specified.
|
25
|
+
# See +IO.binread+.
|
26
|
+
def binread(*args)
|
27
|
+
IO.binread(@path, *args)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def binread(*args)
|
31
|
+
open('rb', &:read)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns all the lines from the file. See +IO.readlines+.
|
36
|
+
def readlines(*args)
|
37
|
+
IO.readlines(@path, *args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# See +IO.sysopen+.
|
41
|
+
def sysopen(*args)
|
42
|
+
IO.sysopen(@path, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
if IO.respond_to? :write
|
46
|
+
# Writes +contents+ to +self+. See +IO.write+ or +IO#write+.
|
47
|
+
def write(contents, *open_args)
|
48
|
+
IO.write(@path, contents, *open_args)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
def write(contents, *open_args)
|
52
|
+
open('w', *open_args) { |f| f.write(contents) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if IO.respond_to? :binwrite
|
57
|
+
# Writes +contents+ to +self+. See +IO.binwrite+.
|
58
|
+
def binwrite(contents, *open_args)
|
59
|
+
IO.binwrite(@path, contents, *open_args)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
def binwrite(contents, *open_args)
|
63
|
+
open('wb', *open_args) { |f| f.write(contents) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if IO.respond_to? :write and !RUBY_DESCRIPTION.start_with?('jruby')
|
68
|
+
# Appends +contents+ to +self+. See +IO.write+ or +IO#write+.
|
69
|
+
def append(contents, open_args = {})
|
70
|
+
open_args[:mode] = 'a'
|
71
|
+
IO.write(@path, contents, open_args)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
def append(contents, *open_args)
|
75
|
+
open('a', *open_args) { |f| f.write(contents) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Rewrites contents of +self+.
|
80
|
+
#
|
81
|
+
# Path('file').rewrite { |contents| contents.reverse }
|
82
|
+
#
|
83
|
+
# @yieldparam [String] contents
|
84
|
+
# @yieldreturn [String] contents to write
|
85
|
+
def rewrite
|
86
|
+
write yield read
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the first +bytes+ bytes of the file.
|
90
|
+
# If the file size is smaller than +bytes+, return the whole contents.
|
91
|
+
def head(bytes)
|
92
|
+
read(bytes)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the last +bytes+ bytes of the file.
|
96
|
+
# If the file size is smaller than +bytes+, return the whole contents.
|
97
|
+
def tail(bytes)
|
98
|
+
return read if size < bytes
|
99
|
+
open { |f|
|
100
|
+
f.seek(-bytes, IO::SEEK_END)
|
101
|
+
f.read
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
data/lib/path/load.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group Loading
|
3
|
+
|
4
|
+
# The list of loaders. See {Path.register_loader}.
|
5
|
+
LOADERS = {}
|
6
|
+
|
7
|
+
# Registers a new loader (a block which will be called with the Path to load)
|
8
|
+
# for the given extensions (either with the leading dot or not)
|
9
|
+
#
|
10
|
+
# Path.register_loader('.marshal') { |file| Marshal.load file.read }
|
11
|
+
#
|
12
|
+
# @yieldparam [Path] path
|
13
|
+
def self.register_loader(*extensions, &loader)
|
14
|
+
extensions.each { |ext|
|
15
|
+
LOADERS[pure_ext(ext)] = loader
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Path#load helps loading data from various files.
|
20
|
+
# JSON and YAML loaders are provided by default.
|
21
|
+
# See {Path.register_loader}.
|
22
|
+
def load
|
23
|
+
if LOADERS.key? ext
|
24
|
+
LOADERS[ext].call(self)
|
25
|
+
else
|
26
|
+
raise "Unable to load #{self} (unrecognized extension)"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/path/parts.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group Path parts
|
3
|
+
|
4
|
+
# Returns the last component of the path. See +File.basename+.
|
5
|
+
def basename(*args)
|
6
|
+
Path.new(File.basename(@path, *args))
|
7
|
+
end
|
8
|
+
|
9
|
+
# basename(extname)
|
10
|
+
def base
|
11
|
+
basename(extname)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns all but the last component of the path.
|
15
|
+
#
|
16
|
+
# Don't chain this when the path is relative:
|
17
|
+
# Path('.').dir # => #<Path .>
|
18
|
+
# Use #parent instead.
|
19
|
+
# See +File.dirname+.
|
20
|
+
def dirname
|
21
|
+
Path.new(File.dirname(@path))
|
22
|
+
end
|
23
|
+
alias :dir :dirname
|
24
|
+
|
25
|
+
# Returns the extension, with a leading dot. See +File.extname+.
|
26
|
+
def extname
|
27
|
+
File.extname(@path)
|
28
|
+
end
|
29
|
+
|
30
|
+
# {#extname} without leading dot.
|
31
|
+
def ext
|
32
|
+
ext = extname
|
33
|
+
ext.empty? ? ext : ext[1..-1]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the #dirname and the #basename in an Array. See +File.split+.
|
37
|
+
def split
|
38
|
+
File.split(@path).map(&Path)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds +ext+ as an extension to +path+.
|
42
|
+
# Handle both extensions with or without leading dot.
|
43
|
+
# No-op if +ext+ is +empty?+.
|
44
|
+
#
|
45
|
+
# Path('file').add_extension('txt') # => #<Path file.txt>
|
46
|
+
def add_extension(ext)
|
47
|
+
return self if ext.to_s.empty?
|
48
|
+
Path.new @path+dotted_ext(ext)
|
49
|
+
end
|
50
|
+
alias :add_ext :add_extension
|
51
|
+
|
52
|
+
# Removes the last extension of +path+.
|
53
|
+
#
|
54
|
+
# Path('script.rb').without_extension # => #<Path script>
|
55
|
+
# Path('archive.tar.gz').without_extension # => #<Path archive.tar>
|
56
|
+
def without_extension
|
57
|
+
Path.new @path[0..-extname.size-1]
|
58
|
+
end
|
59
|
+
alias :rm_ext :without_extension
|
60
|
+
|
61
|
+
# Replaces the last extension of +path+ with +ext+.
|
62
|
+
# Handle both extensions with or without leading dot.
|
63
|
+
# Removes last extension if +ext+ is +empty?+.
|
64
|
+
#
|
65
|
+
# Path('main.c++').replace_extension('cc') # => #<Path main.cc>
|
66
|
+
def replace_extension(ext)
|
67
|
+
return without_extension if ext.to_s.empty?
|
68
|
+
Path.new(@path[0..-extname.size-1] << dotted_ext(ext))
|
69
|
+
end
|
70
|
+
alias :sub_ext :replace_extension
|
71
|
+
|
72
|
+
# Iterates over each component of the path.
|
73
|
+
#
|
74
|
+
# Path.new("/usr/bin/ruby").each_filename { |filename| ... }
|
75
|
+
# # yields "usr", "bin", and "ruby".
|
76
|
+
#
|
77
|
+
# @yieldparam [String] filename
|
78
|
+
def each_filename
|
79
|
+
return to_enum(__method__) unless block_given?
|
80
|
+
_, names = split_names(@path)
|
81
|
+
names.each { |filename| yield filename }
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Iterates over each element in the given path in descending order.
|
86
|
+
#
|
87
|
+
# Path.new('/path/to/some/file.rb').descend { |v| p v }
|
88
|
+
# #<Path />
|
89
|
+
# #<Path /path>
|
90
|
+
# #<Path /path/to>
|
91
|
+
# #<Path /path/to/some>
|
92
|
+
# #<Path /path/to/some/file.rb>
|
93
|
+
#
|
94
|
+
# Path.new('path/to/some/file.rb').descend { |v| p v }
|
95
|
+
# #<Path path>
|
96
|
+
# #<Path path/to>
|
97
|
+
# #<Path path/to/some>
|
98
|
+
# #<Path path/to/some/file.rb>
|
99
|
+
#
|
100
|
+
# It doesn't access actual filesystem.
|
101
|
+
# @yieldparam [Path] path
|
102
|
+
def descend
|
103
|
+
return to_enum(:descend) unless block_given?
|
104
|
+
ascend.reverse_each { |v| yield v }
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
# Iterates over each element in the given path in ascending order.
|
109
|
+
#
|
110
|
+
# Path.new('/path/to/some/file.rb').ascend { |v| p v }
|
111
|
+
# #<Path /path/to/some/file.rb>
|
112
|
+
# #<Path /path/to/some>
|
113
|
+
# #<Path /path/to>
|
114
|
+
# #<Path /path>
|
115
|
+
# #<Path />
|
116
|
+
#
|
117
|
+
# Path.new('path/to/some/file.rb').ascend { |v| p v }
|
118
|
+
# #<Path path/to/some/file.rb>
|
119
|
+
# #<Path path/to/some>
|
120
|
+
# #<Path path/to>
|
121
|
+
# #<Path path>
|
122
|
+
#
|
123
|
+
# It doesn't access actual filesystem.
|
124
|
+
# @yieldparam [Path] path
|
125
|
+
def ascend
|
126
|
+
return to_enum(:ascend) unless block_given?
|
127
|
+
path = @path
|
128
|
+
yield self
|
129
|
+
while r = chop_basename(path)
|
130
|
+
path, = r
|
131
|
+
break if path.empty?
|
132
|
+
yield Path.new(del_trailing_separator(path))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
alias :ancestors :ascend
|
136
|
+
end
|