class Path
Represents a path as an array of path components. Useful for efficient URL manipulation.
Nested
Definitions
def initialize(components = [])
Initialize a path from its individual components.
Signature
-
parameter
componentsArray(String) The path components, including empty components that denote leading or trailing separators.
Implementation
def initialize(components = [])
@components = components
end
def freeze
Freeze this object and its internal state.
Signature
-
returns
self This object.
Implementation
def freeze
return self if frozen?
@components.freeze
super
end
def empty?
Check whether this path has no components.
Signature
-
returns
Boolean Whether the path has no components.
Implementation
def empty?
@components.empty?
end
def self.root
Construct the root path.
Signature
-
returns
Path The root path.
Implementation
def self.root
self.new([""])
end
def self.prefix_length(a, b)
Compute the number of leading components shared by two sequences.
Signature
-
parameter
aArray The first sequence.
-
parameter
bArray The second sequence.
-
returns
Integer | Nil The shared prefix length, or
nilwhen every component in the shorter sequence matches.
Implementation
def self.prefix_length(a, b)
[a.size, b.size].min.times{|i| return i if a[i] != b[i]}
end
def self.shortest_path(path, root)
Compute the shortest relative path from the containing directory of root to path.
Signature
-
parameter
pathPath | String | Array The destination path.
-
parameter
rootPath | String | Array The source path.
-
returns
Path The shortest relative path.
Implementation
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
def shortest_path(root)
Compute the shortest relative path from the containing directory of root to this path.
Signature
-
parameter
rootPath | String | Array The source path.
-
returns
Path The shortest relative path.
Implementation
def shortest_path(root)
self.class.shortest_path(self, root)
end
def self.unescape(string)
Decode URL-encoded path content, converting + to whitespace and percent-encoded bytes to their corresponding characters.
Signature
-
parameter
stringString The encoded content.
-
returns
String The decoded content.
Implementation
def self.unescape(string)
string.tr("+", " ").gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
[$1.delete("%")].pack("H*")
end
end
def self.[](path)
Coerce the given value into a path.
Signature
-
parameter
pathUtopia::Path | String The path.
-
returns
Path | Nil The coerced path.
Implementation
def self.[] path
self.create(path)
end
def [](index)
Fetch one or more path components, excluding root and directory markers from indexing.
Signature
-
parameter
indexInteger | Range The component index or range.
-
returns
String | Array(String) | Nil The selected component or components.
Implementation
def [] index
return @components[component_offset(index)]
end
def self.split(path)
Convert a path value into an array of components.
Signature
-
parameter
pathUtopia::Path | String The path.
-
returns
Array The path components.
Implementation
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
def split(at)
Split this path around a component or component index.
Signature
-
parameter
atInteger | String The component index or value at which to split.
-
returns
Array(Path, Path) | Nil The paths before and after the matched component, or
nilwhen it is not found.
Implementation
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
def self.from_string(string)
Construct a path from URL-encoded text. This is an optimized direct entry point used by controller invocations.
Signature
-
parameter
stringString The encoded path.
-
returns
Path The decoded path.
Implementation
def self.from_string(string)
self.new(unescape(string).split(SEPARATOR, -1))
end
def self.load(value)
Load a path from its serialized form.
Signature
-
parameter
valueString | Nil The serialized path.
-
returns
Path | Nil The loaded path.
Implementation
def self.load(value)
from_string(value) if value
end
def self.dump(instance)
Serialize a path.
Signature
-
parameter
instancePath | Nil The path to serialize.
-
returns
String | Nil The serialized path.
Implementation
def self.dump(instance)
instance.to_s if instance
end
def self.create(path)
Coerce a value into a path.
Signature
-
parameter
pathPath | Array | String | Object | Nil The value to coerce.
-
returns
Path | Nil The coerced path.
Implementation
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
def replace(other_path)
Replace this path's components with a copy of another path's components.
Signature
-
parameter
other_pathPath The replacement path.
-
returns
Array(String) The copied components.
Implementation
def replace(other_path)
@components = other_path.components.dup
end
def include?(*arguments)
Check whether this collection includes the given value.
Signature
-
parameter
argumentsArray The arguments.
-
returns
Boolean Whether any component matches the given argument.
Implementation
def include?(*arguments)
@components.include?(*arguments)
end
def directory?
Check whether this path denotes a directory.
Signature
-
returns
Boolean Whether the path ends with a directory separator.
Implementation
def directory?
return @components.last == ""
end
def file?
Check whether this path denotes a file.
Signature
-
returns
Boolean Whether the path ends with a file component.
Implementation
def file?
return @components.last != ""
end
def to_directory
Convert this path to a directory path.
Signature
-
returns
Path A directory path.
Implementation
def to_directory
if directory?
return self
else
return self.class.new(@components + [""])
end
end
def relative?
Check whether this path is relative.
Signature
-
returns
Boolean Whether the path is relative.
Implementation
def relative?
@components.first != ""
end
def absolute?
Check whether this path is absolute.
Signature
-
returns
Boolean Whether the path is absolute.
Implementation
def absolute?
@components.first == ""
end
def to_absolute
Convert this path to an absolute path.
Signature
-
returns
Path An absolute path.
Implementation
def to_absolute
if absolute?
return self
else
return self.class.new([""] + @components)
end
end
def to_relative!
Remove the first component when this path is relative.
Signature
-
returns
String | Nil The removed component, or
nilwhen the path is absolute.
Implementation
def to_relative!
@components.shift if relative?
end
def to_str
Convert this object to a string.
Signature
-
returns
String The resulting string.
Implementation
def to_str
if @components == [""]
SEPARATOR
else
@components.join(SEPARATOR)
end
end
def to_a
Convert this path to an array of components.
Signature
-
returns
Array The resulting values.
Implementation
def to_a
@components
end
def join(other)
Signature
-
parameter
otherArray(String) The path components to append.
-
returns
Path The joined and simplified path.
Implementation
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
def expand(root)
Resolve this path relative to a root path.
Signature
-
parameter
rootPath The root path.
-
returns
Path The resolved path.
Implementation
def expand(root)
root + self
end
def +(other)
Append path components and return the resulting path.
Signature
-
parameter
otherPath | Array | String | Object The value to append.
-
returns
Path The joined and simplified path, or
otherwhen it is an absolute path.
Implementation
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
def with_prefix(*arguments)
Prepend a path to this path.
Signature
-
parameter
argumentsArray The arguments accepted by
.create.-
returns
Path The prefixed path.
Implementation
def with_prefix(*arguments)
self.class.create(*arguments) + self
end
def -(other)
Computes the difference of the path. /a/b/c - /a/b -> c a/b/c - a/b -> c
Signature
-
parameter
otherPath The prefix path to remove.
-
returns
Path The remaining path.
Implementation
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
def simplify
Normalize current-directory, parent-directory, and repeated-separator components.
Signature
-
returns
Path The normalized path.
Implementation
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
def first
Return the first path component, excluding the root marker.
Signature
-
returns
String | Nil The first component.
Implementation
def first
if absolute?
@components[1]
else
@components[0]
end
end
def last
Return the last path component, excluding the root marker.
Signature
-
returns
String | Nil The last component.
Implementation
def last
if @components != [""]
@components.last
end
end
def pop
Remove the last path component without converting the root path to a relative path.
Signature
-
returns
String | Nil The removed component.
Implementation
def pop
# We don't want to convert an absolute path to a relative path.
if @components != [""]
@components.pop
end
end
def basename
Signature
-
returns
String The last path component without its file extension.
Implementation
def basename
basename, _ = @components.last.split(".", 2)
return basename || ""
end
def extension
Signature
-
returns
String | Nil The last path component's file extension.
Implementation
def extension
_, extension = @components.last.split(".", 2)
return extension
end
def dirname(count = 1)
Remove trailing path components.
Signature
-
parameter
countInteger The number of components.
-
returns
Path The containing path.
Implementation
def dirname(count = 1)
path = self.class.new(@components[0...-count])
return absolute? ? path.to_absolute : path
end
def local_path(separator = File::SEPARATOR)
Format this path using a local filesystem separator.
Signature
-
parameter
separatorString The component separator.
-
returns
String The local path.
Implementation
def local_path(separator = File::SEPARATOR)
@components.join(separator)
end
def descend(&block)
Enumerate paths from the first component down to this path.
Signature
-
yields
{|path| ...} Each successively longer path.
-
returns
Enumerator | Array An enumerator when no block is given, otherwise the component array.
Implementation
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
def ascend(&block)
Enumerate paths from this path up to its first component.
Signature
-
yields
{|path| ...} Each successively shorter path.
-
returns
Enumerator | Nil An enumerator when no block is given.
Implementation
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
def dup
Copy this path and its component array.
Signature
-
returns
Path The copied path.
Implementation
def dup
return Path.new(components.dup)
end
def <=>(other)
Compare this object with another object.
Signature
-
parameter
otherObject The object to compare.
-
returns
Integer | Nil The comparison result.
Implementation
def <=> other
@components <=> other.components
end
def eql?(other)
Check whether this object is equivalent to another object.
Signature
-
parameter
otherObject The object to compare.
-
returns
Boolean Whether the paths have the same class and components.
Implementation
def eql? other
self.class.eql?(other.class) and @components.eql?(other.components)
end
def hash
Compute the hash value for this object.
Signature
-
returns
Integer The resulting integer.
Implementation
def hash
@components.hash
end
def ==(other)
Compare this object with another object.
Signature
-
parameter
otherObject The object to compare.
-
returns
Boolean Whether the path is equivalent to the given string, array, or path.
Implementation
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
def start_with?(other)
Check whether this path starts with the given path.
Signature
-
parameter
otherPath The possible prefix.
-
returns
Boolean Whether this path starts with all components of
other.
Implementation
def start_with? other
other.components.each_with_index do |part, index|
return false if @components[index] != part
end
return true
end
def []=(index, value)
Replace one or more path components using the same root- and directory-marker-aware indexing as #[].
Signature
-
parameter
indexInteger | Range The component index or range.
-
parameter
valueString | Array(String) The replacement component or components.
-
returns
String | Array(String) The assigned value.
Implementation
def []= index, value
return @components[component_offset(index)] = value
end
def delete_at(index)
Delete a path component, excluding root and directory markers from indexing.
Signature
-
parameter
indexInteger The component index.
-
returns
String | Nil The deleted component.
Implementation
def delete_at(index)
@components.delete_at(component_offset(index))
end
def component_offset(index)
We adjust the index slightly so that indices reference path components rather than the directory markers at the start and end of the path components array.
Implementation
def component_offset(index)
if Range === index
Range.new(adjust_index(index.first), adjust_index(index.last), index.exclude_end?)
else
adjust_index(index)
end
end