Skip to content

Commit 4d47343

Browse files
Support explicit relative URL paths
1 parent 0306af5 commit 4d47343

7 files changed

Lines changed: 40 additions & 6 deletions

File tree

lib/protocol/url/absolute.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,9 @@ def with(scheme: @scheme, authority: @authority, path: nil, query: @query, fragm
147147

148148
# Absolute URLs cannot be made relative without comparing their origins.
149149
# @parameter base [Object] The ignored base URL or path.
150+
# @parameter explicit [Boolean] Ignored for absolute URLs.
150151
# @returns [self] This absolute URL.
151-
def relative_to(base)
152+
def relative_to(base, explicit: false)
152153
return self
153154
end
154155

lib/protocol/url/path.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def self.for(components, encoding: Encoding)
6767
#
6868
# @parameter target [String] The destination path (where you want to go).
6969
# @parameter from [String] The source path (where you are starting from).
70+
# @parameter explicit [Boolean] Whether same-directory paths should start with `./`.
7071
# @returns [String] The relative path from `from` to `target`.
7172
#
7273
# @example Calculate relative path between pages.
@@ -76,8 +77,8 @@ def self.for(components, encoding: Encoding)
7677
# @example Calculate relative path in same directory.
7778
# Path.relative("/docs/guide.html", "/docs/index.html")
7879
# # => "guide.html"
79-
def self.relative(target, from)
80-
return Path[target].relative(from).to_s
80+
def self.relative(target, from, explicit: false)
81+
return Path[target].relative(from, explicit: explicit).to_s
8182
end
8283

8384
# Initialize a path from either its complete encoded representation or encoded segments.
@@ -357,8 +358,9 @@ def join(other, pop: true, simplify: true)
357358
# Calculate this path relative to another path.
358359
#
359360
# @parameter from [String | Array(String) | Path] The source path.
361+
# @parameter explicit [Boolean] Whether same-directory paths should start with `./`.
360362
# @returns [Path] The relative path from `from` to this path.
361-
def relative(from)
363+
def relative(from, explicit: false)
362364
target_segments = self.segments
363365
from_segments = Path[from].segments
364366

@@ -386,6 +388,9 @@ def relative(from)
386388
# An empty reference identifies the current document, so identify the current directory explicitly:
387389
if relative_segments == [""]
388390
relative_segments = [".", ""]
391+
elsif explicit && relative_segments.first != ".."
392+
# Identify same-directory references explicitly:
393+
relative_segments.unshift(".")
389394
elsif relative_segments.first&.include?(":")
390395
# A colon in the first segment would be interpreted as a URI scheme:
391396
relative_segments.unshift(".")

lib/protocol/url/relative.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,16 @@ def with(path: nil, query: @query, fragment: @fragment, pop: true)
138138
# are preserved when converting a root-relative path.
139139
#
140140
# @parameter base [Relative | Path | String] The base URL or path.
141+
# @parameter explicit [Boolean] Whether same-directory paths should start with `./`.
141142
# @returns [Relative] The relative URL.
142-
def relative_to(base)
143+
def relative_to(base, explicit: false)
143144
return self unless @path.absolute?
144145

145146
if base.is_a?(Relative)
146147
base = base.path
147148
end
148149

149-
return self.class.new(@path.relative(base), @query, @fragment)
150+
return self.class.new(@path.relative(base, explicit: explicit), @query, @fragment)
150151
end
151152

152153
# Normalize the encoded path and simplify its structure.

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- Add optional explicit `./` prefixes when generating same-directory relative URLs.
6+
37
## v0.16.0
48

59
- Preserve directory and file semantics when generating relative URL paths.

test/protocol/url/absolute.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@
204204
url = Protocol::URL::Absolute.new("https", "example.com", "/docs/guide", "q=ruby", "examples")
205205

206206
expect(url.relative_to("/docs/index")).to be_equal(url)
207+
expect(url.relative_to("/docs/index", explicit: true)).to be_equal(url)
207208
end
208209
end
209210

test/protocol/url/path.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,14 @@
501501
expect(Protocol::URL::Path.relative("/docs/guide.html", "/docs/index.html")).to be == "guide.html"
502502
end
503503

504+
it "can identify a same-directory path explicitly" do
505+
expect(Protocol::URL::Path.relative("/docs/guide.html", "/docs/index.html", explicit: true)).to be == "./guide.html"
506+
end
507+
508+
it "does not prefix a parent-directory path" do
509+
expect(Protocol::URL::Path.relative("/assets/app.js", "/docs/index.html", explicit: true)).to be == "../assets/app.js"
510+
end
511+
504512
it "calculates relative path from root to subdirectory" do
505513
expect(Protocol::URL::Path.relative("/foo/bar/", "/")).to be == "foo/bar/"
506514
end

test/protocol/url/relative.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,20 @@
205205
expect(result.fragment).to be == "examples"
206206
end
207207

208+
it "can identify same-directory URLs explicitly" do
209+
url = Protocol::URL::Relative.new("/docs/guide", "q=ruby", "examples")
210+
result = url.relative_to("/docs/index", explicit: true)
211+
212+
expect(result.to_s).to be == "./guide?q=ruby#examples"
213+
end
214+
215+
it "does not prefix parent-directory URLs" do
216+
url = Protocol::URL::Relative.new("/assets/app.js")
217+
result = url.relative_to("/docs/index", explicit: true)
218+
219+
expect(result.to_s).to be == "../assets/app.js"
220+
end
221+
208222
it "accepts a URL as the base" do
209223
url = Protocol::URL::Relative.new("/docs/guide")
210224
base = Protocol::URL::Relative.new("/docs/index")

0 commit comments

Comments
 (0)