Skip to content
Merged
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
11 changes: 6 additions & 5 deletions lib/protocol/url/absolute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ def +(other)
end

# Append the absolute URL to the given buffer.
def append(buffer = String.new)
# @parameter explicit [Boolean] Ignored because absolute URLs are already lexically identifiable.
def append(buffer = String.new, explicit: false)
buffer << @scheme << ":" if @scheme
buffer << "//" << @authority if @authority
super(buffer)
Expand Down Expand Up @@ -147,9 +148,8 @@ def with(scheme: @scheme, authority: @authority, path: nil, query: @query, fragm

# Absolute URLs cannot be made relative without comparing their origins.
# @parameter base [Object] The ignored base URL or path.
# @parameter explicit [Boolean] Ignored for absolute URLs.
# @returns [self] This absolute URL.
def relative_to(base, explicit: false)
def relative_to(base)
return self
end

Expand All @@ -170,9 +170,10 @@ def <=>(other)

# Convert the URL to its string representation.
#
# @parameter explicit [Boolean] Ignored because absolute URLs are already lexically identifiable.
# @returns [String] The formatted absolute URL string.
def to_s
append
def to_s(explicit: false)
append(explicit: explicit)
end
end
end
Expand Down
11 changes: 3 additions & 8 deletions lib/protocol/url/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def self.for(components, encoding: Encoding)
#
# @parameter target [String] The destination path (where you want to go).
# @parameter from [String] The source path (where you are starting from).
# @parameter explicit [Boolean] Whether same-directory paths should start with `./`.
# @returns [String] The relative path from `from` to `target`.
#
# @example Calculate relative path between pages.
Expand All @@ -77,8 +76,8 @@ def self.for(components, encoding: Encoding)
# @example Calculate relative path in same directory.
# Path.relative("/docs/guide.html", "/docs/index.html")
# # => "guide.html"
def self.relative(target, from, explicit: false)
return Path[target].relative(from, explicit: explicit).to_s
def self.relative(target, from)
return Path[target].relative(from).to_s
end

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

Expand Down Expand Up @@ -388,9 +386,6 @@ def relative(from, explicit: false)
# An empty reference identifies the current document, so identify the current directory explicitly:
if relative_segments == [""]
relative_segments = [".", ""]
elsif explicit && relative_segments.first != ".."
# Identify same-directory references explicitly:
relative_segments.unshift(".")
elsif relative_segments.first&.include?(":")
# A colon in the first segment would be interpreted as a URI scheme:
relative_segments.unshift(".")
Expand Down
5 changes: 3 additions & 2 deletions lib/protocol/url/reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ def fragment?
# Append the reference to the given buffer.
# Encodes the fragment; the path already retains its encoded structure.
# Query strings are passed through as-is (they contain = and & which are valid syntax).
def append(buffer = String.new)
buffer << @path.encoded
# @parameter explicit [Boolean] Whether the result should be lexically identifiable as a URL in a mixed grammar.
def append(buffer = String.new, explicit: false)
append_path(buffer, explicit: explicit)

if @query and !@query.empty?
buffer << "?" << @query
Expand Down
27 changes: 20 additions & 7 deletions lib/protocol/url/relative.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,15 @@ def with(path: nil, query: @query, fragment: @fragment, pop: true)
# are preserved when converting a root-relative path.
#
# @parameter base [Relative | Path | String] The base URL or path.
# @parameter explicit [Boolean] Whether same-directory paths should start with `./`.
# @returns [Relative] The relative URL.
def relative_to(base, explicit: false)
def relative_to(base)
return self unless @path.absolute?

if base.is_a?(Relative)
base = base.path
end

return self.class.new(@path.relative(base, explicit: explicit), @query, @fragment)
return self.class.new(@path.relative(base), @query, @fragment)
end

# Normalize the encoded path and simplify its structure.
Expand Down Expand Up @@ -176,8 +175,9 @@ def normalize!

# Append the relative URL to the given buffer.
# The path, query, and fragment are expected to already be properly encoded.
def append(buffer = String.new)
buffer << @path.encoded
# @parameter explicit [Boolean] Whether the result should be lexically identifiable as a URL in a mixed grammar.
def append(buffer = String.new, explicit: false)
append_path(buffer, explicit: explicit)

if @query and !@query.empty?
buffer << "?" << @query
Expand Down Expand Up @@ -237,10 +237,13 @@ def ===(other)
end

# Convert the URL to its string representation.
# When explicit, same-directory references start with `./` so they can be
# distinguished from non-URL values in a mixed grammar.
#
# @parameter explicit [Boolean] Whether the result should be lexically identifiable as a URL in a mixed grammar.
# @returns [String] The formatted URL string.
def to_s
append
def to_s(explicit: false)
append(explicit: explicit)
end

# Convert the URL to a JSON-compatible representation.
Expand All @@ -264,6 +267,16 @@ def inspect
"#<#{self.class} #{to_s}>"
end

# Append the path, optionally identifying a relative URL explicitly.
private def append_path(buffer, explicit: false)
if explicit && @path.relative?
path = @path.encoded
buffer << "./" unless path.start_with?("./", "../")
end

buffer << @path.encoded
end

end
end
end
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Support explicit URL serialization for mixed grammars, and remove explicit prefixes from relative path calculation.

## v0.17.0

- Add optional explicit `./` prefixes when generating same-directory relative URLs.
Expand Down
9 changes: 8 additions & 1 deletion test/protocol/url/absolute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,14 @@
url = Protocol::URL::Absolute.new("https", "example.com", "/docs/guide", "q=ruby", "examples")

expect(url.relative_to("/docs/index")).to be_equal(url)
expect(url.relative_to("/docs/index", explicit: true)).to be_equal(url)
end
end

with "#to_s" do
it "ignores explicit relative serialization" do
url = Protocol::URL::Absolute.new("https", "example.com", "/docs/guide")

expect(url.to_s(explicit: true)).to be == "https://example.com/docs/guide"
end
end

Expand Down
8 changes: 0 additions & 8 deletions test/protocol/url/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -501,14 +501,6 @@
expect(Protocol::URL::Path.relative("/docs/guide.html", "/docs/index.html")).to be == "guide.html"
end

it "can identify a same-directory path explicitly" do
expect(Protocol::URL::Path.relative("/docs/guide.html", "/docs/index.html", explicit: true)).to be == "./guide.html"
end

it "does not prefix a parent-directory path" do
expect(Protocol::URL::Path.relative("/assets/app.js", "/docs/index.html", explicit: true)).to be == "../assets/app.js"
end

it "calculates relative path from root to subdirectory" do
expect(Protocol::URL::Path.relative("/foo/bar/", "/")).to be == "foo/bar/"
end
Expand Down
8 changes: 8 additions & 0 deletions test/protocol/url/reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,14 @@
end
end

with "#to_s" do
it "can serialize relative references explicitly" do
reference = subject.new("guide", nil, nil, {"page" => "2"})

expect(reference.to_s(explicit: true)).to be == "./guide?page=2"
end
end

describe Protocol::URL::Reference.parse("path%20with%20spaces/image.jpg") do
it "preserves encoded whitespace" do
expect(subject.to_s).to be == "path%20with%20spaces/image.jpg"
Expand Down
49 changes: 35 additions & 14 deletions test/protocol/url/relative.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,6 @@
expect(result.fragment).to be == "examples"
end

it "can identify same-directory URLs explicitly" do
url = Protocol::URL::Relative.new("/docs/guide", "q=ruby", "examples")
result = url.relative_to("/docs/index", explicit: true)

expect(result.to_s).to be == "./guide?q=ruby#examples"
end

it "does not prefix parent-directory URLs" do
url = Protocol::URL::Relative.new("/assets/app.js")
result = url.relative_to("/docs/index", explicit: true)

expect(result.to_s).to be == "../assets/app.js"
end

it "accepts a URL as the base" do
url = Protocol::URL::Relative.new("/docs/guide")
base = Protocol::URL::Relative.new("/docs/index")
Expand Down Expand Up @@ -313,6 +299,41 @@
end
end

with "#to_s" do
it "preserves minimal relative URLs by default" do
url = Protocol::URL::Relative.new("guide", "q=ruby", "examples")

expect(url.to_s).to be == "guide?q=ruby#examples"
end

it "can serialize same-directory URLs explicitly" do
url = Protocol::URL::Relative.new("guide", "q=ruby", "examples")
serialized = url.to_s(explicit: true)

expect(serialized).to be == "./guide?q=ruby#examples"
expect(Protocol::URL[serialized]).to be_a(Protocol::URL::Relative)
end

it "can explicitly serialize a normalized combined URL" do
base = Protocol::URL::Relative.new("./_components/")
url = base + Protocol::URL::Relative.new("./app.js")

expect(url.to_s).to be == "_components/app.js"
expect(url.to_s(explicit: true)).to be == "./_components/app.js"
end

it "preserves parent- and root-relative URLs" do
expect(Protocol::URL::Relative.new("../guide").to_s(explicit: true)).to be == "../guide"
expect(Protocol::URL::Relative.new("/guide").to_s(explicit: true)).to be == "/guide"
end

it "identifies an empty path explicitly" do
url = Protocol::URL::Relative.new("", "q=ruby", "examples")

expect(url.to_s(explicit: true)).to be == "./?q=ruby#examples"
end
end

with "#as_json" do
it "returns string representation" do
url = Protocol::URL::Relative.new("/path", "q=test", "section")
Expand Down