normalizeurl2025 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 074cfe360aac260e52d0aa43406d0ba34be9d0d8cb38255b2f683d8d667aac99
4
+ data.tar.gz: 5f7c9faee5ec6add114898108cd650d8aee674d1d752e3a51a48993f3dd868ce
5
+ SHA512:
6
+ metadata.gz: bdef572d7b2135a420d3e07b4fa6b4e57ac5a9b3e33dab3485d149dd2da1877bad5f15d8aac1aef6c18dbeb8a7d921eef5069e9bee6ec1f712388c4385e2277f
7
+ data.tar.gz: b9580b5c71d3ef21f5a3f08f2b65859433499a0637f8581be39175d3389a6132809ab232a514e59f29110c3043a361ed8a9572de654f0c6427589a47b2517e90
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Use the new gemspec name
4
+ gemspec name: "normalizeurl2025"
5
+
6
+ gem "rake", "~> 13.0"
7
+ gem "minitest", "~> 5.0"
data/README.md ADDED
@@ -0,0 +1,278 @@
1
+ # NormalizeURL 2025
2
+
3
+ A Ruby library for normalizing URLs by removing tracking parameters, session IDs, and other extraneous elements whilst preserving important parameters.
4
+
5
+ NormalizeURL 2025 creates a canonical representation of URLs that can be used for deduplication, caching keys, or comparison purposes and was developed initially to deduplicate URLs found in RSS feeds.
6
+
7
+ > [!IMPORTANT]
8
+ > The normalized URLs are intended for creating unique representations and may not always remain functional URLs.
9
+
10
+ > [!NOTE]
11
+ > The library has such a silly name because RubyGems refused to let me use the available name of 'normalizeurl' due to an ancient gem with a similar name. Rather than putting 'lol' or 'poo' on the end, you get 2025 instead.
12
+
13
+ **Important**:
14
+
15
+ ## Installation
16
+
17
+ For your `Gemfile`:
18
+
19
+ ```ruby
20
+ gem 'normalizeurl2025'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle install
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install normalizeurl2025
30
+
31
+ ## Usage
32
+
33
+ ### Basic Usage
34
+
35
+ ```ruby
36
+ require 'normalizeurl2025'
37
+
38
+ # Simple normalization
39
+ url = "https://example.com/page?utm_source=google&utm_medium=cpc&id=123"
40
+ normalized = Normalizeurl2025.normalize(url)
41
+ # => "https://example.com/page?id=123"
42
+
43
+ # Remove tracking parameters whilst preserving important ones
44
+ youtube_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&utm_source=twitter&t=42"
45
+ normalized = Normalizeurl2025.normalize(youtube_url)
46
+ # => "https://youtube.com/watch?t=42&v=dQw4w9WgXcQ"
47
+ ```
48
+
49
+ ### Configuration Options
50
+
51
+ NormalizeURL 2025 accepts various options to customize the normalization behaviour:
52
+
53
+ ```ruby
54
+ # Default options
55
+ options = {
56
+ remove_tracking_params: true, # Remove UTM and other tracking parameters
57
+ remove_trailing_slash: true, # Remove trailing slashes from paths
58
+ downcase_hostname: true, # Convert hostname to lowercase
59
+ remove_www: false, # Remove 'www.' prefix from hostname
60
+ remove_fragment: true, # Remove URL fragments (#section)
61
+ custom_tracking_params: [], # Additional tracking parameters to remove
62
+ preserve_params: {} # Domain-specific parameters to preserve
63
+ }
64
+
65
+ normalized = Normalizeurl2025.normalize(url, options)
66
+ ```
67
+
68
+ ### Examples
69
+
70
+ #### Removing Tracking Parameters
71
+
72
+ ```ruby
73
+ # UTM parameters are removed
74
+ url = "https://example.com/article?utm_source=newsletter&utm_campaign=spring2024&id=456"
75
+ Normalizeurl2025.normalize(url)
76
+ # => "https://example.com/article?id=456"
77
+
78
+ # Google Click ID and Facebook Click ID are removed
79
+ url = "https://shop.example.com/product?gclid=abc123&fbclid=def456&product_id=789"
80
+ Normalizeurl2025.normalize(url)
81
+ # => "https://shop.example.com/product?product_id=789"
82
+
83
+ # Session IDs and tracking pixels are removed
84
+ url = "https://site.com/page?PHPSESSID=abc123&_ga=GA1.2.123456&content=article"
85
+ Normalizeurl2025.normalize(url)
86
+ # => "https://site.com/page?content=article"
87
+ ```
88
+
89
+ #### Preserving Important Parameters
90
+
91
+ ```ruby
92
+ # YouTube video parameters are preserved
93
+ url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&utm_source=email&t=120&list=PLxyz"
94
+ Normalizeurl2025.normalize(url)
95
+ # => "https://youtube.com/watch?list=PLxyz&t=120&v=dQw4w9WgXcQ"
96
+
97
+ # Amazon product parameters are preserved
98
+ url = "https://amazon.com/dp/B08N5WRWNW?tag=mysite-20&utm_source=blog&keywords=ruby"
99
+ Normalizeurl2025.normalize(url)
100
+ # => "https://amazon.com/dp/B08N5WRWNW?keywords=ruby&tag=mysite-20"
101
+
102
+ # Search engine queries are preserved
103
+ url = "https://google.com/search?q=ruby+gems&utm_source=referral&hl=en"
104
+ Normalizeurl2025.normalize(url)
105
+ # => "https://google.com/search?q=ruby+gems"
106
+ ```
107
+
108
+ #### URL Structure Normalization
109
+
110
+ ```ruby
111
+ # Hostname is lowercased and www is optionally removed
112
+ url = "HTTPS://WWW.EXAMPLE.COM/Path/?utm_source=email"
113
+ Normalizeurl2025.normalize(url)
114
+ # => "https://www.example.com/Path"
115
+
116
+ # Remove www prefix
117
+ url = "https://www.example.com/page?id=123"
118
+ Normalizeurl2025.normalize(url, remove_www: true)
119
+ # => "https://example.com/page?id=123"
120
+
121
+ # Trailing slashes are removed (except root)
122
+ url = "https://example.com/path/to/page/?utm_medium=social"
123
+ Normalizeurl2025.normalize(url)
124
+ # => "https://example.com/path/to/page?utm_medium=social"
125
+
126
+ # Fragments are removed by default
127
+ url = "https://example.com/page#section?utm_source=twitter"
128
+ Normalizeurl2025.normalize(url)
129
+ # => "https://example.com/page"
130
+
131
+ # Keep fragments if needed
132
+ url = "https://example.com/page#section?utm_source=twitter"
133
+ Normalizeurl2025.normalize(url, remove_fragment: false)
134
+ # => "https://example.com/page#section"
135
+ ```
136
+
137
+ #### Query Parameter Sorting
138
+
139
+ ```ruby
140
+ # Parameters are sorted alphabetically for consistency
141
+ url = "https://example.com/search?z=last&a=first&m=middle&utm_source=email"
142
+ Normalizeurl2025.normalize(url)
143
+ # => "https://example.com/search?a=first&m=middle&z=last"
144
+ ```
145
+
146
+ #### Custom Configuration
147
+
148
+ ```ruby
149
+ # Add custom tracking parameters to remove
150
+ options = {
151
+ custom_tracking_params: ['my_tracker', 'internal_ref', 'campaign_id']
152
+ }
153
+ url = "https://example.com/page?my_tracker=abc&internal_ref=xyz&id=123&campaign_id=spring"
154
+ Normalizeurl2025.normalize(url, options)
155
+ # => "https://example.com/page?id=123"
156
+
157
+ # Preserve custom parameters for specific domains
158
+ options = {
159
+ preserve_params: {
160
+ 'mysite.com' => ['special_param', 'user_pref'],
161
+ 'shop.example.com' => ['affiliate_id', 'discount_code']
162
+ }
163
+ }
164
+ url = "https://mysite.com/page?special_param=value&utm_source=email&user_pref=dark"
165
+ Normalizeurl2025.normalize(url, options)
166
+ # => "https://mysite.com/page?special_param=value&user_pref=dark"
167
+
168
+ # Subdomain matching works automatically
169
+ url = "https://blog.mysite.com/post?special_param=test&utm_campaign=launch"
170
+ Normalizeurl2025.normalize(url, options)
171
+ # => "https://blog.mysite.com/post?special_param=test"
172
+ ```
173
+
174
+ #### Error Handling
175
+
176
+ ```ruby
177
+ # Invalid URLs are returned unchanged
178
+ Normalizeurl2025.normalize("not-a-url")
179
+ # => "not-a-url"
180
+
181
+ # Nil and empty strings return nil
182
+ Normalizeurl2025.normalize(nil)
183
+ # => nil
184
+ ```
185
+
186
+ ## Default Behaviour
187
+
188
+ ### Tracking Parameters Removed
189
+
190
+ NormalizeURL 2025 removes these common tracking parameters by default:
191
+
192
+ **UTM Parameters:**
193
+ - `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`
194
+ - `utm_name`, `utm_cid`, `utm_reader`, `utm_viz_id`, `utm_pubreferrer`, `utm_swu`
195
+
196
+ **Click IDs:**
197
+ - `gclid` (Google Ads), `fbclid` (Facebook), `msclkid` (Microsoft Advertising)
198
+ - `yclid` (Yandex), `rb_clickid` (Rakuten)
199
+
200
+ **Analytics & Tracking:**
201
+ - `_ga`, `_gl` (Google Analytics)
202
+ - `mc_cid`, `mc_eid` (Mailchimp)
203
+ - `s_cid` (Adobe Analytics)
204
+ - `_openstat` (OpenStat)
205
+ - `ck_subscriber_id` (Kit / ConvertKit)
206
+
207
+ **Session & User IDs:**
208
+ - `PHPSESSID`, `JSESSIONID`, `ASPSESSIONID`
209
+ - `sid`, `sessionid`, `session_id`
210
+ - `subscriber_id`, `ig_rid` (Instagram)
211
+ - `oly_anon_id`, `oly_enc_id` (Optimizely)
212
+ - `wickedid`, `vero_conv`, `vero_id`
213
+
214
+ **Referrer & Source Tracking:**
215
+ - `ref`, `referer`, `referrer`, `source`, `src`
216
+ - `campaign`, `__s`
217
+
218
+ ### Parameters Preserved by Domain
219
+
220
+ Certain parameters are automatically preserved for specific domains:
221
+
222
+ - **YouTube** (`youtube.com`, `youtu.be`): `v` (video ID), `t` (timestamp), `list` (playlist), `index`
223
+ - **Amazon** (`amazon.com`, `amazon.co.uk`): `keywords`, `tag`
224
+ - **eBay** (`ebay.com`, `ebay.co.uk`): `hash`
225
+ - **Google** (`google.com`, `google.co.uk`): `q` (search query)
226
+ - **Bing** (`bing.com`): `q` (search query)
227
+ - **DuckDuckGo** (`duckduckgo.com`): `q` (search query)
228
+ - **Stack Overflow** (`stackoverflow.com`): `answertab`
229
+ - **Vimeo** (`vimeo.com`): `h_original`
230
+
231
+ ### URL Transformations
232
+
233
+ 1. **Scheme**: Converted to lowercase (`HTTP` → `http`)
234
+ 2. **Hostname**: Converted to lowercase (`Example.COM` → `example.com`)
235
+ 3. **WWW prefix**: Optionally removed (`www.example.com` → `example.com`)
236
+ 4. **Path**:
237
+ - Trailing slashes removed (except root path)
238
+ - Empty paths become `/`
239
+ 5. **Query parameters**:
240
+ - Tracking parameters filtered out
241
+ - Remaining parameters sorted alphabetically
242
+ - Empty query strings removed entirely
243
+ 6. **Fragments**: Removed by default (`#section` removed)
244
+
245
+ ## Advanced Usage
246
+
247
+ ### Batch Processing
248
+
249
+ ```ruby
250
+ urls = [
251
+ "https://example.com/page1?utm_source=email",
252
+ "https://example.com/page2?gclid=abc123",
253
+ "https://example.com/page3?fbclid=def456"
254
+ ]
255
+
256
+ normalized_urls = urls.map { |url| Normalizeurl2025.normalize(url) }
257
+ # => ["https://example.com/page1", "https://example.com/page2", "https://example.com/page3"]
258
+ ```
259
+
260
+ ### Creating Consistent Cache Keys
261
+
262
+ ```ruby
263
+ def cache_key_for_url(url)
264
+ normalized = Normalizeurl2025.normalize(url)
265
+ Digest::MD5.hexdigest(normalized)
266
+ end
267
+
268
+ cache_key_for_url("https://example.com/article?utm_source=twitter&id=123")
269
+ # => "a1b2c3d4e5f6..." (consistent hash regardless of tracking params)
270
+ ```
271
+
272
+ ## Contributing
273
+
274
+ Bug reports and pull requests are welcome on GitHub at https://github.com/peterc/normalizeurl2025.
275
+
276
+ ## Licence
277
+
278
+ The gem is available as open source under the terms of the [MIT Licence](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,165 @@
1
+ require 'uri'
2
+
3
+ module Normalizeurl2025
4
+ class Normalizer
5
+ # Parameters that are commonly used for tracking and should be removed
6
+ DEFAULT_TRACKING_PARAMS = %w[
7
+ utm_source utm_medium utm_campaign utm_term utm_content
8
+ utm_name utm_cid utm_reader utm_viz_id utm_pubreferrer utm_swu
9
+ gclid fbclid msclkid
10
+ ck_subscriber_id
11
+ ck_subscriber
12
+ _ga _gl
13
+ mc_cid mc_eid
14
+ PHPSESSID JSESSIONID ASPSESSIONID
15
+ sid sessionid session_id
16
+ ref referer referrer
17
+ source src
18
+ campaign
19
+ yclid
20
+ _openstat
21
+ rb_clickid
22
+ s_cid
23
+ vero_conv vero_id
24
+ wickedid
25
+ oly_anon_id oly_enc_id
26
+ __s
27
+ subscriber_id
28
+ ig_rid
29
+ ].freeze
30
+
31
+ # Parameters that should be preserved for certain domains
32
+ PRESERVE_PARAMS = {
33
+ 'youtube.com' => %w[v t list index],
34
+ 'youtu.be' => %w[v t],
35
+ 'vimeo.com' => %w[h_original],
36
+ 'amazon.com' => %w[keywords tag],
37
+ 'amazon.co.uk' => %w[keywords tag],
38
+ 'ebay.com' => %w[hash],
39
+ 'ebay.co.uk' => %w[hash],
40
+ 'stackoverflow.com' => %w[answertab],
41
+ 'google.com' => %w[q],
42
+ 'google.co.uk' => %w[q],
43
+ 'bing.com' => %w[q],
44
+ 'duckduckgo.com' => %w[q]
45
+ }.freeze
46
+
47
+ def initialize(options = {})
48
+ @remove_tracking_params = options.fetch(:remove_tracking_params, true)
49
+ @remove_trailing_slash = options.fetch(:remove_trailing_slash, true)
50
+ @downcase_hostname = options.fetch(:downcase_hostname, true)
51
+ @remove_www = options.fetch(:remove_www, false)
52
+ @remove_fragment = options.fetch(:remove_fragment, true)
53
+ @custom_tracking_params = options.fetch(:custom_tracking_params, [])
54
+ @preserve_params = options.fetch(:preserve_params, {})
55
+ end
56
+
57
+ def normalize(url)
58
+ return nil if url.nil? || url.strip.empty?
59
+
60
+ begin
61
+ uri = URI.parse(url.strip)
62
+ rescue URI::InvalidURIError
63
+ return url
64
+ end
65
+
66
+ # Only process HTTP/HTTPS URLs
67
+ return url unless %w[http https].include?(uri.scheme&.downcase)
68
+
69
+ normalize_scheme!(uri)
70
+ normalize_hostname!(uri)
71
+ normalize_path!(uri)
72
+ normalize_query!(uri)
73
+ normalize_fragment!(uri)
74
+
75
+ uri.to_s
76
+ end
77
+
78
+ private
79
+
80
+ def normalize_scheme!(uri)
81
+ uri.scheme = uri.scheme.downcase if uri.scheme
82
+ end
83
+
84
+ def normalize_hostname!(uri)
85
+ return unless uri.host
86
+
87
+ if @downcase_hostname
88
+ uri.host = uri.host.downcase
89
+ end
90
+
91
+ if @remove_www && uri.host.start_with?('www.')
92
+ uri.host = uri.host[4..-1]
93
+ end
94
+ end
95
+
96
+ def normalize_path!(uri)
97
+ return unless uri.path
98
+
99
+ # Remove trailing slash unless it's the root path
100
+ if @remove_trailing_slash && uri.path != '/' && uri.path.end_with?('/')
101
+ uri.path = uri.path.chomp('/')
102
+ end
103
+
104
+ # Ensure root path is present
105
+ uri.path = '/' if uri.path.empty?
106
+ end
107
+
108
+ def normalize_query!(uri)
109
+ return unless uri.query && @remove_tracking_params
110
+
111
+ params = URI.decode_www_form(uri.query)
112
+
113
+ # Get domain-specific parameters to preserve
114
+ domain_preserve_params = get_preserve_params_for_domain(uri.host)
115
+
116
+ # Filter out tracking parameters
117
+ filtered_params = params.reject do |key, _value|
118
+ should_remove_param?(key, domain_preserve_params)
119
+ end
120
+
121
+ if filtered_params.empty?
122
+ uri.query = nil
123
+ else
124
+ # Sort parameters for consistency
125
+ uri.query = URI.encode_www_form(filtered_params.sort)
126
+ end
127
+ end
128
+
129
+ def normalize_fragment!(uri)
130
+ uri.fragment = nil if @remove_fragment
131
+ end
132
+
133
+ def get_preserve_params_for_domain(host)
134
+ return [] unless host
135
+
136
+ # Check for exact domain match first
137
+ preserved = PRESERVE_PARAMS[host] || []
138
+
139
+ # Check for subdomain matches
140
+ PRESERVE_PARAMS.each do |domain, params|
141
+ if host.end_with?(".#{domain}")
142
+ preserved.concat(params)
143
+ end
144
+ end
145
+
146
+ # Add custom preserve params for this domain
147
+ if @preserve_params[host]
148
+ preserved.concat(@preserve_params[host])
149
+ end
150
+
151
+ preserved.uniq
152
+ end
153
+
154
+ def should_remove_param?(param_name, preserve_params)
155
+ param_lower = param_name.downcase
156
+
157
+ # Don't remove if it's in the preserve list
158
+ return false if preserve_params.any? { |p| p.downcase == param_lower }
159
+
160
+ # Remove if it's a tracking parameter
161
+ tracking_params = DEFAULT_TRACKING_PARAMS + @custom_tracking_params
162
+ tracking_params.any? { |tp| tp.downcase == param_lower }
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,3 @@
1
+ module Normalizeurl2025
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "normalizeurl2025/version"
2
+ require_relative "normalizeurl2025/normalizer"
3
+
4
+ module Normalizeurl2025
5
+ class Error < StandardError; end
6
+
7
+ def self.normalize(url, options = {})
8
+ Normalizer.new(options).normalize(url)
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ require_relative "lib/normalizeurl2025/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "normalizeurl2025"
5
+ spec.version = Normalizeurl2025::VERSION
6
+ spec.authors = ["Peter Cooper"]
7
+ spec.email = ["git@peterc.org"]
8
+
9
+ spec.summary = "A Ruby library for normalizing URLs"
10
+ spec.description = "Normalizes URLs by removing tracking parameters, session IDs, and other extraneous elements whilst preserving important parameters"
11
+ spec.homepage = "https://github.com/peterc/normalizeurl2025"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 3.0.0"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/peterc/normalizeurl2025"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ spec.files = Dir.chdir(__dir__) do
20
+ `git ls-files -z`.split("\x0").reject do |f|
21
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
22
+ end
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency "minitest", "~> 5.0"
29
+ spec.add_development_dependency "rake", "~> 13.0"
30
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: normalizeurl2025
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Peter Cooper
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ description: Normalizes URLs by removing tracking parameters, session IDs, and other
41
+ extraneous elements whilst preserving important parameters
42
+ email:
43
+ - git@peterc.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - README.md
50
+ - Rakefile
51
+ - lib/normalizeurl2025.rb
52
+ - lib/normalizeurl2025/normalizer.rb
53
+ - lib/normalizeurl2025/version.rb
54
+ - normalizeurl2025.gemspec
55
+ homepage: https://github.com/peterc/normalizeurl2025
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ homepage_uri: https://github.com/peterc/normalizeurl2025
60
+ source_code_uri: https://github.com/peterc/normalizeurl2025
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.7
76
+ specification_version: 4
77
+ summary: A Ruby library for normalizing URLs
78
+ test_files: []