purl 1.6.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29fc6866f5fbc457ae2e2f29842be73ce8b6a3c5025e6478253294a0ba001443
4
- data.tar.gz: a345a99fddde1570eba0a38238848fd92c03b6e314835dc1bc18f98f589c11f8
3
+ metadata.gz: 02a4895e51ce7c9ace65a53d97fd8d4c5d288d7137daa71c9e8b8a778595e6cb
4
+ data.tar.gz: 55d348a442e23a0ffddc2b236bc1be22c9cf29d8eac0ce3ff289ce637e1ba6f5
5
5
  SHA512:
6
- metadata.gz: a4196a9cee395fac223edccf0524c7eb297ada80411d658aa6c1ae6827248c48608b41fee66d616b9c5b3f8281d0099dbfaeb223f05b2cfac930bad9067b4c11
7
- data.tar.gz: c0580ccb98f4dfb9516879049294c6feb553539d25d43a9f44fef35b9c60bb22857264e934148c11a2532e72149a43c919a0d00bdec4c3e12a965928e17ae140
6
+ metadata.gz: 4f8ff4ff13c1184c4b1adf6950fc1d04acedb8ce2cbbf4d62f2430455567e98f8b153b5493732919e1921a55132a2f3f51b61157b6eba85e207e6675d8675771
7
+ data.tar.gz: 9f203b73ef705fe70a27de5aae3b85558bd34204a2362a7429c441dc7f66d4a5ae403741fcb36471ab33e972868e4d31e6121a5eeaf23fb88832e3d38fec1e9c
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.7.0] - 2026-01-02
11
+
12
+ ### Added
13
+ - Download URL generation for 18 package ecosystems
14
+ - `download_url` instance method on `PackageURL` for getting artifact download URLs
15
+ - `supports_download_url?` method to check if download URL generation is supported
16
+ - `Purl.download_supported_types` to list all types with download URL support
17
+ - `download` command in CLI for getting download URLs from PURLs
18
+ - Supported types: bioconductor, bitbucket, cargo, clojars, cran, elm, gem, github, gitlab, golang, hackage, hex, luarocks, maven, npm, nuget, pub, swift
19
+ - Support for `repository_url` qualifier to use custom registries for downloads
20
+ - Support for custom base URLs via parameter override
21
+
10
22
  ## [1.6.0] - 2025-10-24
11
23
 
12
24
  ### Added
data/README.md CHANGED
@@ -16,10 +16,11 @@ This library features comprehensive error handling with namespaced error types,
16
16
 
17
17
  ## Features
18
18
 
19
- - **Command-line interface** with parse, validate, convert, generate, info, lookup, and advisories commands plus JSON output
19
+ - **Command-line interface** with parse, validate, convert, download, generate, info, lookup, and advisories commands plus JSON output
20
20
  - **Comprehensive PURL parsing and validation** with 37 package types (32 official + 5 additional ecosystems)
21
21
  - **Better error handling** with namespaced error classes and contextual information
22
22
  - **Bidirectional registry URL conversion** - generate registry URLs from PURLs and parse PURLs from registry URLs
23
+ - **Download URL generation** for 18 package ecosystems (gem, npm, cargo, maven, etc.)
23
24
  - **Security advisory lookup** - query security advisories from advisories.ecosyste.ms
24
25
  - **Package information lookup** - query package metadata from ecosyste.ms
25
26
  - **Type-specific validation** for conan, cran, and swift packages
@@ -69,6 +70,7 @@ purl parse <purl-string> # Parse and display PURL components
69
70
  purl validate <purl-string> # Validate a PURL (exit code indicates success)
70
71
  purl convert <registry-url> # Convert registry URL to PURL
71
72
  purl url <purl-string> # Convert PURL to registry URL
73
+ purl download <purl-string> # Get download URL for package version
72
74
  purl generate [options] # Generate PURL from components
73
75
  purl info [type] # Show information about PURL types
