Class: Protocol::URL::Relative

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/protocol/url/relative.rb

Overview

Represents a relative URL, which does not include a scheme or authority.

Direct Known Subclasses

Absolute, Reference

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, query = nil, fragment = nil) ⇒ Relative

Initialize a new relative URL.



20
21
22
23
24
# File 'lib/protocol/url/relative.rb', line 20

def initialize(path, query = nil, fragment = nil)
	@path = Path[path]
	@query = query
	@fragment = fragment
end

Instance Attribute Details

#fragmentObject

Returns the value of attribute fragment.



52
53
54
# File 'lib/protocol/url/relative.rb', line 52

def fragment
  @fragment
end

#pathObject

Returns the value of attribute path.



39
40
41
# File 'lib/protocol/url/relative.rb', line 39

def path
  @path
end

#queryObject

Returns the value of attribute query.



49
50
51
# File 'lib/protocol/url/relative.rb', line 49

def query
  @query
end

#The fragment identifier.(fragmentidentifier.) ⇒ Object (readonly)



52
# File 'lib/protocol/url/relative.rb', line 52

attr_accessor :fragment

#The query string component.(querystringcomponent.) ⇒ Object (readonly)



49
# File 'lib/protocol/url/relative.rb', line 49

attr_accessor :query

Instance Method Details

#+(other) ⇒ Object

Combine this relative URL with another URL or path.

Examples:

Combine two relative paths.

base = Relative.new("/documents/reports/")
other = Relative.new("invoices/2024.pdf")
result = base + other
result.path.to_s  # => "/documents/reports/invoices/2024.pdf"

Navigate to parent directory.

base = Relative.new("/documents/reports/archive/")
other = Relative.new("../../summary.pdf")
result = base + other
result.path.to_s  # => "/documents/summary.pdf"


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/protocol/url/relative.rb', line 91

def +(other)
	case other
	when Absolute
		# Relative + Absolute: the absolute URL takes precedence
		# You can't apply relative navigation to an absolute URL
		other
	when Relative
		# Relative + Relative: merge paths directly
		self.class.new(
			@path.join(other.path),
			other.query,
			other.fragment
		)
	when String
		# Relative + String: parse and combine
		self + URL[other]
	else
		raise ArgumentError, "Cannot combine Relative URL with #{other.class}"
	end
end

#<=>(other) ⇒ Object

Compare this URL with another for sorting purposes.



219
220
221
# File 'lib/protocol/url/relative.rb', line 219

def <=>(other)
	to_ary <=> other.to_ary
end

#==(other) ⇒ Object

Check structural equality by comparing components.



227
228
229
# File 'lib/protocol/url/relative.rb', line 227

def ==(other)
	to_ary == other.to_ary
end

#===(other) ⇒ Object

Check string equality, useful for case statements.



235
236
237
# File 'lib/protocol/url/relative.rb', line 235

def ===(other)
	to_s === other
end

#append(buffer = String.new, explicit: false) ⇒ Object

Append the relative URL to the given buffer. The path, query, and fragment are expected to already be properly encoded.



179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/protocol/url/relative.rb', line 179

def append(buffer = String.new, explicit: false)
	append_path(buffer, explicit: explicit)
	
	if @query and !@query.empty?
		buffer << "?" << @query
	end
	
	if @fragment and !@fragment.empty?
		buffer << "#" << @fragment
	end
	
	return buffer
end

#as_jsonObject

Convert the URL to a JSON-compatible representation.



252
253
254
# File 'lib/protocol/url/relative.rb', line 252

def as_json(...)
	to_s
end

#equal?(other) ⇒ Boolean

Check if this URL is equal to another URL by comparing components.

Returns:

  • (Boolean)


211
212
213
# File 'lib/protocol/url/relative.rb', line 211

def equal?(other)
	to_ary == other.to_ary
end

#fragment?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/protocol/url/relative.rb', line 71

def fragment?
	@fragment and !@fragment.empty?
end

#freezeObject

Freeze the URL and its direct components.



28
29
30
31
32
33
34
35
36
# File 'lib/protocol/url/relative.rb', line 28

def freeze
	return self if frozen?
	
	@path.freeze
	@query.freeze
	@fragment.freeze
	
	return super
end

#hashObject

Compute a hash value for the URL based on its components.



203
204
205
# File 'lib/protocol/url/relative.rb', line 203

def hash
	to_ary.hash
end

#inspectObject

Generate a human-readable representation for debugging.



266
267
268
# File 'lib/protocol/url/relative.rb', line 266

def inspect
	"#<#{self.class} #{to_s}>"
end

#local_path(root) ⇒ Object Also known as: to_local_path

Resolve the URL path beneath a local filesystem root.



59
60
61
# File 'lib/protocol/url/relative.rb', line 59

def local_path(root)
	@path.local_path(root)
end

#normalize!Object

Normalize the encoded path and simplify its structure.

This modifies the URL in-place by normalizing and simplifying the path component:

  • Decodes percent-encoded unreserved characters
  • Uses uppercase hexadecimal digits for retained percent escapes
  • Removes "." segments (current directory)
  • Resolves ".." segments (parent directory)
  • Collapses empty path segments represented by consecutive slashes

Normalization is intentionally lossy. Callers that need to preserve the original path structure should retain the parsed URL and avoid this method.

Examples:

Basic normalization

url = Relative.new("/foo//bar/./baz/../qux")
url.normalize!
url.path.to_s  # => "/foo/bar/qux"


170
171
172
173
174
# File 'lib/protocol/url/relative.rb', line 170

def normalize!
	@path = @path.normalize.simplify
	
	return self
end

#query?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/protocol/url/relative.rb', line 66

def query?
	@query and !@query.empty?
end

#relative_to(base) ⇒ Object

Express this URL relative to the given base path.

Already-relative paths are returned unchanged. Query and fragment components are preserved when converting a root-relative path.



142
143
144
145
146
147
148
149
150
# File 'lib/protocol/url/relative.rb', line 142

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), @query, @fragment)
end

#The path component of the URL.=(pathcomponentoftheURL. = (value)) ⇒ Object



39
# File 'lib/protocol/url/relative.rb', line 39

attr :path

#to_aryObject

Convert the URL to an array representation.



196
197
198
# File 'lib/protocol/url/relative.rb', line 196

def to_ary
	[@path, @query, @fragment]
end

#to_jsonObject

Convert the URL to JSON.



259
260
261
# File 'lib/protocol/url/relative.rb', line 259

def to_json(...)
	as_json.to_json(...)
end

#to_s(explicit: false) ⇒ Object

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.



245
246
247
# File 'lib/protocol/url/relative.rb', line 245

def to_s(explicit: false)
	append(explicit: explicit)
end

#with(path: nil, query: @query, fragment: @fragment, pop: true) ⇒ Object

Create a new Relative URL with modified components.

Examples:

Update the query string.

url = Relative.new("/search", "query=ruby")
updated = url.with(query: "query=python")
updated.to_s  # => "/search?query=python"

Append to the path.

url = Relative.new("/documents/")
updated = url.with(path: "report.pdf", pop: false)
updated.to_s  # => "/documents/report.pdf"


129
130
131
132
133
# File 'lib/protocol/url/relative.rb', line 129

def with(path: nil, query: @query, fragment: @fragment, pop: true)
	path = @path.join(path, pop: pop) unless path.nil?
	
	self.class.new(path || @path, query, fragment)
end