path 1.3.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/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
|