74
76
  purl lookup <purl-string> # Look up package information from ecosyste.ms
@@ -150,6 +152,26 @@ $ purl --json url "pkg:gem/rails@7.0.0"
150
152
  }
151
153
  ```
152
154
 
155
+ #### Get Download URL
156
+ ```bash
157
+ $ purl download "pkg:gem/rails@7.0.0"
158
+ https://rubygems.org/downloads/rails-7.0.0.gem
159
+
160
+ $ purl download "pkg:npm/@babel/core@7.20.0"
161
+ https://registry.npmjs.org/@babel/core/-/core-7.20.0.tgz
162
+
163
+ $ purl download "pkg:cargo/serde@1.0.152"
164
+ https://static.crates.io/crates/serde/serde-1.0.152.crate
165
+
166
+ $ purl --json download "pkg:maven/org.apache.commons/commons-lang3@3.12.0"
167
+ {
168
+ "success": true,
169
+ "purl": "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
170
+ "download_url": "https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar",
171
+ "type": "maven"
172
+ }
173
+ ```
174
+
153
175
  #### Generate a PURL
154
176
  ```bash
155
177
  $ purl generate --type gem --name rails --version 7.0.0
@@ -408,6 +430,42 @@ purl = Purl.parse("pkg:npm/@babel/core@7.0.0")
408
430
  puts purl.registry_url # => "https://www.npmjs.com/package/@babel/core"
409
431
  ```
410
432
 
433
+ ### Download URL Generation
434
+
435
+ Generate direct download URLs for package artifacts:
436
+
437
+ ```ruby
438
+ # Generate download URLs from PURLs
439
+ purl = Purl.parse("pkg:gem/rails@7.0.0")
440
+ puts purl.download_url # => "https://rubygems.org/downloads/rails-7.0.0.gem"
441
+
442
+ # Check if download URL generation is supported
443
+ puts purl.supports_download_url? # => true
444
+
445
+ # NPM with scoped packages
446
+ purl = Purl.parse("pkg:npm/@babel/core@7.20.0")
447
+ puts purl.download_url # => "https://registry.npmjs.org/@babel/core/-/core-7.20.0.tgz"
448
+
449
+ # Maven packages
450
+ purl = Purl.parse("pkg:maven/org.apache.commons/commons-lang3@3.12.0")
451
+ puts purl.download_url # => "https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar"
452
+
453
+ # Custom registry via repository_url qualifier
454
+ purl = Purl.parse("pkg:npm/lodash@4.17.21?repository_url=https://npm.mycompany.com")
455
+ puts purl.download_url # => "https://npm.mycompany.com/lodash/-/lodash-4.17.21.tgz"
456
+
457
+ # Custom registry via parameter
458
+ purl = Purl.parse("pkg:gem/rails@7.0.0")
459
+ puts purl.download_url(base_url: "https://gems.internal.com/downloads")
460
+ # => "https://gems.internal.com/downloads/rails-7.0.0.gem"
461
+
462
+ # Get list of supported types
463
+ puts Purl.download_supported_types
464
+ # => ["bioconductor", "bitbucket", "cargo", "clojars", "cran", "elm", "gem",
465
+ # "github", "gitlab", "golang", "hackage", "hex", "luarocks", "maven",
466
+ # "npm", "nuget", "pub", "swift"]
467
+ ```
468
+
411
469
  ### Reverse Parsing: Registry URLs to PURLs
412
470
 
413
471
  ```ruby
@@ -501,6 +559,7 @@ puts Purl.known_types.include?("gem") # => true
501
559
  puts Purl.known_type?("gem") # => true
502
560
  puts Purl.registry_supported_types # => ["cargo", "gem", "maven", "npm", ...]
503
561
  puts Purl.reverse_parsing_supported_types # => ["bioconductor", "cargo", "clojars", ...]
562
+ puts Purl.download_supported_types # => ["cargo", "gem", "maven", "npm", ...]
504
563
 
