Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 43 additions & 25 deletions lib/utopia/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def empty?
# Construct the root path.
# @returns [Path] The root path.
def self.root
self.new([""])
self.new(["", ""])
end

# Compute the number of leading components shared by two sequences.
Expand All @@ -63,8 +63,14 @@ def self.shortest_path(path, root)

# The difference between the root path and the required path, taking into account the common prefix:
up = root.components.size - i
down = path.components[i..-1]

return self.create([".."] * up + path.components[i..-1])
# A parent component already denotes the destination directory, so a trailing directory separator is redundant:
if up > 0 && down == [""]
down = []
end

return self.create([".."] * up + down)
end

# Compute the shortest relative path from the containing directory of `root` to this path.
Expand Down Expand Up @@ -135,6 +141,7 @@ def self.create(path)
when Path
return path
when Array
return self.root if path == [""]
return self.new(path)
when String
return self.new(unescape(path).split(SEPARATOR, -1))
Expand Down Expand Up @@ -199,7 +206,7 @@ def to_absolute
if absolute?
return self
else
return self.class.new([""] + @components)
return self.class.create([""] + @components)
end
end

Expand All @@ -212,23 +219,14 @@ def to_relative!
# Convert this object to a string.
# @returns [String] The resulting string.
def to_str
if @components == [""]
SEPARATOR
else
@components.join(SEPARATOR)
end
@components.join(SEPARATOR)
end

alias to_s to_str

# Encode this application path as a URL path.
# @returns [Protocol::URL::Path] The encoded URL path.
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,
Expand All @@ -246,7 +244,7 @@ def to_a
def join(other)
# Check whether other is an absolute path:
if other.first == ""
self.class.new(other)
self.class.create(other)
else
self.class.new(@components + other).simplify
end
Expand Down Expand Up @@ -332,7 +330,7 @@ def simplify
index += 1
end

return self.class.new(components)
return self.class.create(components)
end

# Return the first path component, excluding the root marker.
Expand All @@ -348,20 +346,27 @@ def first
# Return the last path component, excluding the root marker.
# @returns [String | Nil] The last component.
def last
if @components != [""]
@components.last
end
@components.last
end

alias last? file?

# Remove the last path component without converting the root path to a relative path.
# @returns [String | Nil] The removed component.
def pop
# We don't want to convert an absolute path to a relative path.
if @components != [""]
@components.pop
# The absolute root has no path component to remove:
if @components == ["", ""]
return nil
end

component = @components.pop

# Preserve the canonical root after removing the final component:
if @components == [""]
@components << ""
end

return component
end

# @returns [String] The last path component without its file extension.
Expand All @@ -382,7 +387,7 @@ def extension
# @parameter count [Integer] The number of components.
# @returns [Path] The containing path.
def dirname(count = 1)
path = self.class.new(@components[0...-count])
path = self.class.create(@components[0...-count])

return absolute? ? path.to_absolute : path
end
Expand All @@ -400,12 +405,17 @@ def local_path(separator = File::SEPARATOR)
def descend(&block)
return to_enum(:descend) unless block_given?

if @components == ["", ""]
yield self.class.root
return @components
end

components = []

@components.each do |component|
components << component

yield self.class.new(components.dup)
yield self.class.create(components.dup)
end
end

Expand All @@ -418,7 +428,10 @@ def ascend(&block)
components = self.components.dup

while components.any?
yield self.class.new(components.dup)
path = self.class.create(components.dup)
yield path

break if path.components == ["", ""]

components.pop
end
Expand All @@ -433,7 +446,7 @@ def split(at)
end

if at
return [self.class.new(@components[0...at]), self.class.new(@components[at+1..-1])]
return [self.class.create(@components[0...at]), self.class.create(@components[at+1..-1])]
else
return nil
end
Expand Down Expand Up @@ -482,6 +495,11 @@ def == other
# @parameter other [Path] The possible prefix.
# @returns [Boolean] Whether this path starts with all components of `other`.
def start_with? other
# The root directory contains every absolute path:
if other.components == ["", ""]
return absolute?
end

