Skip to content

Commit 30d31c2

Browse files
Correct root path representation.
Assisted-By: devx/cbdeae41-9308-4071-ad53-58cd92db2946
1 parent 8f9df47 commit 30d31c2

2 files changed

Lines changed: 74 additions & 23 deletions

File tree

lib/utopia/path.rb

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ class Path
1515
# Initialize a path from its individual components.
1616
# @parameter components [Array(String)] The path components, including empty components that denote leading or trailing separators.
1717
def initialize(components = [])
18-
@components = components
18+
# An absolute root includes both its leading and trailing separators:
19+
if components == [""]
20+
@components = ["", ""]
21+
else
22+
@components = components
23+
end
1924
end
2025

2126
attr_accessor :components
@@ -39,7 +44,7 @@ def empty?
3944
# Construct the root path.
4045
# @returns [Path] The root path.
4146
def self.root
42-
self.new([""])
47+
self.new(["", ""])
4348
end
4449

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

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

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

7081
# Compute the shortest relative path from the containing directory of `root` to this path.
@@ -212,23 +223,14 @@ def to_relative!
212223
# Convert this object to a string.
213224
# @returns [String] The resulting string.
214225
def to_str
215-
if @components == [""]
216-
SEPARATOR
217-
else
218-
@components.join(SEPARATOR)
219-
end
226+
@components.join(SEPARATOR)
220227
end
221228

222229
alias to_s to_str
223230

224231
# Encode this application path as a URL path.
225232
# @returns [Protocol::URL::Path] The encoded URL path.
226233
def to_url_path
227-
# Preserve Utopia's compact representation of the absolute root:
228-
if @components == [""]
229-
return Protocol::URL::Path[SEPARATOR]
230-
end
231-
232234
return Protocol::URL::Path.for(
233235
@components,
234236
encoding: Protocol::URL::Encoding::System,
@@ -348,20 +350,27 @@ def first
348350
# Return the last path component, excluding the root marker.
349351
# @returns [String | Nil] The last component.
350352
def last
351-
if @components != [""]
352-
@components.last
353-
end
353+
@components.last
354354
end
355355

356356
alias last? file?
357357

358358
# Remove the last path component without converting the root path to a relative path.
359359
# @returns [String | Nil] The removed component.
360360
def pop
361-
# We don't want to convert an absolute path to a relative path.
362-
if @components != [""]
363-
@components.pop
361+
# The absolute root has no path component to remove:
362+
if @components == ["", ""]
363+
return nil
364364
end
365+
366+
component = @components.pop
367+
368+
# Preserve the canonical absolute root after removing its final component:
369+
if @components == [""]
370+
@components << ""
371+
end
372+
373+
return component
365374
end
366375

367376
# @returns [String] The last path component without its file extension.
@@ -482,6 +491,11 @@ def == other
482491
# @parameter other [Path] The possible prefix.
483492
# @returns [Boolean] Whether this path starts with all components of `other`.
484493
def start_with? other
494+
# The root directory contains every absolute path:
495+
if other.components == ["", ""]
496+
return absolute?
497+
end
498+
485499
other.components.each_with_index do |part, index|
486500
return false if @components[index] != part
487501
end

test/utopia/path.rb

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
let(:path) {subject.root}
2424

2525
it "is a root path" do
26-
expect(path).to be == [""]
26+
expect(path).to be == ["", ""]
27+
expect(path).to be == subject.create("/")
28+
expect(path).not.to be == subject.create("")
2729
expect(path).not.to be(:relative?)
2830
expect(path).to be(:absolute?)
29-
expect(path).to have_attributes(local_path: be == "")
31+
expect(path).to have_attributes(local_path: be == "/")
3032
end
3133
end
3234

@@ -120,7 +122,7 @@
120122
let(:path) {subject.root}
121123

122124
it "can extract the last path component from a a root path" do
123-
expect(path.last).to be == nil
125+
expect(path.last).to be == ""
124126
end
125127
end
126128

@@ -131,6 +133,22 @@
131133
end
132134
end
133135

136+
with "#pop" do
137+
it "does not remove the absolute root" do
138+
path = Utopia::Path.root
139+
140+
expect(path.pop).to be_nil
141+
expect(path).to be == Utopia::Path.root
142+
end
143+
144+
it "preserves the absolute root after removing the final component" do
145+
path = Utopia::Path["/foo"]
146+
147+
expect(path.pop).to be == "foo"
148+
expect(path).to be == Utopia::Path.root
149+
end
150+
end
151+
134152
with "#+" do
135153
it "can add root path as string" do
136154
root = Utopia::Path["/invoices/_template"]
@@ -173,7 +191,7 @@
173191

174192
descendants = root.descend.to_a
175193

176-
expect(descendants[0].components).to be == [""]
194+
expect(descendants[0].components).to be == ["", ""]
177195
expect(descendants[1].components).to be == ["", "foo"]
178196
expect(descendants[2].components).to be == ["", "foo", "bar"]
179197

@@ -224,6 +242,18 @@
224242
expect(path.start_with?(path.dirname)).to be == true
225243
end
226244

245+
it "should start with the absolute root" do
246+
path = Utopia::Path["/a/b/c/d/e"]
247+
248+
expect(path.start_with?(Utopia::Path.root)).to be == true
249+
end
250+
251+
it "should preserve the absolute root when taking the directory name" do
252+
path = Utopia::Path["/foo"]
253+
254+
expect(path.dirname).to be == Utopia::Path.root
255+
end
256+
227257
it "should split at the specified point" do
228258
path = Utopia::Path["/a/b/c/d/e"]
229259

@@ -287,6 +317,13 @@
287317
expect((output + short).simplify).to be == input
288318
end
289319

320+
it "should omit a redundant root separator when ascending to a parent directory" do
321+
input = Utopia::Path.root
322+
output = Utopia::Path["/nested/index"]
323+
324+
expect(input.shortest_path(output)).to be == Utopia::Path[".."]
325+
end
326+
290327
with "#simplify" do
291328
it "doesn't remove leading .. from relative paths" do
292329
path = Utopia::Path["../foo/bar"]

0 commit comments

Comments
 (0)