505
564
  # Get default registry for a type
506
565
  puts Purl.default_registry("gem") # => "https://rubygems.org"
@@ -520,6 +579,7 @@ puts info[:default_registry] # => "https://rubygems.org"
520
579
  puts info[:examples] # => ["pkg:gem/rails@7.0.4", ...]
521
580
  puts info[:registry_url_generation] # => true
522
581
  puts info[:reverse_parsing] # => true
582
+ puts info[:download_url_generation] # => true
523
583
  puts info[:route_patterns] # => ["https://rubygems.org/gems/:name", ...]
524
584
  ```
525
585
 
@@ -610,6 +670,26 @@ The library supports 37 package types (32 official + 5 additional ecosystems):
610
670
  **Reverse Parsing (20 types):**
611
671
  - `bioconductor`, `cargo`, `clojars`, `cocoapods`, `composer`, `conda`, `cpan`, `deno`, `elm`, `gem`, `golang`, `hackage`, `hex`, `homebrew`, `maven`, `npm`, `nuget`, `pub`, `pypi`, `swift`
612
672
 
673
+ **Download URL Generation (18 types):**
674
+ - `bioconductor` - bioconductor.org/packages/release/bioc/src/contrib
675
+ - `bitbucket` - bitbucket.org archive downloads
676
+ - `cargo` - static.crates.io/crates
677
+ - `clojars` - repo.clojars.org (Maven-style)
678
+ - `cran` - cran.r-project.org/src/contrib
679
+ - `elm` - GitHub archive downloads
680
+ - `gem` - rubygems.org/downloads
681
+ - `github` - GitHub archive downloads
682
+ - `gitlab` - GitLab archive downloads
683
+ - `golang` - proxy.golang.org
684
+ - `hackage` - hackage.haskell.org/package
685
+ - `hex` - repo.hex.pm/tarballs
686
+ - `luarocks` - luarocks.org/manifests
687
+ - `maven` - repo.maven.apache.org/maven2
688
+ - `npm` - registry.npmjs.org
689
+ - `nuget` - api.nuget.org/v3-flatcontainer
690
+ - `pub` - pub.dev/packages
691
+ - `swift` - GitHub/GitLab/Bitbucket (derived from namespace)
692
+
613
693
  **All 37 Supported Types:**
614
694
  `alpm`, `apk`, `bioconductor`, `bitbucket`, `bitnami`, `cargo`, `clojars`, `cocoapods`, `composer`, `conan`, `conda`, `cpan`, `cran`, `deb`, `deno`, `docker`, `elm`, `gem`, `generic`, `github`, `golang`, `hackage`, `hex`, `homebrew`, `huggingface`, `luarocks`, `maven`, `mlflow`, `npm`, `nuget`, `oci`, `pub`, `pypi`, `qpkg`, `rpm`, `swid`, `swift`
615
695
 
data/exe/purl CHANGED
@@ -40,6 +40,8 @@ class PurlCLI
40
40
  generate_command(args)
41
41
  when "url"
42
42
  url_command(args)
43
+ when "download"
44
+ download_command(args)
43
45
  when "info"
44
46
  info_command(args)
45
47
  when "lookup"
@@ -70,6 +72,7 @@ class PurlCLI
70
72
  purl [--json] validate <purl-string> Validate a PURL (exit code indicates success)
71
73
  purl [--json] convert <registry-url> Convert registry URL to PURL
72
74
  purl [--json] url <purl-string> Convert PURL to registry URL
75
+ purl [--json] download <purl-string> Get download URL for package version
73
76
  purl [--json] generate [options] Generate PURL from components
74
77
  purl [--json] info [type] Show information about PURL types
75
78
  purl [--json] lookup <purl-string> Look up package information from ecosyste.ms
@@ -277,6 +280,73 @@ class PurlCLI
277
280
  end
278
281
  end
279
282
 
283
+ def download_command(args)
284
+ if args.empty?
285
+ output_error("PURL string required")
286
+ exit 1
287
+ end
288
+
289
+ purl_string = args[0]
290
+
291
+ begin
292
+ purl = Purl.parse(purl_string)
293
+
294
+ unless purl.supports_download_url?
295
+ if @json_output
296
+ result = {
297
+ success: false,
298
+ purl: purl_string,
299
+ error: "Download URL generation not supported for type '#{purl.type}'",
300
+ supported_types: Purl.download_supported_types
301
+ }
302
+ puts JSON.pretty_generate(result)
303
+ else
304
+ puts "Error: Download URL generation not supported for type '#{purl.type}'"
305
+ puts "Supported types: #{Purl.download_supported_types.join(', ')}"
306
+ end
307
+ exit 1
308
+ end
309
+
310
+ download_url = purl.download_url
311
+
312
+ if @json_output
313
+ result = {
314
+ success: true,
315
+ purl: purl.to_s,
316
+ download_url: download_url,
317
+ type: purl.type
318
+ }
319
+ puts JSON.pretty_generate(result)
320
+ else
321
+ puts download_url
322
+ end
323
+ rescue Purl::MissingVersionError => e
324
+ if @json_output
325
+ result = {
326
+ success: false,
327
+ purl: purl_string,
328
+ error: "Version required for download URL"
329
+ }
330
+ puts JSON.pretty_generate(result)
331
+ else
332
+ puts "Error: Version required for download URL"
333
+ end
334
+ exit 1
335
+ rescue Purl::Error => e
336
+ if @json_output
337
+ result = {
338
+ success: false,
339
+ purl: purl_string,
340
+ error: e.message
341
+ }
342
+ puts JSON.pretty_generate(result)
343
+ else
344
+ puts "Error: #{e.message}"
345
+ end
346
+ exit 1
347
+ end
348
+ end
349
+
280
350
  def generate_command(args)
281
351
  options = {}
282
352
  OptionParser.new do |opts|
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purl
4
+ class DownloadURL
5
+ DOWNLOAD_PATTERNS = {
6
+ "gem" => {
7
+ base_url: "https://rubygems.org/downloads",
8
+ pattern: ->(purl) { "#{purl.name}-#{purl.version}.gem" }
9
+ },
10
+ "npm" => {
11
+ base_url: "https://registry.npmjs.org",
12
+ pattern: ->(purl) do
13
+ # npm: /{name}/-/{basename}-{version}.tgz
14
+ # For scoped packages: /@scope/name/-/name-version.tgz
15
+ basename = purl.name.split("/").last
16
+ if purl.namespace
17
+ "#{purl.namespace}/#{purl.name}/-/#{basename}-#{purl.version}.tgz"
18
+ else
19
+ "#{purl.name}/-/#{purl.name}-#{purl.version}.tgz"
20
+ end
21
+ end
22
+ },
23
+ "cargo" => {
24
+ base_url: "https://static.crates.io/crates",
25
+ pattern: ->(purl) { "#{purl.name}/#{purl.name}-#{purl.version}.crate" }
26
+ },
27
+ "nuget" => {
28
+ base_url: "https://api.nuget.org/v3-flatcontainer",
29
+ pattern: ->(purl) do
30
+ name_lower = purl.name.downcase
31
+ "#{name_lower}/#{purl.version}/#{name_lower}.#{purl.version}.nupkg"
32
+ end
33
+ },
34
+ "hex" => {
35
+ base_url: "https://repo.hex.pm/tarballs",
36
+ pattern: ->(purl) { "#{purl.name}-#{purl.version}.tar" }
37
+ },
38
+ "hackage" => {
39
+ base_url: "https://hackage.haskell.org/package",
40
+ pattern: ->(purl) { "#{purl.name}-#{purl.version}/#{purl.name}-#{purl.version}.tar.gz" }
41
+ },
42
+ "pub" => {
43
+ base_url: "https://pub.dev/packages",
44
+ pattern: ->(purl) { "#{purl.name}/versions/#{purl.version}.tar.gz" }
45
+ },
46
+ "golang" => {
47
+ base_url: "https://proxy.golang.org",
48
+ pattern: ->(purl) do
49
+ # Go module proxy requires encoding capital letters as !lowercase
50
+ full_path = purl.namespace ? "#{purl.namespace}/#{purl.name}" : purl.name
51
+ encoded = full_path.gsub(/[A-Z]/) { |s| "!#{s.downcase}" }
52
+ "#{encoded}/@v/#{purl.version}.zip"
53
+ end
54
+ },
55
+ "maven" => {
56
+ base_url: "https://repo.maven.apache.org/maven2",
57
+ pattern: ->(purl) do
58
+ # Maven: /{group_path}/{artifact}/{version}/{artifact}-{version}.jar
59
+ # group_id uses dots, path uses slashes
60
+ group_path = purl.namespace.gsub(".", "/")
61
+ "#{group_path}/#{purl.name}/#{purl.version}/#{purl.name}-#{purl.version}.jar"
62
+ end
63
+ },
64
+ "cran" => {
65
+ base_url: "https://cran.r-project.org/src/contrib",
66
+ pattern: ->(purl) { "#{purl.name}_#{purl.version}.tar.gz" }
67
+ },
68
+ "bioconductor" => {
69
+ base_url: "https://bioconductor.org/packages/release/bioc/src/contrib",
70
+ pattern: ->(purl) { "#{purl.name}_#{purl.version}.tar.gz" }
71
+ },
72
+ "clojars" => {
73
+ base_url: "https://repo.clojars.org",
74
+ pattern: ->(purl) do
75
+ # Clojars uses maven-style paths
76
+ # namespace is group_id, name is artifact_id
77
+ # If no namespace, group_id = artifact_id
78
+ group_id = purl.namespace || purl.name
79
+ artifact_id = purl.name
80
+ group_path = group_id.gsub(".", "/")
81
+ "#{group_path}/#{artifact_id}/#{purl.version}/#{artifact_id}-#{purl.version}.jar"
82
+ end
83
+ },
84
+ "elm" => {
85
+ base_url: "https://github.com",
86
+ pattern: ->(purl) do
87
+ # Elm packages are hosted on GitHub
88
+ # namespace/name format maps to GitHub user/repo
89
+ return nil unless purl.namespace
90
+ "#{purl.namespace}/#{purl.name}/archive/#{purl.version}.zip"
91
+ end
92
+ },
93
+ "github" => {
94
+ base_url: "https://github.com",
95
+ pattern: ->(purl) do
96
+ return nil unless purl.namespace
97
+ "#{purl.namespace}/#{purl.name}/archive/refs/tags/#{purl.version}.tar.gz"
98
+ end
99
+ },
100
+ "gitlab" => {
101
+ base_url: "https://gitlab.com",
102
+ pattern: ->(purl) do
103
+ return nil unless purl.namespace
104
+ "#{purl.namespace}/#{purl.name}/-/archive/#{purl.version}/#{purl.name}-#{purl.version}.tar.gz"
105
+ end
106
+ },
107
+ "bitbucket" => {
108
+ base_url: "https://bitbucket.org",
109
+ pattern: ->(purl) do
110
+ return nil unless purl.namespace
111
+ "#{purl.namespace}/#{purl.name}/get/#{purl.version}.tar.gz"
112
+ end
113
+ },
114
+ "luarocks" => {
115
+ base_url: "https://luarocks.org/manifests",
116
+ pattern: ->(purl) do
117
+ return nil unless purl.namespace
118
+ "#{purl.namespace}/#{purl.name}-#{purl.version}.src.rock"
119
+ end
120
+ },
121
+ "swift" => {
122
+ base_url: nil,
123
+ pattern: ->(purl) do
124
+ # Swift namespace is like "github.com/owner", name is repo
125
+ # e.g. pkg:swift/github.com/Alamofire/Alamofire@5.6.4
126
+ return nil unless purl.namespace
127
+ parts = purl.namespace.split("/", 2)
128
+ host = parts[0]
129
+ owner = parts[1]
130
+ return nil unless host && owner
131
+
132
+ case host
133
+ when "github.com"
134
+ "https://github.com/#{owner}/#{purl.name}/archive/refs/tags/#{purl.version}.tar.gz"
135
+ when "gitlab.com"
136
+ "https://gitlab.com/#{owner}/#{purl.name}/-/archive/#{purl.version}/#{purl.name}-#{purl.version}.tar.gz"
137
+ when "bitbucket.org"
138
+ "https://bitbucket.org/#{owner}/#{purl.name}/get/#{purl.version}.tar.gz"
139
+ end
140
+ end
141
+ },
142
+ "composer" => {
143
+ base_url: nil,
144
+ pattern: ->(purl) { nil },
145
+ note: "Composer packages are downloaded from source repositories, not Packagist"
146
+ },
147
+ "cocoapods" => {
148
+ base_url: nil,
149
+ pattern: ->(purl) { nil },
150
+ note: "CocoaPods packages are downloaded from source repositories"
151
+ }
152
+ }.freeze
153
+
154
+ def self.generate(purl, base_url: nil)
155
+ new(purl).generate(base_url: base_url)
156
+ end
157
+
158
+ # Types that require a namespace for download URLs
159
+ NAMESPACE_REQUIRED_TYPES = %w[maven elm github gitlab bitbucket luarocks swift].freeze
160
+
161
+ def self.supported_types
162
+ DOWNLOAD_PATTERNS.keys.select do |k|
163
+ pattern = DOWNLOAD_PATTERNS[k]
164
+ # Skip types with notes (they're not really supported)
165
+ next false if pattern[:note]
166
+
167
+ # Test with appropriate namespace for types that need it
168
+ namespace = if NAMESPACE_REQUIRED_TYPES.include?(k)
169
+ k == "swift" ? "github.com/test" : "test"
170
+ end
171
+ begin
172
+ result = pattern[:pattern].call(Purl::PackageURL.new(type: k, name: "test", version: "1.0", namespace: namespace))
173
+ !result.nil?
174
+ rescue
175
+ false
176
+ end
177
+ end.sort
178
+ end
179
+
180
+ def self.supports?(type)
181
+ pattern = DOWNLOAD_PATTERNS[type.to_s.downcase]
182
+ return false unless pattern
183
+ # Types with base_url are supported, or types that return full URLs (like swift)
184
+ return true if pattern[:base_url]
185
+ # Check if this type returns full URLs by testing with a sample
186
+ return false if pattern[:note] # Has a note means it's not really supported
187
+ true
188
+ end
189
+
190
+ def initialize(purl)
191
+ @purl = purl
192
+ end
193
+
194
+ def generate(base_url: nil)
195
+ unless @purl.version
196
+ raise MissingVersionError.new(
197
+ "Download URL requires a version",
198
+ type: @purl.type
199
+ )
200
+ end
201
+
202
+ pattern_config = DOWNLOAD_PATTERNS[@purl.type.downcase]
203
+
204
+ unless pattern_config
205
+ raise UnsupportedTypeError.new(
206
+ "No download URL pattern defined for type '#{@purl.type}'. Supported types: #{self.class.supported_types.join(", ")}",
207
+ type: @purl.type,
208
+ supported_types: self.class.supported_types
209
+ )
210
+ end
211
+
212
+ # Check for repository_url qualifier in the PURL
213
+ qualifier_base_url = @purl.qualifiers&.dig("repository_url")
214
+
215
+ # Generate the path/URL from the pattern
216
+ path = pattern_config[:pattern].call(@purl)
217
+
218
+ if path.nil?
219
+ raise UnsupportedTypeError.new(
220
+ "Could not generate download URL for '#{@purl.type}'. #{pattern_config[:note]}",
221
+ type: @purl.type,
222
+ supported_types: self.class.supported_types
223
+ )
224
+ end
225
+
226
+ # If the pattern returns a full URL, use it directly
227
+ return path if path.start_with?("http://", "https://")
228
+
229
+ # For relative paths, we need a base_url
230
+ effective_base_url = base_url || qualifier_base_url || pattern_config[:base_url]
231
+
232
+ unless effective_base_url
233
+ raise UnsupportedTypeError.new(
234
+ "Download URLs are not available for type '#{@purl.type}'. #{pattern_config[:note]}",
235
+ type: @purl.type,
236
+ supported_types: self.class.supported_types
237
+ )
238
+ end
239
+
240
+ "#{effective_base_url}/#{path}"
241
+ end
242
+
243
+ attr_reader :purl
244
+ end
245
+
246
+ # Error for missing version
247
+ class MissingVersionError < Error
248
+ attr_reader :type
249
+
250
+ def initialize(message, type: nil)
251
+ @type = type
252
+ super(message)
253
+ end
254
+ end
255
+
256
+ # Add download URL generation methods to PackageURL
257
+ class PackageURL
258
+ def download_url(base_url: nil)
259
+ DownloadURL.generate(self, base_url: base_url)
260
+ end
261
+
262
+ def supports_download_url?
263
+ DownloadURL.supports?(type)
264
+ end
265
+ end
266
+ end
data/lib/purl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Purl
4
- VERSION = "1.6.0"
4
+ VERSION = "1.7.0"
5
5
  end
data/lib/purl.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "purl/version"
4
4
  require_relative "purl/errors"
5
5
  require_relative "purl/package_url"
6
6
  require_relative "purl/registry_url"
7
+ require_relative "purl/download_url"
7
8
  require_relative "purl/lookup"
8
9
  require_relative "purl/lookup_formatter"
9
10
  require_relative "purl/advisory"
@@ -102,6 +103,17 @@ module Purl
102
103
  RegistryURL.supported_reverse_types
103
104
  end
104
105
 
106
+ # Returns types that have download URL support
107
+ #
108
+ # @return [Array<String>] sorted array of types that can generate download URLs
109
+ #
110
+ # @example
111
+ # types = Purl.download_supported_types
112
+ # puts types.include?("gem") # true if gem has download URL support
113
+ def self.download_supported_types
114
+ DownloadURL.supported_types
115
+ end
116
+
105
117
  # Check if a type is known/valid
106
118
  #
107
119
  # @param type [String, Symbol] the type to check
@@ -140,6 +152,7 @@ module Purl
140
152
  examples: type_examples(normalized_type),
141
153
  registry_url_generation: RegistryURL.supports?(normalized_type),
142
154
  reverse_parsing: RegistryURL.supported_reverse_types.include?(normalized_type),
155
+ download_url_generation: DownloadURL.supports?(normalized_type),
143
156
  route_patterns: RegistryURL.route_patterns_for(normalized_type)
144
157
  }
145
158
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: purl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
@@ -45,6 +45,7 @@ files:
45
45
  - lib/purl.rb
46
46
  - lib/purl/advisory.rb
47
47
  - lib/purl/advisory_formatter.rb
48
+ - lib/purl/download_url.rb
48
49
  - lib/purl/errors.rb
49
50
  - lib/purl/lookup.rb
50
51
  - lib/purl/lookup_formatter.rb
@@ -74,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
75
  - !ruby/object:Gem::Version
75
76
  version: '0'
76
77
  requirements: []
77
- rubygems_version: 3.6.9
78
+ rubygems_version: 4.0.1
78
79
  specification_version: 4
79
80
  summary: Parse and convert package urls (purls)
80
81
  test_files: []