Class: Utopia::Path
- Inherits:
-
Object
- Object
- Utopia::Path
- Includes:
- Comparable
- Defined in:
- lib/utopia/path.rb,
lib/utopia/path/matcher.rb
Overview
Represents a path as an array of path components. Useful for efficient URL manipulation.
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 root path.
-
.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_relative! ⇒ Object
Remove the first component when this path is relative.
-
#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.
17 18 19 |
# File 'lib/utopia/path.rb', line 17 def initialize(components = []) @components = components end |
Instance Attribute Details
#components ⇒ Object
Returns the value of attribute components.
21 22 23 |
# File 'lib/utopia/path.rb', line 21 def components @components end |
Class Method Details
.[](path) ⇒ Object
Coerce the given value into a path.
89 90 91 |
# File 'lib/utopia/path.rb', line 89 def self.[] path self.create(path) end |
.create(path) ⇒ Object
Coerce a value into a path.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/utopia/path.rb', line 133 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.
126 127 128 |
# File 'lib/utopia/path.rb', line 126 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.
112 113 114 |
# File 'lib/utopia/path.rb', line 112 def self.from_string(string) self.new(unescape(string).split(SEPARATOR, -1)) end |
.load(value) ⇒ Object
Load a path from its serialized form.
119 120 121 |
# File 'lib/utopia/path.rb', line 119 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.
49 50 51 |
# File 'lib/utopia/path.rb', line 49 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 root path.
41 42 43 |
# File 'lib/utopia/path.rb', line 41 def self.root self.new([""]) end |
.shortest_path(path, root) ⇒ Object
Compute the shortest relative path from the containing directory of root to path.
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/utopia/path.rb', line 57 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.
96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/utopia/path.rb', line 96 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.
80 81 82 83 84 |
# File 'lib/utopia/path.rb', line 80 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.
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/utopia/path.rb', line 265 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
293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/utopia/path.rb', line 293 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.
451 452 453 |
# File 'lib/utopia/path.rb', line 451 def <=> other @components <=> other.components end |
#==(other) ⇒ Object
Compare this object with another object.
471 472 473 474 475 476 477 478 479 |
# File 'lib/utopia/path.rb', line 471 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.
495 496 497 |
# File 'lib/utopia/path.rb', line 495 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 #[].
503 504 505 |
# File 'lib/utopia/path.rb', line 503 def []= index, value return @components[component_offset(index)] = value end |
#absolute? ⇒ Boolean
Check whether this path is absolute.
192 193 194 |
# File 'lib/utopia/path.rb', line 192 def absolute? @components.first == "" end |
#ascend(&block) ⇒ Object
Enumerate paths from this path up to its first component.
415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/utopia/path.rb', line 415 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
368 369 370 371 372 |
# File 'lib/utopia/path.rb', line 368 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.
510 511 512 |
# File 'lib/utopia/path.rb', line 510 def delete_at(index) @components.delete_at(component_offset(index)) end |
#descend(&block) ⇒ Object
Enumerate paths from the first component down to this path.
400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/utopia/path.rb', line 400 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.
164 165 166 |
# File 'lib/utopia/path.rb', line 164 def directory? return @components.last == "" end |
#dirname(count = 1) ⇒ Object
Remove trailing path components.
384 385 386 387 388 |
# File 'lib/utopia/path.rb', line 384 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.
444 445 446 |
# File 'lib/utopia/path.rb', line 444 def dup return Path.new(components.dup) end |
#empty? ⇒ Boolean
Check whether this path has no components.
35 36 37 |
# File 'lib/utopia/path.rb', line 35 def empty? @components.empty? end |
#eql?(other) ⇒ Boolean
Check whether this object is equivalent to another object.
458 459 460 |
# File 'lib/utopia/path.rb', line 458 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.
258 259 260 |
# File 'lib/utopia/path.rb', line 258 def (root) root + self end |
#extension ⇒ Object
375 376 377 378 379 |
# File 'lib/utopia/path.rb', line 375 def extension _, extension = @components.last.split(".", 2) return extension end |
#file? ⇒ Boolean Also known as: last?
Check whether this path denotes a file.
170 171 172 |
# File 'lib/utopia/path.rb', line 170 def file? return @components.last != "" end |
#first ⇒ Object
Return the first path component, excluding the root marker.
340 341 342 343 344 345 346 |
# File 'lib/utopia/path.rb', line 340 def first if absolute? @components[1] else @components[0] end end |
#freeze ⇒ Object
Freeze this object and its internal state.
25 26 27 28 29 30 31 |
# File 'lib/utopia/path.rb', line 25 def freeze return self if frozen? @components.freeze super end |
#hash ⇒ Object
Compute the hash value for this object.
464 465 466 |
# File 'lib/utopia/path.rb', line 464 def hash @components.hash end |
#include?(*arguments) ⇒ Boolean
Check whether this collection includes the given value.
158 159 160 |
# File 'lib/utopia/path.rb', line 158 def include?(*arguments) @components.include?(*arguments) end |
#join(other) ⇒ Object
246 247 248 249 250 251 252 253 |
# File 'lib/utopia/path.rb', line 246 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.
350 351 352 353 354 |
# File 'lib/utopia/path.rb', line 350 def last if @components != [""] @components.last end end |
#local_path(separator = File::SEPARATOR) ⇒ Object
Format this path using a local filesystem separator.
393 394 395 |
# File 'lib/utopia/path.rb', line 393 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.
360 361 362 363 364 365 |
# File 'lib/utopia/path.rb', line 360 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.
186 187 188 |
# File 'lib/utopia/path.rb', line 186 def relative? @components.first != "" end |
#replace(other_path) ⇒ Object
Replace this path's components with a copy of another path's components.
151 152 153 |
# File 'lib/utopia/path.rb', line 151 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.
73 74 75 |
# File 'lib/utopia/path.rb', line 73 def shortest_path(root) self.class.shortest_path(self, root) end |
#simplify ⇒ Object
Normalize current-directory, parent-directory, and repeated-separator components.
307 308 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 |
# File 'lib/utopia/path.rb', line 307 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.
430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/utopia/path.rb', line 430 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.
484 485 486 487 488 489 490 |
# File 'lib/utopia/path.rb', line 484 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.
240 241 242 |
# File 'lib/utopia/path.rb', line 240 def to_a @components end |
#to_absolute ⇒ Object
Convert this path to an absolute path.
198 199 200 201 202 203 204 |
# File 'lib/utopia/path.rb', line 198 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.
176 177 178 179 180 181 182 |
# File 'lib/utopia/path.rb', line 176 def to_directory if directory? return self else return self.class.new(@components + [""]) end end |
#to_relative! ⇒ Object
Remove the first component when this path is relative.
208 209 210 |
# File 'lib/utopia/path.rb', line 208 def to_relative! @components.shift if relative? end |
#to_str ⇒ Object Also known as: to_s
Convert this object to a string.
214 215 216 217 218 219 220 |
# File 'lib/utopia/path.rb', line 214 def to_str if @components == [""] SEPARATOR else @components.join(SEPARATOR) end end |
#to_url_path ⇒ Object
Encode this application path as a URL path.
226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/utopia/path.rb', line 226 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.
284 285 286 |
# File 'lib/utopia/path.rb', line 284 def with_prefix(*arguments) self.class.create(*arguments) + self end |