Class: Utopia::Path
- Inherits:
-
Object
- Object
- Utopia::Path
- Includes:
- Comparable
- Defined in:
- lib/utopia/path.rb,
lib/utopia/path/matcher.rb
Overview
Represents an application path as a traversal through a tree.
Each component names a node and / represents the edge between adjacent nodes. A leading empty component anchors the traversal at the root, while a trailing empty component preserves an explicit final edge and denotes a directory:
["foo", "bar"]representsfoo/bar.["", "foo", "bar"]represents/foo/bar.["", "foo", "bar", ""]represents/foo/bar/.
The structural root is represented by [""] and contains no traversed edge. It is intentionally distinct from parsing /, which preserves the explicit edge as ["", ""]. Both serialize as /, but they retain different structural representations. In particular, the structural root maps to an empty local path so it can be resolved relative to an application root.
Defined Under Namespace
Classes: Matcher
Constant Summary collapse
- SEPARATOR =
"/"
Instance Attribute Summary collapse
-
#components ⇒ Object
Returns the value of attribute components.
Class Method Summary collapse
-
.[](path) ⇒ Object
Coerce the given value into a path.
-
.create(path) ⇒ Object
Coerce a value into a path.
-
.dump(instance) ⇒ Object
Serialize a path.
-
.from_string(string) ⇒ Object
Construct a path from URL-encoded text.
-
.load(value) ⇒ Object
Load a path from its serialized form.
-
.prefix_length(a, b) ⇒ Object
Compute the number of leading components shared by two sequences.
-
.root ⇒ Object
Construct the structural root path without an explicit trailing separator.
-
.shortest_path(path, root) ⇒ Object
Compute the shortest relative path from the containing directory of
roottopath. -
.split(path) ⇒ Object
Convert a path value into an array of components.
-
.unescape(string) ⇒ Object
Decode URL-encoded path content, converting
+to whitespace and percent-encoded bytes to their corresponding characters.
Instance Method Summary collapse
-
#+(other) ⇒ Object
Append path components and return the resulting path.
-
#-(other) ⇒ Object
Computes the difference of the path.
-
#<=>(other) ⇒ Object
Compare this object with another object.
-
#==(other) ⇒ Object
Compare this object with another object.
-
#[](index) ⇒ Object
Fetch one or more path components, excluding root and directory markers from indexing.
-
#[]=(index, value) ⇒ Object
Replace one or more path components using the same root- and directory-marker-aware indexing as #[].
-
#absolute? ⇒ Boolean
Check whether this path is absolute.
-
#ascend(&block) ⇒ Object
Enumerate paths from this path up to its first component.
- #basename ⇒ Object
-
#delete_at(index) ⇒ Object
Delete a path component, excluding root and directory markers from indexing.
-
#descend(&block) ⇒ Object
Enumerate paths from the first component down to this path.
-
#directory? ⇒ Boolean
Check whether this path denotes a directory.
-
#dirname(count = 1) ⇒ Object
Remove trailing path components.
-
#dup ⇒ Object
Copy this path and its component array.
-
#empty? ⇒ Boolean
Check whether this path has no components.
-
#eql?(other) ⇒ Boolean
Check whether this object is equivalent to another object.
-
#expand(root) ⇒ Object
Resolve this path relative to a root path.
- #extension ⇒ Object
-
#file? ⇒ Boolean
(also: #last?)
Check whether this path denotes a file.
-
#first ⇒ Object
Return the first path component, excluding the root marker.
-
#freeze ⇒ Object
Freeze this object and its internal state.
-
#hash ⇒ Object
Compute the hash value for this object.
-
#include?(*arguments) ⇒ Boolean
Check whether this collection includes the given value.
-
#initialize(components = []) ⇒ Path
constructor
Initialize a path from its individual components.
- #join(other) ⇒ Object
-
#last ⇒ Object
Return the last path component, excluding the root marker.
-
#local_path(separator = File::SEPARATOR) ⇒ Object
Format this path using a local filesystem separator.
-
#pop ⇒ Object
Remove the last path component without converting the root path to a relative path.
-
#relative? ⇒ Boolean
Check whether this path is relative.
-
#replace(other_path) ⇒ Object
Replace this path's components with a copy of another path's components.
-
#shortest_path(root) ⇒ Object
Compute the shortest relative path from the containing directory of
rootto this path. -
#simplify ⇒ Object
Normalize current-directory, parent-directory, and repeated-separator components.
-
#split(at) ⇒ Object
Split this path around a component or component index.
-
#start_with?(other) ⇒ Boolean
Check whether this path starts with the given path.
-
#to_a ⇒ Object
Convert this path to an array of components.
-
#to_absolute ⇒ Object
Convert this path to an absolute path.
-
#to_directory ⇒ Object
Convert this path to a directory path.
-
#to_str ⇒ Object
(also: #to_s)
Convert this object to a string.
-
#to_url_path ⇒ Object
Encode this application path as a URL path.
-
#with_prefix(*arguments) ⇒ Object
Prepend a path to this path.
Constructor Details
#initialize(components = []) ⇒ Path
Initialize a path from its individual components.
25 26 27 |
# File 'lib/utopia/path.rb', line 25 def initialize(components = []) @components = components end |
Instance Attribute Details
#components ⇒ Object
Returns the value of attribute components.
29 30 31 |
# File 'lib/utopia/path.rb', line 29 def components @components end |
Class Method Details
.[](path) ⇒ Object
Coerce the given value into a path.
97 98 99 |
# File 'lib/utopia/path.rb', line 97 def self.[] path self.create(path) end |
.create(path) ⇒ Object
Coerce a value into a path.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/utopia/path.rb', line 141 def self.create(path) case path when Path return path when Array return self.new(path) when String return self.new(unescape(path).split(SEPARATOR, -1)) when nil return nil else return self.new([path]) end end |
.dump(instance) ⇒ Object
Serialize a path.
134 135 136 |
# File 'lib/utopia/path.rb', line 134 def self.dump(instance) instance.to_s if instance end |
.from_string(string) ⇒ Object
Construct a path from URL-encoded text. This is an optimized direct entry point used by controller invocations.
120 121 122 |
# File 'lib/utopia/path.rb', line 120 def self.from_string(string) self.new(unescape(string).split(SEPARATOR, -1)) end |
.load(value) ⇒ Object
Load a path from its serialized form.
127 128 129 |
# File 'lib/utopia/path.rb', line 127 def self.load(value) from_string(value) if value end |
.prefix_length(a, b) ⇒ Object
Compute the number of leading components shared by two sequences.
57 58 59 |
# File 'lib/utopia/path.rb', line 57 def self.prefix_length(a, b) [a.size, b.size].min.times{|i| return i if a[i] != b[i]} end |
.root ⇒ Object
Construct the structural root path without an explicit trailing separator.
49 50 51 |
# File 'lib/utopia/path.rb', line 49 def self.root self.new([""]) end |
.shortest_path(path, root) ⇒ Object
Compute the shortest relative path from the containing directory of root to path.
65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/utopia/path.rb', line 65 def self.shortest_path(path, root) path = self.create(path) root = self.create(root).dirname # Find the common prefix: i = prefix_length(path.components, root.components) || 0 # The difference between the root path and the required path, taking into account the common prefix: up = root.components.size - i return self.create([".."] * up + path.components[i..-1]) end |
.split(path) ⇒ Object
Convert a path value into an array of components.
104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/utopia/path.rb', line 104 def self.split(path) case path when Path return path.to_a when Array return path when String create(path).to_a else [path] end end |
.unescape(string) ⇒ Object
Decode URL-encoded path content, converting + to whitespace and percent-encoded bytes to their corresponding characters.
88 89 90 91 92 |
# File 'lib/utopia/path.rb', line 88 def self.unescape(string) string.tr("+", " ").gsub(/((?:%[0-9a-fA-F]{2})+)/n) do [$1.delete("%")].pack("H*") end end |
Instance Method Details
#+(other) ⇒ Object
Append path components and return the resulting path.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/utopia/path.rb', line 267 def +(other) if other.kind_of? Path if other.absolute? return other else return join(other.components) end elsif other.kind_of? Array return join(other) elsif other.kind_of? String return join(other.split(SEPARATOR, -1)) else return join([other.to_s]) end end |
#-(other) ⇒ Object
Computes the difference of the path. /a/b/c - /a/b -> c a/b/c - a/b -> c
295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/utopia/path.rb', line 295 def -(other) i = 0 while i < other.components.size break if @components[i] != other.components[i] i += 1 end return self.class.new(@components[i,@components.size]) end |
#<=>(other) ⇒ Object
Compare this object with another object.
453 454 455 |
# File 'lib/utopia/path.rb', line 453 def <=> other @components <=> other.components end |
#==(other) ⇒ Object
Compare this object with another object.
473 474 475 476 477 478 479 480 481 |
# File 'lib/utopia/path.rb', line 473 def == other return false unless other case other when String then self.to_s == other when Array then self.to_a == other else other.is_a?(self.class) && @components == other.components end end |
#[](index) ⇒ Object
Fetch one or more path components, excluding root and directory markers from indexing.
497 498 499 |
# File 'lib/utopia/path.rb', line 497 def [] index return @components[component_offset(index)] end |
#[]=(index, value) ⇒ Object
Replace one or more path components using the same root- and directory-marker-aware indexing as #[].
505 506 507 |
# File 'lib/utopia/path.rb', line 505 def []= index, value return @components[component_offset(index)] = value end |
#absolute? ⇒ Boolean
Check whether this path is absolute.
200 201 202 |
# File 'lib/utopia/path.rb', line 200 def absolute? @components.first == "" end |
#ascend(&block) ⇒ Object
Enumerate paths from this path up to its first component.
417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/utopia/path.rb', line 417 def ascend(&block) return to_enum(:ascend) unless block_given? components = self.components.dup while components.any? yield self.class.new(components.dup) components.pop end end |
#basename ⇒ Object
370 371 372 373 374 |
# File 'lib/utopia/path.rb', line 370 def basename basename, _ = @components.last.split(".", 2) return basename || "" end |
#delete_at(index) ⇒ Object
Delete a path component, excluding root and directory markers from indexing.
512 513 514 |
# File 'lib/utopia/path.rb', line 512 def delete_at(index) @components.delete_at(component_offset(index)) end |
#descend(&block) ⇒ Object
Enumerate paths from the first component down to this path.
402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/utopia/path.rb', line 402 def descend(&block) return to_enum(:descend) unless block_given? components = [] @components.each do |component| components << component yield self.class.new(components.dup) end end |
#directory? ⇒ Boolean
Check whether this path denotes a directory.
172 173 174 |
# File 'lib/utopia/path.rb', line 172 def directory? return @components.last == "" end |
#dirname(count = 1) ⇒ Object
Remove trailing path components.
386 387 388 389 390 |
# File 'lib/utopia/path.rb', line 386 def dirname(count = 1) path = self.class.new(@components[0...-count]) return absolute? ? path.to_absolute : path end |
#dup ⇒ Object
Copy this path and its component array.
446 447 448 |
# File 'lib/utopia/path.rb', line 446 def dup return Path.new(components.dup) end |
#empty? ⇒ Boolean
Check whether this path has no components.
43 44 45 |
# File 'lib/utopia/path.rb', line 43 def empty? @components.empty? end |
#eql?(other) ⇒ Boolean
Check whether this object is equivalent to another object.
460 461 462 |
# File 'lib/utopia/path.rb', line 460 def eql? other self.class.eql?(other.class) and @components.eql?(other.components) end |
#expand(root) ⇒ Object
Resolve this path relative to a root path.
260 261 262 |
# File 'lib/utopia/path.rb', line 260 def (root) root + self end |
#extension ⇒ Object
377 378 379 380 381 |
# File 'lib/utopia/path.rb', line 377 def extension _, extension = @components.last.split(".", 2) return extension end |
#file? ⇒ Boolean Also known as: last?
Check whether this path denotes a file.
178 179 180 |
# File 'lib/utopia/path.rb', line 178 def file? return @components.last != "" end |
#first ⇒ Object
Return the first path component, excluding the root marker.
342 343 344 345 346 347 348 |
# File 'lib/utopia/path.rb', line 342 def first if absolute? @components[1] else @components[0] end end |
#freeze ⇒ Object
Freeze this object and its internal state.
33 34 35 36 37 38 39 |
# File 'lib/utopia/path.rb', line 33 def freeze return self if frozen? @components.freeze super end |
#hash ⇒ Object
Compute the hash value for this object.
466 467 468 |
# File 'lib/utopia/path.rb', line 466 def hash @components.hash end |
#include?(*arguments) ⇒ Boolean
Check whether this collection includes the given value.
166 167 168 |
# File 'lib/utopia/path.rb', line 166 def include?(*arguments) @components.include?(*arguments) end |
#join(other) ⇒ Object
248 249 250 251 252 253 254 255 |
# File 'lib/utopia/path.rb', line 248 def join(other) # Check whether other is an absolute path: if other.first == "" self.class.new(other) else self.class.new(@components + other).simplify end end |
#last ⇒ Object
Return the last path component, excluding the root marker.
352 353 354 355 356 |
# File 'lib/utopia/path.rb', line 352 def last if @components != [""] @components.last end end |
#local_path(separator = File::SEPARATOR) ⇒ Object
Format this path using a local filesystem separator.
395 396 397 |
# File 'lib/utopia/path.rb', line 395 def local_path(separator = File::SEPARATOR) @components.join(separator) end |
#pop ⇒ Object
Remove the last path component without converting the root path to a relative path.
362 363 364 365 366 367 |
# File 'lib/utopia/path.rb', line 362 def pop # We don't want to convert an absolute path to a relative path. if @components != [""] @components.pop end end |
#relative? ⇒ Boolean
Check whether this path is relative.
194 195 196 |
# File 'lib/utopia/path.rb', line 194 def relative? @components.first != "" end |
#replace(other_path) ⇒ Object
Replace this path's components with a copy of another path's components.
159 160 161 |
# File 'lib/utopia/path.rb', line 159 def replace(other_path) @components = other_path.components.dup end |
#shortest_path(root) ⇒ Object
Compute the shortest relative path from the containing directory of root to this path.
81 82 83 |
# File 'lib/utopia/path.rb', line 81 def shortest_path(root) self.class.shortest_path(self, root) end |
#simplify ⇒ Object
Normalize current-directory, parent-directory, and repeated-separator components.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/utopia/path.rb', line 309 def simplify components = [] index = 0 if @components[0] == "" components << "" index += 1 end while index < @components.size bit = @components[index] if bit == "." # No-op (ignore current directory) elsif bit == "" && index != @components.size - 1 # No-op (ignore multiple slashes) elsif bit == ".." && components.last && components.last != ".." if components.last != "" # We can go up one level: components.pop end else components << bit end index += 1 end return self.class.new(components) end |
#split(at) ⇒ Object
Split this path around a component or component index.
432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/utopia/path.rb', line 432 def split(at) if at.kind_of?(String) at = @components.index(at) end if at return [self.class.new(@components[0...at]), self.class.new(@components[at+1..-1])] else return nil end end |
#start_with?(other) ⇒ Boolean
Check whether this path starts with the given path.
486 487 488 489 490 491 492 |
# File 'lib/utopia/path.rb', line 486 def start_with? other other.components.each_with_index do |part, index| return false if @components[index] != part end return true end |
#to_a ⇒ Object
Convert this path to an array of components.
242 243 244 |
# File 'lib/utopia/path.rb', line 242 def to_a @components end |
#to_absolute ⇒ Object
Convert this path to an absolute path.
206 207 208 209 210 211 212 |
# File 'lib/utopia/path.rb', line 206 def to_absolute if absolute? return self else return self.class.new([""] + @components) end end |
#to_directory ⇒ Object
Convert this path to a directory path.
184 185 186 187 188 189 190 |
# File 'lib/utopia/path.rb', line 184 def to_directory if directory? return self else return self.class.new(@components + [""]) end end |
#to_str ⇒ Object Also known as: to_s
Convert this object to a string.
216 217 218 219 220 221 222 |
# File 'lib/utopia/path.rb', line 216 def to_str if @components == [""] SEPARATOR else @components.join(SEPARATOR) end end |
#to_url_path ⇒ Object
Encode this application path as a URL path.
228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/utopia/path.rb', line 228 def to_url_path # Preserve Utopia's compact representation of the absolute root: if @components == [""] return Protocol::URL::Path[SEPARATOR] end return Protocol::URL::Path.for( @components, encoding: Protocol::URL::Encoding::System, ) end |
#with_prefix(*arguments) ⇒ Object
Prepend a path to this path.
286 287 288 |
# File 'lib/utopia/path.rb', line 286 def with_prefix(*arguments) self.class.create(*arguments) + self end |