iri 0.10.0 → 0.11.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
- data/.0pdd.yml +2 -21
- data/.github/workflows/actionlint.yml +4 -22
- data/.github/workflows/codecov.yml +5 -22
- data/.github/workflows/copyrights.yml +5 -22
- data/.github/workflows/markdown-lint.yml +5 -22
- data/.github/workflows/pdd.yml +4 -21
- data/.github/workflows/rake.yml +4 -23
- data/.github/workflows/reuse.yml +19 -0
- data/.github/workflows/typos.yml +19 -0
- data/.github/workflows/xcop.yml +4 -21
- data/.github/workflows/yamllint.yml +4 -21
- data/.gitignore +7 -2
- data/.rubocop.yml +5 -24
- data/.rultor.yml +4 -22
- data/Gemfile +13 -27
- data/Gemfile.lock +54 -37
- data/LICENSES/MIT.txt +21 -0
- data/README.md +9 -9
- data/REUSE.toml +36 -0
- data/Rakefile +8 -35
- data/iri.gemspec +3 -22
- data/lib/iri.rb +229 -98
- data/test/test__helper.rb +25 -20
- data/test/test_iri.rb +84 -23
- metadata +7 -6
data/lib/iri.rb
CHANGED
@@ -1,76 +1,78 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# (
|
4
|
-
#
|
5
|
-
# Copyright (c) 2019-2025 Yegor Bugayenko
|
6
|
-
#
|
7
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
9
|
-
# in the Software without restriction, including without limitation the rights
|
10
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
-
# copies of the Software, and to permit persons to whom the Software is
|
12
|
-
# furnished to do so, subject to the following conditions:
|
13
|
-
#
|
14
|
-
# The above copyright notice and this permission notice shall be included in all
|
15
|
-
# copies or substantial portions of the Software.
|
16
|
-
#
|
17
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
24
5
|
|
25
6
|
require 'uri'
|
26
7
|
require 'cgi'
|
27
8
|
|
28
|
-
#
|
9
|
+
# Iri is a simple, immutable URI builder with a fluent interface.
|
29
10
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# .del(:q) // remove this query parameter
|
34
|
-
# .del('limit') // remove this one too
|
35
|
-
# .over(q: 'books about tennis', limit: 10) // replace these params
|
36
|
-
# .scheme('https')
|
37
|
-
# .host('localhost')
|
38
|
-
# .port('443')
|
39
|
-
# .to_s
|
11
|
+
# The Iri class provides methods to manipulate different parts of a URI,
|
12
|
+
# including the scheme, host, port, path, query parameters, and fragment.
|
13
|
+
# Each method returns a new Iri instance, maintaining immutability.
|
40
14
|
#
|
41
|
-
#
|
15
|
+
# @example Creating and manipulating a URI
|
16
|
+
# require 'iri'
|
17
|
+
# url = Iri.new('http://google.com/')
|
18
|
+
# .add(q: 'books about OOP', limit: 50)
|
19
|
+
# .del(:q) # remove this query parameter
|
20
|
+
# .del('limit') # remove this one too
|
21
|
+
# .over(q: 'books about tennis', limit: 10) # replace these params
|
22
|
+
# .scheme('https')
|
23
|
+
# .host('localhost')
|
24
|
+
# .port('443')
|
25
|
+
# .to_s
|
26
|
+
#
|
27
|
+
# @example Using the local option
|
28
|
+
# Iri.new('/path?foo=bar', local: true).to_s # => "/path?foo=bar"
|
29
|
+
#
|
30
|
+
# @example Using the safe mode
|
31
|
+
# Iri.new('invalid://uri', safe: true).to_s # => "/" (no exception thrown)
|
32
|
+
# Iri.new('invalid://uri', safe: false) # => raises Iri::InvalidURI
|
33
|
+
#
|
34
|
+
# For more information read the
|
42
35
|
# {README}[https://github.com/yegor256/iri/blob/master/README.md] file.
|
43
36
|
#
|
44
37
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
45
38
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
46
39
|
# License:: MIT
|
47
40
|
class Iri
|
48
|
-
#
|
41
|
+
# Exception raised when a URI is not valid and safe mode is disabled.
|
49
42
|
class InvalidURI < StandardError; end
|
50
43
|
|
51
|
-
#
|
44
|
+
# Exception raised when arguments to .add(), .over(), or .del() are not valid Hashes.
|
52
45
|
class InvalidArguments < StandardError; end
|
53
46
|
|
54
|
-
#
|
47
|
+
# Creates a new Iri object for URI manipulation.
|
55
48
|
#
|
56
|
-
# You can even ignore the argument, which will produce an empty URI.
|
49
|
+
# You can even ignore the argument, which will produce an empty URI ("/").
|
57
50
|
#
|
58
51
|
# By default, this class will never throw any exceptions, even if your URI
|
59
|
-
# is not valid. It will just assume that the URI is"/". However,
|
60
|
-
# you can turn this mode off
|
52
|
+
# is not valid. It will just assume that the URI is "/". However,
|
53
|
+
# you can turn this safe mode off by specifying safe as FALSE, which will
|
54
|
+
# cause InvalidURI to be raised if the URI is malformed.
|
55
|
+
#
|
56
|
+
# The local parameter can be used if you only want to work with the path,
|
57
|
+
# query, and fragment portions of a URI, without the scheme, host, and port.
|
61
58
|
#
|
62
|
-
# @param [String] uri URI
|
63
|
-
# @param [Boolean] local
|
64
|
-
# @param [Boolean] safe
|
59
|
+
# @param [String] uri URI string to parse
|
60
|
+
# @param [Boolean] local When true, ignores scheme, host and port parts
|
61
|
+
# @param [Boolean] safe When true, prevents InvalidURI exceptions
|
62
|
+
# @raise [InvalidURI] If the URI is malformed and safe is false
|
65
63
|
def initialize(uri = '', local: false, safe: true)
|
64
|
+
raise ArgumentError, "The uri can't be nil" if uri.nil?
|
66
65
|
@uri = uri
|
67
66
|
@local = local
|
68
67
|
@safe = safe
|
69
68
|
end
|
70
69
|
|
71
|
-
#
|
70
|
+
# Converts the Iri object to a string representation of the URI.
|
72
71
|
#
|
73
|
-
#
|
72
|
+
# When local mode is enabled, only the path, query, and fragment parts are included.
|
73
|
+
# Otherwise, the full URI including scheme, host, and port is returned.
|
74
|
+
#
|
75
|
+
# @return [String] String representation of the URI
|
74
76
|
def to_s
|
75
77
|
u = the_uri
|
76
78
|
if @local
|
@@ -84,44 +86,60 @@ class Iri
|
|
84
86
|
end
|
85
87
|
end
|
86
88
|
|
87
|
-
#
|
89
|
+
# Returns a string representation of the Iri object for inspection purposes.
|
90
|
+
#
|
91
|
+
# This method is used when the object is displayed in irb/console or with puts/p.
|
88
92
|
#
|
89
|
-
# @return [String]
|
93
|
+
# @return [String] String representation for inspection
|
90
94
|
def inspect
|
91
95
|
@uri.to_s.inspect
|
92
96
|
end
|
93
97
|
|
94
|
-
#
|
98
|
+
# Converts the Iri object to a Ruby standard library URI object.
|
95
99
|
#
|
96
|
-
# @return [
|
100
|
+
# @return [URI] A cloned URI object from the underlying URI
|
97
101
|
def to_uri
|
98
102
|
the_uri.clone
|
99
103
|
end
|
100
104
|
|
101
|
-
#
|
102
|
-
# only the local address, for example, converting "https://google.com/foo"
|
103
|
-
# into "/foo".
|
105
|
+
# Creates a new Iri object with only the local parts of the URI.
|
104
106
|
#
|
105
|
-
#
|
107
|
+
# Removes the host, the port, and the scheme, returning only the local address.
|
108
|
+
# For example, converting "https://google.com/foo" into "/foo".
|
109
|
+
# The path, query string, and fragment are preserved.
|
110
|
+
#
|
111
|
+
# @return [Iri] A new Iri object with local:true and the same URI
|
112
|
+
# @see #initialize
|
106
113
|
def to_local
|
107
114
|
Iri.new(@uri, local: true, safe: @safe)
|
108
115
|
end
|
109
116
|
|
110
|
-
#
|
117
|
+
# Adds query parameters to the URI.
|
118
|
+
#
|
119
|
+
# This method appends query parameters to existing ones. If a parameter with the same
|
120
|
+
# name already exists, both values will be present in the resulting URI.
|
111
121
|
#
|
112
|
-
#
|
122
|
+
# @example Adding query parameters
|
123
|
+
# Iri.new('https://google.com').add(q: 'test', limit: 10)
|
124
|
+
# # => "https://google.com?q=test&limit=10"
|
113
125
|
#
|
114
|
-
#
|
126
|
+
# @example Adding parameters with the same name
|
127
|
+
# Iri.new('https://google.com?q=foo').add(q: 'bar')
|
128
|
+
# # => "https://google.com?q=foo&q=bar"
|
115
129
|
#
|
116
|
-
# You can
|
117
|
-
# URI, even if their names are the same. In order to make sure you have
|
118
|
-
# only one instance of a query argument, use +del+ first:
|
130
|
+
# You can ensure only one instance of a parameter by using +del+ first:
|
119
131
|
#
|
120
|
-
#
|
132
|
+
# @example Replacing a parameter by deleting it first
|
133
|
+
# Iri.new('https://google.com?q=foo').del(:q).add(q: 'test')
|
134
|
+
# # => "https://google.com?q=test"
|
121
135
|
#
|
122
|
-
# @param [Hash] hash Hash of names/values to
|
123
|
-
# @return [Iri] A new
|
136
|
+
# @param [Hash] hash Hash of parameter names/values to add to the query part
|
137
|
+
# @return [Iri] A new Iri instance
|
138
|
+
# @raise [InvalidArguments] If the argument is not a Hash
|
139
|
+
# @see #del
|
140
|
+
# @see #over
|
124
141
|
def add(hash)
|
142
|
+
raise ArgumentError, "The hash can't be nil" if hash.nil?
|
125
143
|
raise InvalidArguments unless hash.is_a?(Hash)
|
126
144
|
modify_query do |params|
|
127
145
|
hash.each do |k, v|
|
@@ -130,15 +148,24 @@ class Iri
|
|
130
148
|
end
|
131
149
|
end
|
132
150
|
end
|
151
|
+
alias with add
|
133
152
|
|
134
|
-
#
|
153
|
+
# Deletes query parameters from the URI.
|
135
154
|
#
|
136
|
-
#
|
155
|
+
# This method removes all instances of the specified parameters from the query string.
|
137
156
|
#
|
138
|
-
#
|
157
|
+
# @example Deleting a query parameter
|
158
|
+
# Iri.new('https://google.com?q=test&limit=10').del(:q)
|
159
|
+
# # => "https://google.com?limit=10"
|
139
160
|
#
|
140
|
-
# @
|
141
|
-
#
|
161
|
+
# @example Deleting multiple parameters
|
162
|
+
# Iri.new('https://google.com?q=test&limit=10&sort=asc').del(:q, :limit)
|
163
|
+
# # => "https://google.com?sort=asc"
|
164
|
+
#
|
165
|
+
# @param [Array<Symbol, String>] keys List of parameter names to delete
|
166
|
+
# @return [Iri] A new Iri instance
|
167
|
+
# @see #add
|
168
|
+
# @see #over
|
142
169
|
def del(*keys)
|
143
170
|
modify_query do |params|
|
144
171
|
keys.each do |k|
|
@@ -146,94 +173,164 @@ class Iri
|
|
146
173
|
end
|
147
174
|
end
|
148
175
|
end
|
176
|
+
alias without del
|
149
177
|
|
150
|
-
#
|
178
|
+
# Replaces query parameters in the URI.
|
179
|
+
#
|
180
|
+
# Unlike #add, this method replaces any existing parameters with the same name
|
181
|
+
# rather than adding additional instances. If a parameter doesn't exist,
|
182
|
+
# it will be added.
|
183
|
+
#
|
184
|
+
# @example Replacing a query parameter
|
185
|
+
# Iri.new('https://google.com?q=test').over(q: 'hey you!')
|
186
|
+
# # => "https://google.com?q=hey+you%21"
|
151
187
|
#
|
152
|
-
#
|
188
|
+
# @example Replacing multiple parameters
|
189
|
+
# Iri.new('https://google.com?q=test&limit=5').over(q: 'books', limit: 10)
|
190
|
+
# # => "https://google.com?q=books&limit=10"
|
153
191
|
#
|
154
|
-
# @param [Hash] hash Hash of names/values to
|
155
|
-
# @return [Iri] A new
|
192
|
+
# @param [Hash] hash Hash of parameter names/values to replace in the query part
|
193
|
+
# @return [Iri] A new Iri instance
|
194
|
+
# @raise [InvalidArguments] If the argument is not a Hash
|
195
|
+
# @see #add
|
196
|
+
# @see #del
|
156
197
|
def over(hash)
|
198
|
+
raise ArgumentError, "The hash can't be nil" if hash.nil?
|
157
199
|
raise InvalidArguments unless hash.is_a?(Hash)
|
158
200
|
modify_query do |params|
|
159
201
|
hash.each do |k, v|
|
160
|
-
params[k.to_s] = [] unless params[k]
|
202
|
+
params[k.to_s] = [] unless params[k.to_s]
|
161
203
|
params[k.to_s] = [v]
|
162
204
|
end
|
163
205
|
end
|
164
206
|
end
|
165
207
|
|
166
|
-
#
|
208
|
+
# Replaces the scheme part of the URI.
|
209
|
+
#
|
210
|
+
# @example Changing the scheme
|
211
|
+
# Iri.new('http://google.com').scheme('https')
|
212
|
+
# # => "https://google.com"
|
167
213
|
#
|
168
214
|
# @param [String] val New scheme to set, like "https" or "http"
|
169
|
-
# @return [Iri] A new
|
215
|
+
# @return [Iri] A new Iri instance
|
216
|
+
# @see #host
|
217
|
+
# @see #port
|
170
218
|
def scheme(val)
|
219
|
+
raise ArgumentError, "The scheme can't be nil" if val.nil?
|
171
220
|
modify do |c|
|
172
221
|
c.scheme = val
|
173
222
|
end
|
174
223
|
end
|
175
224
|
|
176
|
-
#
|
225
|
+
# Replaces the host part of the URI.
|
177
226
|
#
|
178
|
-
# @
|
179
|
-
#
|
227
|
+
# @example Changing the host
|
228
|
+
# Iri.new('https://google.com').host('example.com')
|
229
|
+
# # => "https://example.com"
|
230
|
+
#
|
231
|
+
# @param [String] val New host to set, like "example.com" or "192.168.0.1"
|
232
|
+
# @return [Iri] A new Iri instance
|
233
|
+
# @see #scheme
|
234
|
+
# @see #port
|
180
235
|
def host(val)
|
236
|
+
raise ArgumentError, "The host can't be nil" if val.nil?
|
181
237
|
modify do |c|
|
182
238
|
c.host = val
|
183
239
|
end
|
184
240
|
end
|
185
241
|
|
186
|
-
#
|
242
|
+
# Replaces the port part of the URI.
|
243
|
+
#
|
244
|
+
# @example Changing the port
|
245
|
+
# Iri.new('https://example.com').port('8443')
|
246
|
+
# # => "https://example.com:8443"
|
187
247
|
#
|
188
248
|
# @param [String] val New TCP port to set, like "8080" or "443"
|
189
|
-
# @return [Iri] A new
|
249
|
+
# @return [Iri] A new Iri instance
|
250
|
+
# @see #scheme
|
251
|
+
# @see #host
|
190
252
|
def port(val)
|
253
|
+
raise ArgumentError, "The port can't be nil" if val.nil?
|
191
254
|
modify do |c|
|
192
255
|
c.port = val
|
193
256
|
end
|
194
257
|
end
|
195
258
|
|
196
|
-
#
|
259
|
+
# Replaces the path part of the URI.
|
260
|
+
#
|
261
|
+
# @example Changing the path
|
262
|
+
# Iri.new('https://example.com/foo').path('/bar/baz')
|
263
|
+
# # => "https://example.com/bar/baz"
|
197
264
|
#
|
198
265
|
# @param [String] val New path to set, like "/foo/bar"
|
199
|
-
# @return [Iri] A new
|
266
|
+
# @return [Iri] A new Iri instance
|
267
|
+
# @see #query
|
268
|
+
# @see #fragment
|
200
269
|
def path(val)
|
270
|
+
raise ArgumentError, "The path can't be nil" if val.nil?
|
201
271
|
modify do |c|
|
202
272
|
c.path = val
|
203
273
|
end
|
204
274
|
end
|
205
275
|
|
206
|
-
#
|
276
|
+
# Replaces the fragment part of the URI (the part after #).
|
277
|
+
#
|
278
|
+
# @example Setting a fragment
|
279
|
+
# Iri.new('https://example.com/page').fragment('section2')
|
280
|
+
# # => "https://example.com/page#section2"
|
207
281
|
#
|
208
|
-
# @param [String] val New fragment to set, like "
|
209
|
-
# @return [Iri] A new
|
282
|
+
# @param [String] val New fragment to set, like "section2"
|
283
|
+
# @return [Iri] A new Iri instance
|
284
|
+
# @see #path
|
285
|
+
# @see #query
|
210
286
|
def fragment(val)
|
287
|
+
raise ArgumentError, "The fragment can't be nil" if val.nil?
|
211
288
|
modify do |c|
|
212
289
|
c.fragment = val.to_s
|
213
290
|
end
|
214
291
|
end
|
215
292
|
|
216
|
-
#
|
293
|
+
# Replaces the entire query part of the URI.
|
294
|
+
#
|
295
|
+
# Use this method to completely replace the query string. For modifying
|
296
|
+
# individual parameters, see #add, #del, and #over.
|
297
|
+
#
|
298
|
+
# @example Setting a query string
|
299
|
+
# Iri.new('https://example.com/search').query('q=ruby&limit=10')
|
300
|
+
# # => "https://example.com/search?q=ruby&limit=10"
|
217
301
|
#
|
218
|
-
# @param [String] val New query to set, like "a=1&b=2"
|
219
|
-
# @return [Iri] A new
|
302
|
+
# @param [String] val New query string to set, like "a=1&b=2"
|
303
|
+
# @return [Iri] A new Iri instance
|
304
|
+
# @see #add
|
305
|
+
# @see #del
|
306
|
+
# @see #over
|
220
307
|
def query(val)
|
308
|
+
raise ArgumentError, "The query can't be nil" if val.nil?
|
221
309
|
modify do |c|
|
222
310
|
c.query = val
|
223
311
|
end
|
224
312
|
end
|
225
313
|
|
226
|
-
#
|
314
|
+
# Removes the entire path, query, and fragment parts and sets a new path.
|
227
315
|
#
|
228
|
-
#
|
316
|
+
# This method is useful for "cutting off" everything after the host:port
|
317
|
+
# and setting a new path, effectively removing query string and fragment.
|
229
318
|
#
|
230
|
-
#
|
319
|
+
# @example Cutting off path/query/fragment and setting a new path
|
320
|
+
# Iri.new('https://google.com/a/b?q=test').cut('/hello')
|
321
|
+
# # => "https://google.com/hello"
|
231
322
|
#
|
232
|
-
#
|
323
|
+
# @example Resetting to root path
|
324
|
+
# Iri.new('https://google.com/a/b?q=test#section2').cut()
|
325
|
+
# # => "https://google.com/"
|
233
326
|
#
|
234
|
-
# @param [String] path New path to set,
|
235
|
-
# @return [Iri] A new
|
327
|
+
# @param [String] path New path to set, defaults to "/"
|
328
|
+
# @return [Iri] A new Iri instance
|
329
|
+
# @see #path
|
330
|
+
# @see #query
|
331
|
+
# @see #fragment
|
236
332
|
def cut(path = '/')
|
333
|
+
raise ArgumentError, "The path can't be nil" if path.nil?
|
237
334
|
modify do |c|
|
238
335
|
c.query = nil
|
239
336
|
c.path = path
|
@@ -241,17 +338,28 @@ class Iri
|
|
241
338
|
end
|
242
339
|
end
|
243
340
|
|
244
|
-
#
|
341
|
+
# Appends a new segment to the existing path.
|
245
342
|
#
|
246
|
-
#
|
343
|
+
# This method adds a new segment to the existing path, automatically handling
|
344
|
+
# the slash between segments and URL encoding the new segment.
|
247
345
|
#
|
248
|
-
#
|
346
|
+
# @example Appending a path segment
|
347
|
+
# Iri.new('https://example.com/a/b?q=test').append('hello')
|
348
|
+
# # => "https://example.com/a/b/hello?q=test"
|
249
349
|
#
|
250
|
-
#
|
350
|
+
# @example Appending to a path with a trailing slash
|
351
|
+
# Iri.new('https://example.com/a/').append('hello')
|
352
|
+
# # => "https://example.com/a/hello?q=test"
|
251
353
|
#
|
252
|
-
# @
|
253
|
-
#
|
354
|
+
# @example Appending a segment that needs URL encoding
|
355
|
+
# Iri.new('https://example.com/docs').append('section 1')
|
356
|
+
# # => "https://example.com/docs/section%201"
|
357
|
+
#
|
358
|
+
# @param [String, #to_s] part New segment to add to the existing path
|
359
|
+
# @return [Iri] A new Iri instance
|
360
|
+
# @see #path
|
254
361
|
def append(part)
|
362
|
+
raise ArgumentError, "The part can't be nil" if part.nil?
|
255
363
|
modify do |c|
|
256
364
|
tail = (c.path.end_with?('/') ? '' : '/') + CGI.escape(part.to_s)
|
257
365
|
c.path = c.path + tail
|
@@ -260,6 +368,14 @@ class Iri
|
|
260
368
|
|
261
369
|
private
|
262
370
|
|
371
|
+
# Parses the URI string into a URI object.
|
372
|
+
#
|
373
|
+
# This method handles the safe mode by catching and handling invalid URI errors.
|
374
|
+
# When safe mode is enabled (default), invalid URIs will return the root path URI "/"
|
375
|
+
# instead of raising an exception.
|
376
|
+
#
|
377
|
+
# @return [URI] The parsed URI object
|
378
|
+
# @raise [InvalidURI] If the URI is invalid and safe mode is disabled
|
263
379
|
def the_uri
|
264
380
|
@the_uri ||= URI(@uri)
|
265
381
|
rescue URI::InvalidURIError => e
|
@@ -267,12 +383,27 @@ class Iri
|
|
267
383
|
@the_uri = URI('/')
|
268
384
|
end
|
269
385
|
|
386
|
+
# Creates a new Iri object after modifying the underlying URI.
|
387
|
+
#
|
388
|
+
# This helper method clones the current URI, yields it to a block for modification,
|
389
|
+
# and then creates a new Iri object with the modified URI, preserving the local and safe flags.
|
390
|
+
#
|
391
|
+
# @yield [URI] The cloned URI object for modification
|
392
|
+
# @return [Iri] A new Iri instance with the modified URI
|
270
393
|
def modify
|
271
394
|
c = the_uri.clone
|
272
395
|
yield c
|
273
396
|
Iri.new(c, local: @local, safe: @safe)
|
274
397
|
end
|
275
398
|
|
399
|
+
# Creates a new Iri object after modifying the query parameters.
|
400
|
+
#
|
401
|
+
# This helper method parses the current query string into a hash of parameter names
|
402
|
+
# to arrays of values, yields this hash for modification, and then encodes it back
|
403
|
+
# into a query string. It uses the modify method to create a new Iri object.
|
404
|
+
#
|
405
|
+
# @yield [Hash] The parsed query parameters for modification
|
406
|
+
# @return [Iri] A new Iri instance with the modified query string
|
276
407
|
def modify_query
|
277
408
|
modify do |c|
|
278
409
|
params = CGI.parse(the_uri.query || '').map do |p, a|
|
data/test/test__helper.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2019-2025 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
$stdout.sync = true
|
24
7
|
|
25
8
|
require 'simplecov'
|
26
|
-
|
9
|
+
require 'simplecov-cobertura'
|
10
|
+
unless SimpleCov.running
|
11
|
+
SimpleCov.command_name('test')
|
12
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
13
|
+
[
|
14
|
+
SimpleCov::Formatter::HTMLFormatter,
|
15
|
+
SimpleCov::Formatter::CoberturaFormatter
|
16
|
+
]
|
17
|
+
)
|
18
|
+
SimpleCov.minimum_coverage 100
|
19
|
+
SimpleCov.minimum_coverage_by_file 100
|
20
|
+
SimpleCov.start do
|
21
|
+
add_filter 'test/'
|
22
|
+
add_filter 'vendor/'
|
23
|
+
add_filter 'target/'
|
24
|
+
track_files 'lib/**/*.rb'
|
25
|
+
track_files '*.rb'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'minitest/autorun'
|
30
|
+
require 'minitest/reporters'
|
31
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
data/test/test_iri.rb
CHANGED
@@ -1,28 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# (
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
9
|
-
# in the Software without restriction, including without limitation the rights
|
10
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
-
# copies of the Software, and to permit persons to whom the Software is
|
12
|
-
# furnished to do so, subject to the following conditions:
|
13
|
-
#
|
14
|
-
# The above copyright notice and this permission notice shall be included in all
|
15
|
-
# copies or substantial portions of the Software.
|
16
|
-
#
|
17
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
-
# SOFTWARE.
|
24
|
-
|
25
|
-
require 'minitest/autorun'
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require_relative '../test/test__helper'
|
26
7
|
require_relative '../lib/iri'
|
27
8
|
|
28
9
|
# Iri test.
|
@@ -203,4 +184,84 @@ class IriTest < Minitest::Test
|
|
203
184
|
Iri.new('http://google/?a=1&b=2&c=3&a=33').over(a: 'hey').to_s
|
204
185
|
)
|
205
186
|
end
|
187
|
+
|
188
|
+
def test_rejects_nil_uri
|
189
|
+
assert_raises ArgumentError do
|
190
|
+
Iri.new(nil)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_rejects_nil_scheme
|
195
|
+
assert_raises ArgumentError do
|
196
|
+
Iri.new('http://google.com').scheme(nil)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_rejects_nil_host
|
201
|
+
assert_raises ArgumentError do
|
202
|
+
Iri.new('http://google.com').host(nil)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_rejects_nil_port
|
207
|
+
assert_raises ArgumentError do
|
208
|
+
Iri.new('http://google.com').port(nil)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_rejects_nil_path
|
213
|
+
assert_raises ArgumentError do
|
214
|
+
Iri.new('http://google.com').path(nil)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_rejects_nil_fragment
|
219
|
+
assert_raises ArgumentError do
|
220
|
+
Iri.new('http://google.com').fragment(nil)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_rejects_nil_query
|
225
|
+
assert_raises ArgumentError do
|
226
|
+
Iri.new('http://google.com').query(nil)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_rejects_nil_cut_path
|
231
|
+
assert_raises ArgumentError do
|
232
|
+
Iri.new('http://google.com/foo').cut(nil)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_rejects_nil_append_part
|
237
|
+
assert_raises ArgumentError do
|
238
|
+
Iri.new('http://google.com').append(nil)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def test_rejects_nil_add_hash
|
243
|
+
assert_raises ArgumentError do
|
244
|
+
Iri.new('http://google.com').add(nil)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_rejects_nil_over_hash
|
249
|
+
assert_raises ArgumentError do
|
250
|
+
Iri.new('http://google.com').over(nil)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_with_alias_for_add
|
255
|
+
assert_equal(
|
256
|
+
'http://google.com?q=test&limit=10',
|
257
|
+
Iri.new('http://google.com').with(q: 'test', limit: 10).to_s
|
258
|
+
)
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_without_alias_for_del
|
262
|
+
assert_equal(
|
263
|
+
'http://google.com/?b=2',
|
264
|
+
Iri.new('http://google.com/?a=1&b=2&c=3').without(:a, :c).to_s
|
265
|
+
)
|
266
|
+
end
|
206
267
|
end
|