protocol-url 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/url/absolute.rb +26 -2
- data/lib/protocol/url/encoding.rb +11 -0
- data/lib/protocol/url/form_data/nested.rb +51 -0
- data/lib/protocol/url/form_data/parser.rb +113 -0
- data/lib/protocol/url/reference.rb +31 -2
- data/lib/protocol/url/relative.rb +47 -0
- data/lib/protocol/url/version.rb +1 -1
- data/lib/protocol/url.rb +2 -0
- data/readme.md +13 -4
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +5 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 10d18a95d6f20f5ca2457a52a2defa8d961ecf26eb372d45bea1f2d339f3a5c6
|
|
4
|
+
data.tar.gz: f15a84b24bd971e6262306b84e271196b5c20cde76983eba4625af28b0895ddd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e1493486247a9e36b696bf62225a33888905d7e4a5a83b31357dc67d227b3a6f481844e2423befc5cc21f4c0a14aebc4ea507faa68a06a50490619e455396950
|
|
7
|
+
data.tar.gz: ee45f80e209b66b2a30d6b155188138a513e9a2c632bf1a4edc0322823e9bef209d2515babc955030c43c26c9da9ea687303a2aa91fa9704a2ac0594405d8cda
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -10,6 +10,13 @@ module Protocol
|
|
|
10
10
|
# Represents an absolute URL with scheme and/or authority.
|
|
11
11
|
# Examples: "https://example.com/path", "//cdn.example.com/lib.js", "http://localhost/"
|
|
12
12
|
class Absolute < Relative
|
|
13
|
+
# Initialize a new absolute URL.
|
|
14
|
+
#
|
|
15
|
+
# @parameter scheme [String] The URL scheme (e.g., "https", "http").
|
|
16
|
+
# @parameter authority [String] The authority component (e.g., "example.com", "user@host:port").
|
|
17
|
+
# @parameter path [String] The path component (defaults to "/").
|
|
18
|
+
# @parameter query [String, nil] The query string.
|
|
19
|
+
# @parameter fragment [String, nil] The fragment identifier.
|
|
13
20
|
def initialize(scheme, authority, path = "/", query = nil, fragment = nil)
|
|
14
21
|
@scheme = scheme
|
|
15
22
|
@authority = authority
|
|
@@ -18,13 +25,22 @@ module Protocol
|
|
|
18
25
|
super(path, query, fragment)
|
|
19
26
|
end
|
|
20
27
|
|
|
28
|
+
# @attribute [String] The URL scheme.
|
|
21
29
|
attr :scheme
|
|
30
|
+
|
|
31
|
+
# @attribute [String] The authority component.
|
|
22
32
|
attr :authority
|
|
23
33
|
|
|
34
|
+
# Check if the URL has a non-empty scheme.
|
|
35
|
+
#
|
|
36
|
+
# @returns [Boolean] True if a scheme is present and non-empty.
|
|
24
37
|
def scheme?
|
|
25
38
|
@scheme and !@scheme.empty?
|
|
26
39
|
end
|
|
27
40
|
|
|
41
|
+
# Check if the URL has a non-empty authority.
|
|
42
|
+
#
|
|
43
|
+
# @returns [Boolean] True if an authority is present and non-empty.
|
|
28
44
|
def authority?
|
|
29
45
|
@authority and !@authority.empty?
|
|
30
46
|
end
|
|
@@ -93,8 +109,6 @@ module Protocol
|
|
|
93
109
|
super(buffer)
|
|
94
110
|
end
|
|
95
111
|
|
|
96
|
-
UNSPECIFIED = Object.new
|
|
97
|
-
|
|
98
112
|
# Create a new Absolute URL with modified components.
|
|
99
113
|
#
|
|
100
114
|
# @parameter scheme [String, nil] The scheme to use (nil to remove scheme).
|
|
@@ -118,14 +132,24 @@ module Protocol
|
|
|
118
132
|
self.class.new(scheme, authority, Path.expand(@path, path, pop), query, fragment)
|
|
119
133
|
end
|
|
120
134
|
|
|
135
|
+
# Convert the URL to an array representation.
|
|
136
|
+
#
|
|
137
|
+
# @returns [Array] An array of `[scheme, authority, path, query, fragment]`.
|
|
121
138
|
def to_ary
|
|
122
139
|
[@scheme, @authority, @path, @query, @fragment]
|
|
123
140
|
end
|
|
124
141
|
|
|
142
|
+
# Compare this URL with another for sorting purposes.
|
|
143
|
+
#
|
|
144
|
+
# @parameter other [Absolute] The URL to compare with.
|
|
145
|
+
# @returns [Integer] -1, 0, or 1 based on component-wise comparison.
|
|
125
146
|
def <=>(other)
|
|
126
147
|
to_ary <=> other.to_ary
|
|
127
148
|
end
|
|
128
149
|
|
|
150
|
+
# Convert the URL to its string representation.
|
|
151
|
+
#
|
|
152
|
+
# @returns [String] The formatted absolute URL string.
|
|
129
153
|
def to_s
|
|
130
154
|
append
|
|
131
155
|
end
|
|
@@ -233,6 +233,17 @@ module Protocol
|
|
|
233
233
|
|
|
234
234
|
return parameters
|
|
235
235
|
end
|
|
236
|
+
|
|
237
|
+
# Decode an `application/x-www-form-urlencoded` string into a hash.
|
|
238
|
+
# In addition to percent encoding, this format represents spaces using `+`.
|
|
239
|
+
#
|
|
240
|
+
# @parameter string [String] The form-encoded string to decode.
|
|
241
|
+
# @parameter maximum [Integer] The maximum number of keys in a path.
|
|
242
|
+
# @parameter symbolize_keys [Boolean] Whether to symbolize keys.
|
|
243
|
+
# @returns [Hash] The decoded form values.
|
|
244
|
+
def self.decode_www_form(string, maximum = 8, symbolize_keys: false)
|
|
245
|
+
return self.decode(string.gsub("+", "%20"), maximum, symbolize_keys: symbolize_keys)
|
|
246
|
+
end
|
|
236
247
|
end
|
|
237
248
|
end
|
|
238
249
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "../encoding"
|
|
7
|
+
|
|
8
|
+
module Protocol
|
|
9
|
+
module URL
|
|
10
|
+
module FormData
|
|
11
|
+
# Builds nested form data from names and values.
|
|
12
|
+
class Nested
|
|
13
|
+
# The default maximum depth of a bracketed form name.
|
|
14
|
+
MAXIMUM_DEPTH = 8
|
|
15
|
+
|
|
16
|
+
# Initialize the nested form data.
|
|
17
|
+
# @parameter maximum_depth [Integer | Nil] The maximum depth of a bracketed form name.
|
|
18
|
+
def initialize(maximum_depth: MAXIMUM_DEPTH)
|
|
19
|
+
@maximum_depth = maximum_depth
|
|
20
|
+
@root = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Add a form data value using its bracketed name.
|
|
24
|
+
# @parameter name [String] The form data name.
|
|
25
|
+
# @parameter value [Object] The form data value.
|
|
26
|
+
# @returns [Nested] The receiver.
|
|
27
|
+
def add(name, value)
|
|
28
|
+
keys = Encoding.split(name)
|
|
29
|
+
|
|
30
|
+
if keys.empty?
|
|
31
|
+
raise ArgumentError, "Invalid form data name: #{name.inspect}!"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if @maximum_depth and keys.size > @maximum_depth
|
|
35
|
+
raise RangeError, "Form data depth exceeded limit of #{@maximum_depth}!"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Encoding.assign(keys, value, @root)
|
|
39
|
+
|
|
40
|
+
return self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Convert the arguments to a nested hash.
|
|
44
|
+
# @returns [Hash] The nested arguments.
|
|
45
|
+
def to_h
|
|
46
|
+
return @root
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "nested"
|
|
7
|
+
|
|
8
|
+
module Protocol
|
|
9
|
+
module URL
|
|
10
|
+
# @namespace
|
|
11
|
+
module FormData
|
|
12
|
+
# Incrementally parses `application/x-www-form-urlencoded` form data.
|
|
13
|
+
class Parser
|
|
14
|
+
CONTENT_TYPE = "application/x-www-form-urlencoded"
|
|
15
|
+
|
|
16
|
+
# The default maximum encoded body size.
|
|
17
|
+
MAXIMUM_TOTAL_SIZE = 2 * 1024 * 1024
|
|
18
|
+
|
|
19
|
+
# The default maximum number of form pairs.
|
|
20
|
+
MAXIMUM_PAIR_COUNT = 1024
|
|
21
|
+
|
|
22
|
+
# Initialize the form data parser.
|
|
23
|
+
# @parameter maximum_total_size [Integer | Nil] The maximum encoded body size.
|
|
24
|
+
# @parameter maximum_pair_count [Integer | Nil] The maximum number of form pairs.
|
|
25
|
+
# @parameter maximum_depth [Integer | Nil] The maximum depth of a bracketed form name.
|
|
26
|
+
def initialize(maximum_total_size: MAXIMUM_TOTAL_SIZE, maximum_pair_count: MAXIMUM_PAIR_COUNT, maximum_depth: Nested::MAXIMUM_DEPTH)
|
|
27
|
+
@maximum_total_size = maximum_total_size
|
|
28
|
+
@maximum_pair_count = maximum_pair_count
|
|
29
|
+
@maximum_depth = maximum_depth
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Parse URL-encoded form data into a nested hash.
|
|
33
|
+
#
|
|
34
|
+
# When a block is given, each decoded value is passed through the block before assignment. The value returned by the block is assigned to the result.
|
|
35
|
+
#
|
|
36
|
+
# @parameter body [Object] A readable body which yields chunks from `#read`.
|
|
37
|
+
# @yields {|name, value| ...} Each decoded form pair before assignment.
|
|
38
|
+
# @returns [Hash] The nested form data.
|
|
39
|
+
def parse(body)
|
|
40
|
+
nested = Nested.new(maximum_depth: @maximum_depth)
|
|
41
|
+
|
|
42
|
+
each(body) do |name, value|
|
|
43
|
+
value = yield(name, value) if block_given?
|
|
44
|
+
nested.add(name, value)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return nested.to_h
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Incrementally enumerate URL-encoded form data as ordered name/value pairs.
|
|
51
|
+
# @parameter body [Object] A readable body which yields chunks from `#read`.
|
|
52
|
+
# @yields {|name, value| ...} Each decoded form pair.
|
|
53
|
+
# @returns [Enumerator | Boolean] An enumerator without a block, or true when complete.
|
|
54
|
+
def each(body)
|
|
55
|
+
return to_enum(__method__, body) unless block_given?
|
|
56
|
+
|
|
57
|
+
buffer = String.new.b
|
|
58
|
+
total_size = 0
|
|
59
|
+
pair_count = 0
|
|
60
|
+
|
|
61
|
+
while chunk = body.read
|
|
62
|
+
break if chunk.empty?
|
|
63
|
+
|
|
64
|
+
total_size += chunk.bytesize
|
|
65
|
+
check_limit(:total_size, total_size, @maximum_total_size)
|
|
66
|
+
buffer << chunk
|
|
67
|
+
|
|
68
|
+
while separator = buffer.index("&")
|
|
69
|
+
assignment = buffer.slice!(0, separator + 1)
|
|
70
|
+
assignment.chop!
|
|
71
|
+
|
|
72
|
+
unless assignment.empty?
|
|
73
|
+
pair_count += 1
|
|
74
|
+
check_limit(:pair_count, pair_count, @maximum_pair_count)
|
|
75
|
+
yield_pair(assignment) {|name, value| yield name, value}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
unless buffer.empty?
|
|
81
|
+
pair_count += 1
|
|
82
|
+
check_limit(:pair_count, pair_count, @maximum_pair_count)
|
|
83
|
+
yield_pair(buffer) {|name, value| yield name, value}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
return true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def yield_pair(assignment)
|
|
92
|
+
name, value = assignment.split("=", 2)
|
|
93
|
+
|
|
94
|
+
if value
|
|
95
|
+
value = decode_component(value)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
yield decode_component(name), value
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def decode_component(component)
|
|
102
|
+
return Encoding.unescape(component.tr("+", " "))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def check_limit(name, value, maximum)
|
|
106
|
+
if maximum and value > maximum
|
|
107
|
+
raise RangeError, "Form data #{name} exceeded limit of #{maximum}!"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -17,6 +17,35 @@ module Protocol
|
|
|
17
17
|
class Reference < Relative
|
|
18
18
|
include Comparable
|
|
19
19
|
|
|
20
|
+
# Coerce a value into a {Reference} instance.
|
|
21
|
+
#
|
|
22
|
+
# This method provides flexible conversion from various types into a {Reference}.
|
|
23
|
+
# When given a {String}, it parses the URL-encoded path, query, and fragment components
|
|
24
|
+
# and unescapes them for internal storage. When given a {Relative}, it converts the
|
|
25
|
+
# encoded values to unescaped form suitable for {Reference} instances.
|
|
26
|
+
#
|
|
27
|
+
# @parameter value [String | Relative | Nil] The value to coerce.
|
|
28
|
+
# @parameter parameters [Hash | Nil] Optional user-supplied parameters to append to the query string.
|
|
29
|
+
#
|
|
30
|
+
# @returns [Reference | Nil] A new reference instance, or `nil` if the input is `nil`.
|
|
31
|
+
#
|
|
32
|
+
# @raises [ArgumentError] If the string contains whitespace or control characters.
|
|
33
|
+
# @raises [ArgumentError] If the value cannot be coerced to a {Reference}.
|
|
34
|
+
#
|
|
35
|
+
# @example Coerce a string with path, query, and fragment.
|
|
36
|
+
# reference = Reference["/search?q=ruby#results"]
|
|
37
|
+
# reference.path # => "/search"
|
|
38
|
+
# reference.query # => "q=ruby"
|
|
39
|
+
# reference.fragment # => "results"
|
|
40
|
+
#
|
|
41
|
+
# @example Coerce with additional parameters.
|
|
42
|
+
# reference = Reference["/search", {"limit" => "10"}]
|
|
43
|
+
# reference.to_s # => "/search?limit=10"
|
|
44
|
+
#
|
|
45
|
+
# @example Coerce a Relative instance.
|
|
46
|
+
# relative = Relative.new("/path%20with%20spaces", nil, "top")
|
|
47
|
+
# reference = Reference[relative]
|
|
48
|
+
# reference.path # => "/path with spaces"
|
|
20
49
|
def self.[](value, parameters = nil)
|
|
21
50
|
case value
|
|
22
51
|
when String
|
|
@@ -25,7 +54,7 @@ module Protocol
|
|
|
25
54
|
query = match[:query]
|
|
26
55
|
fragment = match[:fragment]
|
|
27
56
|
|
|
28
|
-
# Unescape path and fragment for user-friendly internal storage
|
|
57
|
+
# Unescape path and fragment for user-friendly internal storage:
|
|
29
58
|
# Query strings are kept as-is since they contain = and & syntax
|
|
30
59
|
path = Encoding.unescape(path) if path && !path.empty?
|
|
31
60
|
fragment = Encoding.unescape(fragment) if fragment
|
|
@@ -35,7 +64,7 @@ module Protocol
|
|
|
35
64
|
raise ArgumentError, "Invalid URL (contains whitespace or control characters): #{value.inspect}"
|
|
36
65
|
end
|
|
37
66
|
when Relative
|
|
38
|
-
# Relative stores encoded values, so we need to unescape them for Reference
|
|
67
|
+
# Relative stores encoded values, so we need to unescape them for Reference:
|
|
39
68
|
path = value.path
|
|
40
69
|
fragment = value.fragment
|
|
41
70
|
|
|
@@ -12,16 +12,29 @@ module Protocol
|
|
|
12
12
|
class Relative
|
|
13
13
|
include Comparable
|
|
14
14
|
|
|
15
|
+
# Initialize a new relative URL.
|
|
16
|
+
#
|
|
17
|
+
# @parameter path [String] The path component.
|
|
18
|
+
# @parameter query [String, nil] The query string.
|
|
19
|
+
# @parameter fragment [String, nil] The fragment identifier.
|
|
15
20
|
def initialize(path, query = nil, fragment = nil)
|
|
16
21
|
@path = path.to_s
|
|
17
22
|
@query = query
|
|
18
23
|
@fragment = fragment
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# @attribute [String] The path component of the URL.
|
|
21
27
|
attr :path
|
|
28
|
+
|
|
29
|
+
# @attribute [String, nil] The query string component.
|
|
22
30
|
attr :query
|
|
31
|
+
|
|
32
|
+
# @attribute [String, nil] The fragment identifier.
|
|
23
33
|
attr :fragment
|
|
24
34
|
|
|
35
|
+
# Convert the URL path to a local filesystem path.
|
|
36
|
+
#
|
|
37
|
+
# @returns [String] The local filesystem path.
|
|
25
38
|
def to_local_path
|
|
26
39
|
Path.to_local_path(@path)
|
|
27
40
|
end
|
|
@@ -131,42 +144,76 @@ module Protocol
|
|
|
131
144
|
return buffer
|
|
132
145
|
end
|
|
133
146
|
|
|
147
|
+
# Convert the URL to an array representation.
|
|
148
|
+
#
|
|
149
|
+
# @returns [Array] An array of `[path, query, fragment]`.
|
|
134
150
|
def to_ary
|
|
135
151
|
[@path, @query, @fragment]
|
|
136
152
|
end
|
|
137
153
|
|
|
154
|
+
# Compute a hash value for the URL based on its components.
|
|
155
|
+
#
|
|
156
|
+
# @returns [Integer] The hash value.
|
|
138
157
|
def hash
|
|
139
158
|
to_ary.hash
|
|
140
159
|
end
|
|
141
160
|
|
|
161
|
+
# Check if this URL is equal to another URL by comparing components.
|
|
162
|
+
#
|
|
163
|
+
# @parameter other [Relative] The URL to compare with.
|
|
164
|
+
# @returns [Boolean] True if the URLs have identical components.
|
|
142
165
|
def equal?(other)
|
|
143
166
|
to_ary == other.to_ary
|
|
144
167
|
end
|
|
145
168
|
|
|
169
|
+
# Compare this URL with another for sorting purposes.
|
|
170
|
+
#
|
|
171
|
+
# @parameter other [Relative] The URL to compare with.
|
|
172
|
+
# @returns [Integer] -1, 0, or 1 based on component-wise comparison.
|
|
146
173
|
def <=>(other)
|
|
147
174
|
to_ary <=> other.to_ary
|
|
148
175
|
end
|
|
149
176
|
|
|
177
|
+
# Check structural equality by comparing components.
|
|
178
|
+
#
|
|
179
|
+
# @parameter other [Relative] The URL to compare with.
|
|
180
|
+
# @returns [Boolean] True if the URLs have identical components.
|
|
150
181
|
def ==(other)
|
|
151
182
|
to_ary == other.to_ary
|
|
152
183
|
end
|
|
153
184
|
|
|
185
|
+
# Check string equality, useful for case statements.
|
|
186
|
+
#
|
|
187
|
+
# @parameter other [String, Relative] The value to compare with.
|
|
188
|
+
# @returns [Boolean] True if the string representations match.
|
|
154
189
|
def ===(other)
|
|
155
190
|
to_s === other
|
|
156
191
|
end
|
|
157
192
|
|
|
193
|
+
# Convert the URL to its string representation.
|
|
194
|
+
#
|
|
195
|
+
# @returns [String] The formatted URL string.
|
|
158
196
|
def to_s
|
|
159
197
|
append
|
|
160
198
|
end
|
|
161
199
|
|
|
200
|
+
# Convert the URL to a JSON-compatible representation.
|
|
201
|
+
#
|
|
202
|
+
# @returns [String] The URL as a string.
|
|
162
203
|
def as_json(...)
|
|
163
204
|
to_s
|
|
164
205
|
end
|
|
165
206
|
|
|
207
|
+
# Convert the URL to JSON.
|
|
208
|
+
#
|
|
209
|
+
# @returns [String] The JSON-encoded URL.
|
|
166
210
|
def to_json(...)
|
|
167
211
|
as_json.to_json(...)
|
|
168
212
|
end
|
|
169
213
|
|
|
214
|
+
# Generate a human-readable representation for debugging.
|
|
215
|
+
#
|
|
216
|
+
# @returns [String] A string like `#<Protocol::URL::Relative /path?query#fragment>`.
|
|
170
217
|
def inspect
|
|
171
218
|
"#<#{self.class} #{to_s}>"
|
|
172
219
|
end
|
data/lib/protocol/url/version.rb
CHANGED
data/lib/protocol/url.rb
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
require_relative "url/version"
|
|
7
7
|
require_relative "url/pattern"
|
|
8
8
|
require_relative "url/encoding"
|
|
9
|
+
require_relative "url/form_data/nested"
|
|
10
|
+
require_relative "url/form_data/parser"
|
|
9
11
|
require_relative "url/reference"
|
|
10
12
|
require_relative "url/relative"
|
|
11
13
|
require_relative "url/absolute"
|
data/readme.md
CHANGED
|
@@ -6,11 +6,11 @@ Provides abstractions for working with URLs.
|
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
|
-
Please see the [project documentation](https://github.
|
|
9
|
+
Please see the [project documentation](https://socketry.github.io/protocol-url/) for more details.
|
|
10
10
|
|
|
11
|
-
- [Getting Started](https://github.
|
|
11
|
+
- [Getting Started](https://socketry.github.io/protocol-url/guides/getting-started/index) - This guide explains how to get started with `protocol-url` for parsing, manipulating, and constructing URLs in Ruby.
|
|
12
12
|
|
|
13
|
-
- [Working with References](https://github.
|
|
13
|
+
- [Working with References](https://socketry.github.io/protocol-url/guides/working-with-references/index) - This guide explains how to use <code class="language-ruby">Protocol::URL::Reference</code> for managing URLs with query parameters and fragments.
|
|
14
14
|
|
|
15
15
|
## Contributing
|
|
16
16
|
|
|
@@ -32,7 +32,16 @@ This project is best served by a collaborative and respectful environment. Treat
|
|
|
32
32
|
|
|
33
33
|
## Releases
|
|
34
34
|
|
|
35
|
-
Please see the [project releases](https://github.
|
|
35
|
+
Please see the [project releases](https://socketry.github.io/protocol-url/releases/index) for all releases.
|
|
36
|
+
|
|
37
|
+
### v0.6.0
|
|
38
|
+
|
|
39
|
+
- Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
|
|
40
|
+
- Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
|
|
41
|
+
|
|
42
|
+
### v0.5.0
|
|
43
|
+
|
|
44
|
+
- Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
|
|
36
45
|
|
|
37
46
|
### v0.4.0
|
|
38
47
|
|
data/releases.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.6.0
|
|
4
|
+
|
|
5
|
+
- Add `Protocol::URL::FormData::Parser` for incremental, limited parsing of `application/x-www-form-urlencoded` form data.
|
|
6
|
+
- Add `Protocol::URL::FormData::Nested` for consistently building nested form data while preserving absent and empty values.
|
|
7
|
+
|
|
8
|
+
## v0.5.0
|
|
9
|
+
|
|
10
|
+
- Add `Protocol::URL::Encoding.decode_www_form` for decoding HTML form data where `+` represents a space.
|
|
11
|
+
|
|
3
12
|
## v0.4.0
|
|
4
13
|
|
|
5
14
|
- Add comparison methods to `Protocol::URL::Relative` (and by inheritance to `Protocol::URL::Absolute`):
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-url
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -45,6 +45,8 @@ files:
|
|
|
45
45
|
- lib/protocol/url.rb
|
|
46
46
|
- lib/protocol/url/absolute.rb
|
|
47
47
|
- lib/protocol/url/encoding.rb
|
|
48
|
+
- lib/protocol/url/form_data/nested.rb
|
|
49
|
+
- lib/protocol/url/form_data/parser.rb
|
|
48
50
|
- lib/protocol/url/path.rb
|
|
49
51
|
- lib/protocol/url/pattern.rb
|
|
50
52
|
- lib/protocol/url/reference.rb
|
|
@@ -58,6 +60,7 @@ licenses:
|
|
|
58
60
|
- MIT
|
|
59
61
|
metadata:
|
|
60
62
|
source_code_uri: https://github.com/socketry/protocol-url.git
|
|
63
|
+
documentation_uri: https://socketry.github.io/protocol-url/
|
|
61
64
|
rdoc_options: []
|
|
62
65
|
require_paths:
|
|
63
66
|
- lib
|
|
@@ -72,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
75
|
- !ruby/object:Gem::Version
|
|
73
76
|
version: '0'
|
|
74
77
|
requirements: []
|
|
75
|
-
rubygems_version:
|
|
78
|
+
rubygems_version: 4.0.10
|
|
76
79
|
specification_version: 4
|
|
77
80
|
summary: Provides abstractions for working with URLs.
|
|
78
81
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|