other.components.each_with_index do |part, index|
return false if @components[index] != part
end
Expand Down
71 changes: 67 additions & 4 deletions test/utopia/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@
let(:path) {subject.root}

it "is a root path" do
expect(path).to be == [""]
expect(path).to be == ["", ""]
expect(path).to be == subject.create("/")
expect(path).to be == subject.create([""])
expect(path).not.to be == subject.create("")
expect(path).not.to be(:relative?)
expect(path).to be(:absolute?)
expect(path).to have_attributes(local_path: be == "")
expect(path).to be(:directory?)
expect(path).not.to be(:file?)
expect(path).to have_attributes(local_path: be == "/")
end

it "traverses the root exactly once" do
expect(path.descend.to_a).to be == [path]
expect(path.ascend.to_a).to be == [path]
end
end

Expand Down Expand Up @@ -120,7 +130,7 @@
let(:path) {subject.root}

it "can extract the last path component from a a root path" do
expect(path.last).to be == nil
expect(path.last).to be == ""
end
end

Expand All @@ -131,6 +141,22 @@
end
end

with "#pop" do
it "does not remove the absolute root" do
path = Utopia::Path.root

expect(path.pop).to be_nil
expect(path).to be == Utopia::Path.root
end

it "preserves the absolute root after removing the final component" do
path = Utopia::Path["/foo"]

expect(path.pop).to be == "foo"
expect(path).to be == Utopia::Path.root
end
end

with "#+" do
it "can add root path as string" do
root = Utopia::Path["/invoices/_template"]
Expand Down Expand Up @@ -161,6 +187,12 @@
end
end

with "#to_absolute" do
it "converts an empty relative path to the absolute root" do
expect(Utopia::Path[""].to_absolute).to be == Utopia::Path.root
end
end

it "should concatenate absolute paths" do
root = Utopia::Path["/"]

Expand All @@ -173,7 +205,7 @@

descendants = root.descend.to_a

expect(descendants[0].components).to be == [""]
expect(descendants[0].components).to be == ["", ""]
expect(descendants[1].components).to be == ["", "foo"]
expect(descendants[2].components).to be == ["", "foo", "bar"]

Expand Down Expand Up @@ -224,12 +256,30 @@
expect(path.start_with?(path.dirname)).to be == true
end

it "should start with the absolute root" do
path = Utopia::Path["/a/b/c/d/e"]

expect(path.start_with?(Utopia::Path.root)).to be == true
end

it "should preserve the absolute root when taking the directory name" do
path = Utopia::Path["/foo"]

expect(path.dirname).to be == Utopia::Path.root
end

it "should split at the specified point" do
path = Utopia::Path["/a/b/c/d/e"]

expect(path.split("c")).to be == [Utopia::Path["/a/b"], Utopia::Path["d/e"]]
end

it "should preserve the absolute root when splitting the first component" do
path = Utopia::Path["/foo"]

expect(path.split("foo")).to be == [Utopia::Path.root, Utopia::Path[""]]
end

it "shouldn't be able to modify frozen paths" do
path = Utopia::Path["dir/foo.html"]

Expand Down Expand Up @@ -287,7 +337,20 @@
expect((output + short).simplify).to be == input
end

it "should omit a redundant root separator when ascending to a parent directory" do
input = Utopia::Path.root
output = Utopia::Path["/nested/index"]

expect(input.shortest_path(output)).to be == Utopia::Path[".."]
end

with "#simplify" do
it "preserves the absolute root" do
path = Utopia::Path["/foo/.."]

expect(path.simplify).to be == Utopia::Path.root
end

it "doesn't remove leading .. from relative paths" do
path = Utopia::Path["../foo/bar"]
simplified = path.simplify
Expand Down
Loading