purl 1.1.2 → 1.3.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 +4 -4
- data/.gitmodules +3 -0
- data/CHANGELOG.md +39 -0
- data/README.md +19 -0
- data/lib/purl/package_url.rb +36 -101
- data/lib/purl/registry_url.rb +150 -43
- data/lib/purl/version.rb +1 -1
- data/purl-types.json +177 -180
- metadata +17 -5
- data/schemas/purl-types.schema.json +0 -154
- data/schemas/test-suite-data.schema.json +0 -134
- data/test-suite-data.json +0 -710
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c6c3fd2d17a4601a4c296b18ce67d41c423a5893aeb2b79cd484c0028863abd
|
|
4
|
+
data.tar.gz: 2c5e92ca69e3bb8442df158f34944fad645319cdbac5fb45875c7c8521be5d0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34e7c52f43f92146d605e06338f25b5d12f80f495e320901aed32206dc3e743029be9b51eb5bcd1f84ef46ca450a76ac45fa7c74da5b2506eb3d53b82db6c840
|
|
7
|
+
data.tar.gz: 7941f3f3cb65695448599c590e75ef06198fdafe4739e7587e0f7518f6e58fc948896452c38136a3c1a1e6e0d52acac4b766c2a5f2dd22d0a33f8b2e1b61feb1
|
data/.gitmodules
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.1] - 2025-08-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Remove arbitrary business logic validation, follow PURL spec for namespace requirements
|
|
14
|
+
|
|
15
|
+
## [1.3.0] - 2025-07-29
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- RFC 6570 URI templates for registry URL generation
|
|
19
|
+
- Advanced URL templating capabilities for dynamic registry URL construction
|
|
20
|
+
|
|
21
|
+
### Enhanced
|
|
22
|
+
- Registry URL generation now supports more flexible URL patterns
|
|
23
|
+
- Improved templating system for custom registry configurations
|
|
24
|
+
|
|
25
|
+
## [1.2.0] - 2025-07-27
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- Default registry URLs for 10 additional package types:
|
|
29
|
+
- `golang`: https://pkg.go.dev (Go package discovery site)
|
|
30
|
+
- `luarocks`: https://luarocks.org (Lua package repository)
|
|
31
|
+
- `clojars`: https://clojars.org (Clojure package repository)
|
|
32
|
+
- `elm`: https://package.elm-lang.org (Elm package catalog)
|
|
33
|
+
- `deno`: https://deno.land (Deno module registry)
|
|
34
|
+
- `homebrew`: https://formulae.brew.sh (Homebrew package browser)
|
|
35
|
+
- `bioconductor`: https://bioconductor.org (R bioinformatics packages)
|
|
36
|
+
- `huggingface`: https://huggingface.co (Machine learning models)
|
|
37
|
+
- `swift`: https://swiftpackageindex.com (Swift package index)
|
|
38
|
+
- `conan`: https://conan.io/center (C/C++ package center)
|
|
39
|
+
|
|
40
|
+
### Enhanced
|
|
41
|
+
- Registry configuration support for newly added package types
|
|
42
|
+
- Updated test suite to validate all new default registries
|
|
43
|
+
- Improved package type coverage with comprehensive registry URL mapping
|
|
44
|
+
|
|
45
|
+
### Configuration
|
|
46
|
+
- Updated `purl-types.json` to version 1.2.0 with enhanced registry configurations
|
|
47
|
+
- Added specialized registry handling for Go's unique import path structure
|
|
48
|
+
|
|
10
49
|
## [1.1.2] - 2025-07-25
|
|
11
50
|
|
|
12
51
|
### Added
|
data/README.md
CHANGED
|
@@ -385,6 +385,25 @@ rake spec:types
|
|
|
385
385
|
rake spec:verify_types
|
|
386
386
|
```
|
|
387
387
|
|
|
388
|
+
### Testing Against Official Specification
|
|
389
|
+
|
|
390
|
+
This library includes the official [purl-spec](https://github.com/package-url/purl-spec) repository as a git submodule for testing and validation:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Initialize submodule (first time only)
|
|
394
|
+
git submodule update --init --recursive
|
|
395
|
+
|
|
396
|
+
# Update submodule to latest spec
|
|
397
|
+
git submodule update --remote purl-spec
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
The tests use files from the submodule to:
|
|
401
|
+
- **Schema validation**: Validate our `purl-types.json` against the official schema in `purl-spec/schemas/`
|
|
402
|
+
- **Type compliance**: Ensure our supported types match the official types in `purl-spec/types/`
|
|
403
|
+
- **Test data**: Access official test cases and examples from `purl-spec/tests/`
|
|
404
|
+
|
|
405
|
+
The submodule is automatically updated weekly via Dependabot, ensuring tests stay current with the latest specification changes. When the submodule updates, you can review and merge the PR to adopt new spec requirements.
|
|
406
|
+
|
|
388
407
|
### Rake Tasks
|
|
389
408
|
|
|
390
409
|
- `rake spec:update` - Fetch latest test cases from official PURL spec repository
|
data/lib/purl/package_url.rb
CHANGED
|
@@ -74,13 +74,13 @@ module Purl
|
|
|
74
74
|
def initialize(type:, name:, namespace: nil, version: nil, qualifiers: nil, subpath: nil)
|
|
75
75
|
@type = validate_and_normalize_type(type)
|
|
76
76
|
@name = validate_name(name)
|
|
77
|
-
@namespace = validate_namespace(namespace)
|
|
77
|
+
@namespace = validate_namespace(namespace)
|
|
78
78
|
@version = validate_version(version) if version
|
|
79
79
|
@qualifiers = validate_qualifiers(qualifiers) if qualifiers
|
|
80
80
|
@subpath = validate_subpath(subpath) if subpath
|
|
81
81
|
|
|
82
|
-
#
|
|
83
|
-
|
|
82
|
+
# Apply post-validation normalization that depends on other components
|
|
83
|
+
apply_post_validation_normalization
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Parse a PURL string into a PackageURL object
|
|
@@ -407,7 +407,7 @@ module Purl
|
|
|
407
407
|
# PyPI names are case-insensitive and _ should be normalized to -
|
|
408
408
|
name_str.downcase.gsub("_", "-")
|
|
409
409
|
when "mlflow"
|
|
410
|
-
# MLflow name normalization
|
|
410
|
+
# MLflow name normalization happens after qualifiers are validated
|
|
411
411
|
name_str
|
|
412
412
|
when "composer"
|
|
413
413
|
# Composer names should be lowercase
|
|
@@ -418,6 +418,16 @@ module Purl
|
|
|
418
418
|
end
|
|
419
419
|
|
|
420
420
|
def validate_namespace(namespace)
|
|
421
|
+
# Check namespace requirements from spec
|
|
422
|
+
if namespace_required_for_type?(@type) && namespace.nil?
|
|
423
|
+
raise ValidationError.new(
|
|
424
|
+
"#{@type.capitalize} PURLs require a namespace per the type definition",
|
|
425
|
+
component: :namespace,
|
|
426
|
+
value: namespace,
|
|
427
|
+
rule: "#{@type.downcase} packages need namespace"
|
|
428
|
+
)
|
|
429
|
+
end
|
|
430
|
+
|
|
421
431
|
return nil if namespace.nil?
|
|
422
432
|
|
|
423
433
|
namespace_str = namespace.to_s.strip
|
|
@@ -516,110 +526,35 @@ module Purl
|
|
|
516
526
|
subpath_str
|
|
517
527
|
end
|
|
518
528
|
|
|
519
|
-
def
|
|
520
|
-
case
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
validate_cran_specific_rules
|
|
525
|
-
when "swift"
|
|
526
|
-
validate_swift_specific_rules
|
|
527
|
-
when "cpan"
|
|
528
|
-
validate_cpan_specific_rules
|
|
529
|
-
when "mlflow"
|
|
530
|
-
validate_mlflow_specific_rules
|
|
529
|
+
def apply_post_validation_normalization
|
|
530
|
+
# MLflow names are case sensitive or insensitive based on repository per spec
|
|
531
|
+
if @type&.downcase == "mlflow" && @qualifiers && @qualifiers["repository_url"] && @qualifiers["repository_url"].include?("azuredatabricks")
|
|
532
|
+
# Databricks MLflow is case insensitive - normalize to lowercase per spec
|
|
533
|
+
@name = @name.downcase
|
|
531
534
|
end
|
|
535
|
+
# Other MLflow repositories (like Azure ML) are case sensitive - no normalization needed
|
|
532
536
|
end
|
|
533
537
|
|
|
534
|
-
def
|
|
535
|
-
|
|
536
|
-
# it's ambiguous (test case 30)
|
|
537
|
-
if @namespace && (@qualifiers.nil? || (@qualifiers["user"].nil? && @qualifiers["channel"].nil?))
|
|
538
|
-
raise ValidationError.new(
|
|
539
|
-
"Conan PURLs with namespace require 'user' and/or 'channel' qualifiers to be unambiguous",
|
|
540
|
-
component: :qualifiers,
|
|
541
|
-
value: @qualifiers,
|
|
542
|
-
rule: "conan packages with namespace need user/channel qualifiers"
|
|
543
|
-
)
|
|
544
|
-
end
|
|
538
|
+
def namespace_required_for_type?(type)
|
|
539
|
+
return false unless type
|
|
545
540
|
|
|
546
|
-
#
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
"Conan PURLs with 'channel' qualifier require 'user' qualifier to be unambiguous",
|
|
551
|
-
component: :qualifiers,
|
|
552
|
-
value: @qualifiers,
|
|
553
|
-
rule: "conan packages with channel need user qualifier"
|
|
554
|
-
)
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
def validate_cran_specific_rules
|
|
559
|
-
# CRAN packages require a version to be unambiguous
|
|
560
|
-
if @version.nil?
|
|
561
|
-
raise ValidationError.new(
|
|
562
|
-
"CRAN PURLs require a version to be unambiguous",
|
|
563
|
-
component: :version,
|
|
564
|
-
value: @version,
|
|
565
|
-
rule: "cran packages need version"
|
|
566
|
-
)
|
|
567
|
-
end
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
def validate_swift_specific_rules
|
|
571
|
-
# Swift packages require a namespace to be unambiguous
|
|
572
|
-
if @namespace.nil?
|
|
573
|
-
raise ValidationError.new(
|
|
574
|
-
"Swift PURLs require a namespace to be unambiguous",
|
|
575
|
-
component: :namespace,
|
|
576
|
-
value: @namespace,
|
|
577
|
-
rule: "swift packages need namespace"
|
|
578
|
-
)
|
|
579
|
-
end
|
|
541
|
+
# Read from purl-types.json (included in gem)
|
|
542
|
+
types_data = self.class.purl_types_data
|
|
543
|
+
type_config = types_data.dig("types", type.downcase)
|
|
544
|
+
return false unless type_config
|
|
580
545
|
|
|
581
|
-
#
|
|
582
|
-
|
|
583
|
-
raise ValidationError.new(
|
|
584
|
-
"Swift PURLs require a version to be unambiguous",
|
|
585
|
-
component: :version,
|
|
586
|
-
value: @version,
|
|
587
|
-
rule: "swift packages need version"
|
|
588
|
-
)
|
|
589
|
-
end
|
|
590
|
-
end
|
|
591
|
-
|
|
592
|
-
def validate_mlflow_specific_rules
|
|
593
|
-
# MLflow names are case sensitive or insensitive based on repository
|
|
594
|
-
if @qualifiers && @qualifiers["repository_url"] && @qualifiers["repository_url"].include?("azuredatabricks")
|
|
595
|
-
# Azure Databricks MLflow is case insensitive - normalize to lowercase
|
|
596
|
-
@name = @name.downcase
|
|
597
|
-
end
|
|
598
|
-
# Other MLflow repositories are case sensitive - no normalization needed
|
|
546
|
+
# Check namespace_requirement field
|
|
547
|
+
type_config["namespace_requirement"] == "required"
|
|
599
548
|
end
|
|
600
549
|
|
|
601
|
-
def
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
component: :name,
|
|
610
|
-
value: @name,
|
|
611
|
-
rule: "cpan module vs distribution name conflict"
|
|
612
|
-
)
|
|
613
|
-
end
|
|
614
|
-
|
|
615
|
-
# Case 52: namespace with distribution-like name should be invalid
|
|
616
|
-
if @namespace == "GDT" && @name == "URI::PackageURL"
|
|
617
|
-
raise ValidationError.new(
|
|
618
|
-
"CPAN distribution name 'GDT/URI::PackageURL' has invalid format",
|
|
619
|
-
component: :name,
|
|
620
|
-
value: "#{@namespace}/#{@name}",
|
|
621
|
-
rule: "cpan distribution vs module name conflict"
|
|
622
|
-
)
|
|
550
|
+
def self.purl_types_data
|
|
551
|
+
@purl_types_data ||= begin
|
|
552
|
+
require "json"
|
|
553
|
+
types_file = File.join(File.dirname(__FILE__), "..", "..", "purl-types.json")
|
|
554
|
+
JSON.parse(File.read(types_file))
|
|
555
|
+
rescue
|
|
556
|
+
# Fallback to empty structure if file can't be read
|
|
557
|
+
{"types" => {}}
|
|
623
558
|
end
|
|
624
559
|
end
|
|
625
560
|
|
data/lib/purl/registry_url.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "addressable/template"
|
|
4
|
+
|
|
3
5
|
module Purl
|
|
4
6
|
class RegistryURL
|
|
5
7
|
# Load registry patterns from JSON configuration
|
|
6
8
|
def self.load_registry_patterns
|
|
7
9
|
@registry_patterns ||= begin
|
|
8
|
-
# Load
|
|
10
|
+
# Load extended registry configs
|
|
9
11
|
config_path = File.join(__dir__, "..", "..", "purl-types.json")
|
|
10
12
|
require "json"
|
|
11
13
|
config = JSON.parse(File.read(config_path))
|
|
@@ -16,37 +18,18 @@ module Purl
|
|
|
16
18
|
next unless type_config["registry_config"]
|
|
17
19
|
|
|
18
20
|
registry_config = type_config["registry_config"]
|
|
19
|
-
patterns[type] = build_pattern_config(type, registry_config)
|
|
21
|
+
patterns[type] = build_pattern_config(type, registry_config, type_config)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
patterns
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
def self.build_pattern_config(type, config)
|
|
27
|
-
# Get the default registry for this type from
|
|
28
|
-
type_config = load_types_config["types"][type]
|
|
28
|
+
def self.build_pattern_config(type, config, type_config)
|
|
29
|
+
# Get the default registry for this type from the extended config
|
|
29
30
|
default_registry = type_config["default_registry"]
|
|
30
31
|
|
|
31
|
-
#
|
|
32
|
-
route_patterns = []
|
|
33
|
-
if default_registry
|
|
34
|
-
# Add all template variations
|
|
35
|
-
if config["path_template"]
|
|
36
|
-
route_patterns << default_registry + config["path_template"]
|
|
37
|
-
end
|
|
38
|
-
if config["namespace_path_template"]
|
|
39
|
-
route_patterns << default_registry + config["namespace_path_template"]
|
|
40
|
-
end
|
|
41
|
-
if config["version_path_template"]
|
|
42
|
-
route_patterns << default_registry + config["version_path_template"]
|
|
43
|
-
end
|
|
44
|
-
if config["namespace_version_path_template"]
|
|
45
|
-
route_patterns << default_registry + config["namespace_version_path_template"]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
# Fall back to legacy route_patterns if available
|
|
49
|
-
route_patterns = config["route_patterns"] if route_patterns.empty? && config["route_patterns"]
|
|
32
|
+
# Route patterns are replaced by URI templates
|
|
50
33
|
|
|
51
34
|
# Build reverse regex from template or use legacy format
|
|
52
35
|
reverse_regex = nil
|
|
@@ -63,10 +46,13 @@ module Purl
|
|
|
63
46
|
|
|
64
47
|
{
|
|
65
48
|
base_url: config["base_url"] || (default_registry ? default_registry + config["path_template"]&.split('/:').first : nil),
|
|
66
|
-
route_patterns: route_patterns,
|
|
67
49
|
reverse_regex: reverse_regex,
|
|
68
50
|
pattern: build_generation_lambda(type, config, default_registry),
|
|
69
|
-
reverse_parser: reverse_regex ? build_reverse_parser(type, config) : nil
|
|
51
|
+
reverse_parser: reverse_regex ? build_reverse_parser(type, config) : nil,
|
|
52
|
+
uri_template: config["uri_template"] ? Addressable::Template.new(config["uri_template"]) : nil,
|
|
53
|
+
uri_template_no_namespace: config["uri_template_no_namespace"] ? Addressable::Template.new(config["uri_template_no_namespace"]) : nil,
|
|
54
|
+
uri_template_with_version: config["uri_template_with_version"] ? Addressable::Template.new(config["uri_template_with_version"]) : nil,
|
|
55
|
+
uri_template_with_version_no_namespace: config["uri_template_with_version_no_namespace"] ? Addressable::Template.new(config["uri_template_with_version_no_namespace"]) : nil
|
|
70
56
|
}
|
|
71
57
|
end
|
|
72
58
|
|
|
@@ -394,18 +380,41 @@ module Purl
|
|
|
394
380
|
pattern_config = REGISTRY_PATTERNS[type.to_s.downcase]
|
|
395
381
|
return [] unless pattern_config
|
|
396
382
|
|
|
397
|
-
|
|
383
|
+
# Generate route patterns from URI templates
|
|
384
|
+
patterns = []
|
|
385
|
+
|
|
386
|
+
if pattern_config[:uri_template]
|
|
387
|
+
patterns << uri_template_to_route_pattern(pattern_config[:uri_template])
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
if pattern_config[:uri_template_no_namespace]
|
|
391
|
+
patterns << uri_template_to_route_pattern(pattern_config[:uri_template_no_namespace])
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
if pattern_config[:uri_template_with_version]
|
|
395
|
+
patterns << uri_template_to_route_pattern(pattern_config[:uri_template_with_version])
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
if pattern_config[:uri_template_with_version_no_namespace]
|
|
399
|
+
patterns << uri_template_to_route_pattern(pattern_config[:uri_template_with_version_no_namespace])
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
patterns.uniq
|
|
398
403
|
end
|
|
399
404
|
|
|
400
405
|
def self.all_route_patterns
|
|
401
406
|
result = {}
|
|
402
407
|
REGISTRY_PATTERNS.each do |type, config|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
end
|
|
408
|
+
patterns = route_patterns_for(type)
|
|
409
|
+
result[type] = patterns unless patterns.empty?
|
|
406
410
|
end
|
|
407
411
|
result
|
|
408
412
|
end
|
|
413
|
+
|
|
414
|
+
private_class_method def self.uri_template_to_route_pattern(template)
|
|
415
|
+
# Convert URI template format {variable} to route pattern format :variable
|
|
416
|
+
template.pattern.gsub(/\{([^}]+)\}/, ':\1')
|
|
417
|
+
end
|
|
409
418
|
|
|
410
419
|
def initialize(purl)
|
|
411
420
|
@purl = purl
|
|
@@ -426,8 +435,12 @@ module Purl
|
|
|
426
435
|
if base_url
|
|
427
436
|
# Use custom base URL with the same URL structure
|
|
428
437
|
generate_with_custom_base_url(base_url, pattern_config)
|
|
438
|
+
elsif pattern_config[:uri_template]
|
|
439
|
+
# Use URI template if available
|
|
440
|
+
template = select_uri_template(pattern_config, include_version: false)
|
|
441
|
+
generate_with_uri_template(template)
|
|
429
442
|
else
|
|
430
|
-
#
|
|
443
|
+
# Fall back to legacy lambda pattern
|
|
431
444
|
pattern_config[:pattern].call(@purl)
|
|
432
445
|
end
|
|
433
446
|
rescue MissingRegistryInfoError
|
|
@@ -438,27 +451,121 @@ module Purl
|
|
|
438
451
|
end
|
|
439
452
|
|
|
440
453
|
def generate_with_version(base_url: nil)
|
|
441
|
-
|
|
454
|
+
return generate(base_url: base_url) unless @purl.version
|
|
455
|
+
|
|
456
|
+
pattern_config = REGISTRY_PATTERNS[@purl.type.downcase]
|
|
457
|
+
|
|
458
|
+
if base_url
|
|
459
|
+
# Use custom base URL with version
|
|
460
|
+
generate_with_custom_base_url_and_version(base_url, pattern_config)
|
|
461
|
+
elsif pattern_config[:uri_template_with_version] || pattern_config[:uri_template]
|
|
462
|
+
# Use version-specific URI template if available
|
|
463
|
+
template = select_uri_template(pattern_config, include_version: true)
|
|
464
|
+
generate_with_uri_template(template, include_version: true)
|
|
465
|
+
else
|
|
466
|
+
# Fall back to legacy version handling
|
|
467
|
+
registry_url = generate(base_url: base_url)
|
|
468
|
+
|
|
469
|
+
case @purl.type.downcase
|
|
470
|
+
when "npm"
|
|
471
|
+
"#{registry_url}/v/#{@purl.version}"
|
|
472
|
+
when "pypi"
|
|
473
|
+
"#{registry_url}#{@purl.version}/"
|
|
474
|
+
when "gem"
|
|
475
|
+
"#{registry_url}/versions/#{@purl.version}"
|
|
476
|
+
when "maven"
|
|
477
|
+
"#{registry_url}/#{@purl.version}"
|
|
478
|
+
when "nuget"
|
|
479
|
+
"#{registry_url}/#{@purl.version}"
|
|
480
|
+
else
|
|
481
|
+
registry_url
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
private
|
|
487
|
+
|
|
488
|
+
def select_uri_template(pattern_config, include_version: false)
|
|
489
|
+
if include_version
|
|
490
|
+
if @purl.namespace && pattern_config[:uri_template_with_version]
|
|
491
|
+
pattern_config[:uri_template_with_version]
|
|
492
|
+
elsif !@purl.namespace && pattern_config[:uri_template_with_version_no_namespace]
|
|
493
|
+
pattern_config[:uri_template_with_version_no_namespace]
|
|
494
|
+
elsif pattern_config[:uri_template_with_version]
|
|
495
|
+
pattern_config[:uri_template_with_version]
|
|
496
|
+
elsif @purl.namespace && pattern_config[:uri_template]
|
|
497
|
+
pattern_config[:uri_template]
|
|
498
|
+
elsif !@purl.namespace && pattern_config[:uri_template_no_namespace]
|
|
499
|
+
pattern_config[:uri_template_no_namespace]
|
|
500
|
+
else
|
|
501
|
+
pattern_config[:uri_template]
|
|
502
|
+
end
|
|
503
|
+
else
|
|
504
|
+
if @purl.namespace && pattern_config[:uri_template]
|
|
505
|
+
pattern_config[:uri_template]
|
|
506
|
+
elsif !@purl.namespace && pattern_config[:uri_template_no_namespace]
|
|
507
|
+
pattern_config[:uri_template_no_namespace]
|
|
508
|
+
else
|
|
509
|
+
pattern_config[:uri_template]
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def generate_with_uri_template(template, include_version: false)
|
|
515
|
+
variables = {
|
|
516
|
+
name: @purl.name
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
# Add namespace if present and required
|
|
520
|
+
if @purl.namespace
|
|
521
|
+
variables[:namespace] = @purl.namespace
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Add version if requested and present
|
|
525
|
+
if include_version && @purl.version
|
|
526
|
+
variables[:version] = @purl.version
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Handle namespace requirements based on package type
|
|
530
|
+
case @purl.type.downcase
|
|
531
|
+
when "composer", "maven", "swift", "elm"
|
|
532
|
+
unless @purl.namespace
|
|
533
|
+
raise MissingRegistryInfoError.new(
|
|
534
|
+
"#{@purl.type.capitalize} packages require a namespace",
|
|
535
|
+
type: @purl.type,
|
|
536
|
+
missing: "namespace"
|
|
537
|
+
)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# Build the URL manually to avoid encoding issues with special characters like @
|
|
542
|
+
result = template.pattern
|
|
543
|
+
|
|
544
|
+
variables.each do |key, value|
|
|
545
|
+
result = result.gsub("{#{key}}", value.to_s)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
result
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def generate_with_custom_base_url_and_version(custom_base_url, pattern_config)
|
|
552
|
+
# For now, fall back to the existing custom base URL method and add version
|
|
553
|
+
base_result = generate_with_custom_base_url(custom_base_url, pattern_config)
|
|
442
554
|
|
|
443
555
|
case @purl.type.downcase
|
|
444
556
|
when "npm"
|
|
445
|
-
|
|
557
|
+
"#{base_result}/v/#{@purl.version}"
|
|
446
558
|
when "pypi"
|
|
447
|
-
|
|
559
|
+
"#{base_result}#{@purl.version}/"
|
|
448
560
|
when "gem"
|
|
449
|
-
|
|
450
|
-
when "maven"
|
|
451
|
-
|
|
452
|
-
when "nuget"
|
|
453
|
-
@purl.version ? "#{registry_url}/#{@purl.version}" : registry_url
|
|
561
|
+
"#{base_result}/versions/#{@purl.version}"
|
|
562
|
+
when "maven", "nuget"
|
|
563
|
+
"#{base_result}/#{@purl.version}"
|
|
454
564
|
else
|
|
455
|
-
|
|
456
|
-
registry_url
|
|
565
|
+
base_result
|
|
457
566
|
end
|
|
458
567
|
end
|
|
459
568
|
|
|
460
|
-
private
|
|
461
|
-
|
|
462
569
|
def generate_with_custom_base_url(custom_base_url, pattern_config)
|
|
463
570
|
|
|
464
571
|
# Replace the base URL in the pattern lambda
|
data/lib/purl/version.rb
CHANGED