URI-PackageURL-2.22/0000755000175000017500000000000014730116003013011 5ustar peppepeppeURI-PackageURL-2.22/t/0000755000175000017500000000000014730116003013254 5ustar peppepeppeURI-PackageURL-2.22/t/90-cpan-distname-info.t0000644000175000017500000000546714650267662017402 0ustar peppepeppe#!perl use v5.10; use Test::More; use Data::Dumper; use CPAN::DistnameInfo; use URI::PackageURL; while (my $file = ) { chomp $file; my $d = CPAN::DistnameInfo->new($file); next unless $d->cpanid; next unless $d->dist; subtest "$file" => sub { my $qualifiers = {}; # "tar.gz" is the default extension for CPAN distributions if ($d->extension ne 'tar.gz') { $qualifiers->{ext} = $d->extension; } my $purl = URI::PackageURL->new( type => 'cpan', namespace => $d->cpanid, name => $d->dist, version => $d->version, qualifiers => $qualifiers ); ok($purl, "Conversion: $file --> $purl"); my $purl2 = URI::PackageURL->from_string($purl->to_string); is($d->cpanid, $purl2->namespace, 'dist(cpanid) == purl(namespace)'); is($d->dist, $purl2->name, 'dist(dist) == purl(name)'); is($d->version, $purl2->version, 'dist(version) == purl(version)'); }; } done_testing(); __DATA__ CPAN/authors/id/J/JA/JAMCC/ngb-101.zip CPAN/authors/id/J/JS/JSHY/DateTime-Fiscal-Year-0.01.tar.gz CPAN/authors/id/G/GA/GARY/Math-BigInteger-1.0.tar.gz CPAN/authors/id/T/TE/TERRY/VoiceXML-Server-1.6.tar.gz CPAN/authors/id/J/JA/JAMCC/ngb-100.tar.gz CPAN/authors/id/J/JS/JSHY/DateTime-Fiscal-Year-0.02.tar.gz CPAN/authors/id/G/GA/GARY/Crypt-DES-1.0.tar.gz CPAN/authors/id/G/GA/GARY/Stream-1.00.tar.gz CPAN/authors/id/T/TM/TMAEK/DBIx-Cursor-0.14.tar.gz CPAN/authors/id/G/GA/GARY/Crypt-IDEA-1.0.tar.gz CPAN/authors/id/G/GA/GARY/Math-TrulyRandom-1.0.tar.gz CPAN/authors/id/T/TE/TERRY/VoiceXML-Server-1.13.tar.gz JWILLIAMS/MasonX-Lexer-MSP-0.02.tar.gz CPAN/authors/id/J/JA/JAMCC/Tie-CacheHash-0.50.tar.gz CPAN/authors/id/T/TM/TMAEK/DBIx-Cursor-0.13.tar.gz CPAN/authors/id/J/JD/JDUTTON/Parse-RandGen-0.100.tar.gz id/N/NI/NI-S/Tk400.202.tar.gz authors/id/G/GB/GBARR/perl5.005_03.tar.gz M/MS/MSCHWERN/Test-Simple-0.48_01.tar.gz id/J/JV/JV/PostScript-Font-1.09.tar.gz id/I/IB/IBMTORDB2/DBD-DB2-0.77.tar.gz id/I/IB/IBMTORDB2/DBD-DB2-0.99.tar.bz2 CPAN/authors/id/L/LD/LDS/CGI.pm-2.34.tar.gz CPAN/authors/id/J/JE/JESSE/perl-5.12.0-RC0.tar.gz CPAN/authors/id/G/GS/GSAR/perl-5.6.1-TRIAL3.tar.gz CPAN/authors/id/R/RJ/RJBS/Dist-Zilla-2.100860-TRIAL.tar.gz CPAN/authors/id/M/MI/MINGYILIU/Bio-ASN1-EntrezGene-1.10-withoutworldwriteables.tar.gz CPAN/authors/id/I/IL/ILYAZ/modules/Term-Gnuplot-0.90380906.zip CPAN/authors/id/S/SA/SANDEEPV/GuiBuilder_v0_3.zip CPAN/authors/id/I/IL/ILYAZ/modules/Term-Gnuplot-0.90380906.zip BDFOY/authors/id/B/BD/BDFOY/Mojolicious-Plugin-DirectoryServer-1.003.tar KMACLEOD/Frontier-RPC-0.07b4.tar.gz RTFIREFLY/Frontier-RPC-0.07b4p1.tar.gz AJPEACOCK/HTML-Table-2.08a.tar.gz DANPEDER/MIME-Base32-1.02a.tar.gz CPAN/authors/id/G/GD/GDT/URI-PackageURL-2.21.tar.gz URI-PackageURL-2.22/t/40-cli.t0000644000175000017500000000262114650267662014455 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; use JSON::PP qw(decode_json); use URI::PackageURL::App; use URI::VersionRange::App; sub cmd { my ($class, @arguments) = @_; my $output; open(my $output_handle, '>', \$output) or die "Can't open handle file: $!"; my $original_handle = select $output_handle; $class->run(@arguments); chomp $output; select $original_handle; return $output; } my $t1 = 'pkg:cpan/GDT/URI-PackageURL@2.21'; my $t2 = 'vers:cpan/1.00|>=2.00|<5.00'; subtest "URI::PackageURL::App - '$t1' (JSON output)" => sub { my $test_1 = cmd('URI::PackageURL::App', $t1, '--json'); ok($test_1, 'Parse PackageURL string to JSON'); my $test_2 = eval { decode_json($test_1) }; ok($test_2, 'Valid JSON output'); is($test_2->{type}, 'cpan', 'JSON output: Type'); is($test_2->{namespace}, 'GDT', 'JSON output: Namespace'); is($test_2->{name}, 'URI-PackageURL', 'JSON output: Name'); is($test_2->{version}, '2.21', 'JSON output: Version'); }; subtest "URI::VersionRange::App - '$t2' (JSON output)" => sub { my $test_1 = cmd('URI::VersionRange::App', $t2, '--json'); ok($test_1, 'Parse Version Range string to JSON'); my $test_2 = eval { decode_json($test_1) }; ok($test_2, 'Valid JSON output'); is($test_2->{scheme}, 'cpan', 'JSON output: Scheme'); }; done_testing(); URI-PackageURL-2.22/t/10-encode.t0000644000175000017500000000534614620241420015124 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; use URI::PackageURL qw(encode_purl); my @TESTS = ( {purl => 'pkg:cpan/Perl::Version@1.013', type => 'cpan', name => 'Perl::Version', version => '1.013'}, { purl => 'pkg:cpan/DROLSKY/DateTime@1.55', type => 'cpan', namespace => 'DROLSKY', name => 'DateTime', version => '1.55' }, {purl => 'pkg:cpan/DateTime@1.55', type => 'cpan', name => 'DateTime', version => '1.55'}, {purl => 'pkg:cpan/GDT/URI-PackageURL', type => 'cpan', namespace => 'GDT', name => 'URI-PackageURL',}, {purl => 'pkg:cpan/LWP::UserAgent', type => 'cpan', name => 'LWP::UserAgent'}, { purl => 'pkg:cpan/OALDERS/libwww-perl@6.76', type => 'cpan', namespace => 'OALDERS', name => 'libwww-perl', version => '6.76' }, {purl => 'pkg:cpan/URI', type => 'cpan', name => 'URI'}, { purl => 'pkg:generic/100%25/100%25@100%25?repository_url=https://example.com/100%2525/#100%25', type => 'generic', namespace => '100%', name => '100%', version => '100%', qualifiers => {'repository_url' => 'https://example.com/100%25/'}, subpath => '100%', }, {purl => 'pkg:brew/openssl%401.1@1.1.1w', type => 'brew', name => 'openssl@1.1', version => '1.1.1w'}, {purl => 'pkg:cpan/Storable@0.6%402', type => 'cpan', name => 'Storable', version => '0.6@2'}, { purl => 'pkg:cpan/Storable@0.5%403-bin-1-MacOS', type => 'cpan', name => 'Storable', version => '0.5@3-bin-1-MacOS' }, ); foreach my $test (@TESTS) { my $expected_purl = $test->{purl}; subtest "$expected_purl" => sub { my $got_purl_1 = encode_purl( type => $test->{type}, namespace => $test->{namespace}, name => $test->{name}, version => $test->{version}, qualifiers => $test->{qualifiers}, subpath => $test->{subpath}, ); my $got_purl_2 = URI::PackageURL->new( type => $test->{type}, namespace => $test->{namespace}, name => $test->{name}, version => $test->{version}, qualifiers => $test->{qualifiers}, subpath => $test->{subpath}, ); my $got_purl_3 = URI::PackageURL->from_string($expected_purl)->to_string; is($got_purl_1, $expected_purl, "encode_purl --> $got_purl_1"); is($got_purl_2, $expected_purl, "URI::PackageURL --> $got_purl_2"); is($got_purl_3, $expected_purl, "decode+encode --> $got_purl_3"); }; } done_testing(); URI-PackageURL-2.22/t/30-util.t0000644000175000017500000000740114727631674014666 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; use URI::PackageURL::Util qw(purl_to_urls); my @tests = ( { purl => 'pkg:cpan/GDT/URI-PackageURL@2.01', download_url => 'https://www.cpan.org/authors/id/G/GD/GDT/URI-PackageURL-2.01.tar.gz', repository_url => 'https://metacpan.org/release/GDT/URI-PackageURL-2.01' }, { purl => 'pkg:github/package-url/purl-spec@40d01e26f9ae0af6b50a1309e6b089c14d6d2244', download_url => 'https://github.com/package-url/purl-spec/archive/40d01e26f9ae0af6b50a1309e6b089c14d6d2244.tar.gz', repository_url => 'https://github.com/package-url/purl-spec' }, { purl => 'pkg:gitlab/gitlab-org/gitlab-runner@v16.0.2', download_url => 'https://gitlab.com/gitlab-org/gitlab-runner/-/archive/v16.0.2/gitlab-runner-v16.0.2.tar.gz', repository_url => 'https://gitlab.com/gitlab-org/gitlab-runner' }, { purl => 'pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c', download_url => 'https://bitbucket.org/birkenfeld/pygments-main/get/244fd47e07d1014f0aed9c.tar.gz', repository_url => 'https://bitbucket.org/birkenfeld/pygments-main' }, { purl => 'pkg:gem/ruby-advisory-db-check@0.0.4', repository_url => 'https://rubygems.org/gems/ruby-advisory-db-check/versions/0.0.4', download_url => 'https://rubygems.org/downloads/ruby-advisory-db-check-0.0.4.gem' }, { purl => 'pkg:cargo/rand@0.7.2', repository_url => 'https://crates.io/crates/rand/0.7.2', download_url => 'https://crates.io/api/v1/crates/rand/0.7.2/download' }, { purl => 'pkg:npm/%40angular/animations@12.2.17', repository_url => 'https://www.npmjs.com/package/@angular/animations/v/12.2.17', download_url => 'https://registry.npmjs.org/@angular/animations/-/animations-12.2.17.tgz' }, { purl => 'pkg:nuget/EnterpriseLibrary.Common@6.0.1304', repository_url => 'https://www.nuget.org/packages/EnterpriseLibrary.Common/6.0.1304', download_url => 'https://www.nuget.org/api/v2/package/EnterpriseLibrary.Common/6.0.1304' }, { purl => 'pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?packaging=sources', repository_url => 'https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-anim/1.9.1', download_url => 'https://repo.maven.apache.org/maven2/org/apache/xmlgraphics/batik-anim/1.9.1/batik-anim-1.9.1.jar' }, {purl => 'pkg:pypi/django@1.11.1', repository_url => 'https://pypi.org/project/django/1.11.1'}, {purl => 'pkg:composer/laravel/laravel@5.5.0', repository_url => 'https://packagist.org/packages/laravel/laravel'}, {purl => 'pkg:docker/cassandra@latest', repository_url => 'https://hub.docker.com/_/cassandra'}, { purl => 'pkg:docker/smartentry/debian@dc437cc87d10', repository_url => 'https://hub.docker.com/r/smartentry/debian' }, { purl => 'pkg:github/nexb/scancode-toolkit@v3.1.1', download_url => 'https://github.com/nexb/scancode-toolkit/archive/refs/tags/v3.1.1.tar.gz', repository_url => 'https://github.com/nexb/scancode-toolkit' } ); foreach my $test (@tests) { my $purl = $test->{purl}; my $download_url = $test->{download_url}; my $repository_url = $test->{repository_url}; subtest "'$purl' URLs" => sub { my $urls = purl_to_urls($purl); is($urls->{download}, $download_url, 'Download URL') if defined $urls->{download}; is($urls->{repository}, $repository_url, 'Repository URL') if defined $urls->{repository}; }; } done_testing(); URI-PackageURL-2.22/t/20-decode.t0000644000175000017500000000532014730114237015112 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; use URI::PackageURL; my $t1 = 'pkg:cpan/GDT/URI-PackageURL@2.22'; my $t2 = 'pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie'; my $t3 = 'pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations'; my $t4 = 'pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io'; my $t5 = 'pkg:generic/ns/n@m#?@version?qualifier=#v@lue#subp@th?'; subtest "Decode '$t1'" => sub { my $purl = decode_purl($t1); is($purl->type, 'cpan', 'Type'); is($purl->namespace, 'GDT', 'Namespace'); is($purl->name, 'URI-PackageURL', 'Name'); is($purl->version, '2.22', 'Version'); is($purl->to_string, $t1, 'PackageURL'); }; subtest "Decode '$t2'" => sub { my $purl = decode_purl($t2); is($purl->type, 'deb', 'Type'); is($purl->namespace, 'debian', 'Namespace'); is($purl->name, 'curl', 'Name'); is($purl->version, '7.50.3-1', 'Version'); is($purl->qualifiers->{arch}, 'i386', 'Qualifier "arch"'); is($purl->qualifiers->{distro}, 'jessie', 'Qualifier "distro"'); is($purl->to_string, $t2, 'PackageURL'); }; subtest "Decode '$t3'" => sub { my $purl = decode_purl($t3); is($purl->type, 'golang', 'Type'); is($purl->namespace, 'google.golang.org', 'Namespace'); is($purl->name, 'genproto', 'Name'); is($purl->version, 'abcdedf', 'Version'); is($purl->subpath, 'googleapis/api/annotations', 'Subpath'); is($purl->to_string, $t3, 'PackageURL'); }; subtest "Decode '$t4'" => sub { my $purl = decode_purl($t4); is($purl->type, 'docker', 'Type'); is($purl->namespace, 'customer', 'Namespace'); is($purl->name, 'dockerimage', 'Name'); is($purl->version, 'sha256:244fd47e07d1004f0aed9c', 'Version'); is($purl->qualifiers->{repository_url}, 'gcr.io', 'Qualifier "repository_url"'); is($purl->to_string, $t4, 'PackageURL'); }; subtest "Decode '$t5'" => sub { my $purl = decode_purl($t5); is($purl->type, 'generic', 'Type'); is($purl->namespace, 'ns', 'Namespace'); is($purl->name, 'n@m#?', 'Name'); is($purl->version, 'version', 'Version'); is($purl->qualifiers->{qualifier}, '#v@lue', 'Qualifier "qualifier"'); is($purl->subpath, 'subp@th?', 'Subpath'); }; done_testing(); URI-PackageURL-2.22/t/sync-purl-test-suite-data.sh0000644000175000017500000000060514615733420020572 0ustar peppepeppe#!/bin/sh # sync-purl-test-suite-data - Sync the PackageURL test suite data # (C) 2024, Giuseppe Di Terlizzi cd $(dirname $0) ; CWD=$(pwd) echo "Remove old PackageURL test suite file" rm test-suite-data.json echo "Download new PackageURL test suite file" wget https://raw.githubusercontent.com/package-url/purl-spec/master/test-suite-data.json exit 0 URI-PackageURL-2.22/t/50-version-range.t0000644000175000017500000000275214727620533016466 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; use URI::VersionRange; my $vers = 'vers:cpan/>v1.00|!=v2.10|<=v3.00'; my $v1 = URI::VersionRange->from_string($vers); my $v2 = URI::VersionRange->new( scheme => 'cpan', constraints => [ URI::VersionRange::Constraint->new(comparator => '>', version => 'v1.00'), URI::VersionRange::Constraint->new(comparator => '!=', version => 'v2.10'), URI::VersionRange::Constraint->new(comparator => '<=', version => 'v3.00') ] ); my $v3 = URI::VersionRange->new(scheme => 'cpan', constraints => ['>v1.00', '!=v2.10', '<=v3.00']); my $v4 = decode_vers($vers); my $v5 = decode_vers(encode_vers(scheme => 'cpan', constraints => ['>v1.00', '!=v2.10', '<=v3.00'])); my %TESTS = ( 'from_string' => $v1, 'object-oriented #1' => $v2, 'object-oriented #2' => $v3, 'decode_vers' => $v4, 'encode_vers + decode_vers' => $v5, ); my @in_range = ('v2.11', 'v2.99', 'v3.00'); my @not_in_range = ('v0.01', 'v0.99', 'v2.10'); foreach (sort keys %TESTS) { is $v1, $TESTS{$_}, "Version range ($_)"; } is $v1->contains($_), !!1, "$_ version in range ($v1)" for (sort @in_range); is $v1->contains($_), !!0, "$_ version not in range ($v1)" for (sort @not_in_range); is decode_vers('vers:cpan/contains($_), !!1, "$_ version in range" for (sort @in_range); eval { decode_vers('foo:bar{description}; my $purl = eval { URI::PackageURL->new( type => $test->{type}, namespace => $test->{namespace}, name => $test->{name}, version => $test->{version}, qualifiers => $test->{qualifiers}, subpath => $test->{subpath} ); }; if ($test->{is_invalid}) { like($@, qr/Invalid Package URL/i, "ENCODE: $test_name"); return; } if (!$test->{is_invalid} && $@) { fail("ENCODE: $test_name"); return; } if (!$test->{is_invalid}) { is($purl->to_string, $test->{canonical_purl}, "ENCODE: $test_name"); return; } } sub test_purl_decode { my ($test) = @_; my $test_name = $test->{description}; my $purl = eval { URI::PackageURL->from_string($test->{purl}) }; if ($test->{is_invalid}) { like($@, qr/(Invalid|Malformed) Package URL/i, "DECODE: $test_name"); return; } if (!$test->{is_invalid} && $@) { fail("DECODE: $test_name"); return; } if (!$test->{is_invalid}) { is($purl->to_string, $test->{canonical_purl}, "DECODE: $test_name"); return; } } my $test_suite_file = File::Spec->catfile('t', 'test-suite-data.json'); BAIL_OUT('"test-suite-data.json" file not found') if (!-e $test_suite_file); open my $fh, '<', $test_suite_file or Carp::croak "Can't open file: $!"; my $test_suite_content = do { local $/; <$fh> }; my $test_suite_data = JSON::PP::decode_json($test_suite_content); foreach my $test (@{$test_suite_data}) { test_purl_encode($test); test_purl_decode($test); } done_testing(); URI-PackageURL-2.22/t/pod.t0000644000175000017500000000052014442172325014231 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; unless ($ENV{RELEASE_TESTING}) { plan(skip_all => "Author tests not required for installation"); } # Ensure a recent version of Test::Pod my $min_tp = 1.22; eval "use Test::Pod $min_tp"; plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; all_pod_files_ok(); URI-PackageURL-2.22/t/00-load.t0000644000175000017500000000056514620232415014607 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; use_ok('URI::PackageURL'); use_ok('URI::PackageURL::Util'); use_ok('URI::PackageURL::App'); use_ok('URI::VersionRange'); use_ok('URI::VersionRange::Constraint'); use_ok('URI::VersionRange::Version'); use_ok('URI::VersionRange::App'); done_testing(); diag("URI::PackageURL $URI::PackageURL::VERSION, Perl $], $^X"); URI-PackageURL-2.22/t/manifest.t0000644000175000017500000000045214442172325015261 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; unless ($ENV{RELEASE_TESTING}) { plan(skip_all => "Author tests not required for installation"); } my $min_tcm = 0.9; eval "use Test::CheckManifest $min_tcm"; plan skip_all => "Test::CheckManifest $min_tcm required" if $@; ok_manifest(); URI-PackageURL-2.22/t/pod-coverage.t0000644000175000017500000000122214442172325016022 0ustar peppepeppe#!perl -T use strict; use warnings; use Test::More; unless ($ENV{RELEASE_TESTING}) { plan(skip_all => "Author tests not required for installation"); } # Ensure a recent version of Test::Pod::Coverage my $min_tpc = 1.08; eval "use Test::Pod::Coverage $min_tpc"; plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" if $@; # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, # but older versions don't recognize some common documentation styles my $min_pc = 0.18; eval "use Pod::Coverage $min_pc"; plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" if $@; all_pod_coverage_ok(); URI-PackageURL-2.22/t/test-suite-data.json0000644000175000017500000005066414727621070017212 0ustar peppepeppe[ { "description": "valid maven purl", "purl": "pkg:maven/org.apache.commons/io@1.3.4", "canonical_purl": "pkg:maven/org.apache.commons/io@1.3.4", "type": "maven", "namespace": "org.apache.commons", "name": "io", "version": "1.3.4", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "basic valid maven purl without version", "purl": "pkg:maven/org.apache.commons/io", "canonical_purl": "pkg:maven/org.apache.commons/io", "type": "maven", "namespace": "org.apache.commons", "name": "io", "version": null, "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "valid go purl without version and with subpath", "purl": "pkg:GOLANG/google.golang.org/genproto#/googleapis/api/annotations/", "canonical_purl": "pkg:golang/google.golang.org/genproto#googleapis/api/annotations", "type": "golang", "namespace": "google.golang.org", "name": "genproto", "version": null, "qualifiers": null, "subpath": "googleapis/api/annotations", "is_invalid": false }, { "description": "valid go purl with version and subpath", "purl": "pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/api/annotations/", "canonical_purl": "pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations", "type": "golang", "namespace": "google.golang.org", "name": "genproto", "version": "abcdedf", "qualifiers": null, "subpath": "googleapis/api/annotations", "is_invalid": false }, { "description": "bitbucket namespace and name should be lowercased", "purl": "pkg:bitbucket/birKenfeld/pyGments-main@244fd47e07d1014f0aed9c", "canonical_purl": "pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c", "type": "bitbucket", "namespace": "birkenfeld", "name": "pygments-main", "version": "244fd47e07d1014f0aed9c", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "github namespace and name should be lowercased", "purl": "pkg:github/Package-url/purl-Spec@244fd47e07d1004f0aed9c", "canonical_purl": "pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c", "type": "github", "namespace": "package-url", "name": "purl-spec", "version": "244fd47e07d1004f0aed9c", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "debian can use qualifiers", "purl": "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie", "canonical_purl": "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie", "type": "deb", "namespace": "debian", "name": "curl", "version": "7.50.3-1", "qualifiers": {"arch": "i386", "distro": "jessie"}, "subpath": null, "is_invalid": false }, { "description": "docker uses qualifiers and hash image id as versions", "purl": "pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io", "canonical_purl": "pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io", "type": "docker", "namespace": "customer", "name": "dockerimage", "version": "sha256:244fd47e07d1004f0aed9c", "qualifiers": {"repository_url": "gcr.io"}, "subpath": null, "is_invalid": false }, { "description": "Java gem can use a qualifier", "purl": "pkg:gem/jruby-launcher@1.1.2?Platform=java", "canonical_purl": "pkg:gem/jruby-launcher@1.1.2?platform=java", "type": "gem", "namespace": null, "name": "jruby-launcher", "version": "1.1.2", "qualifiers": {"platform": "java"}, "subpath": null, "is_invalid": false }, { "description": "maven often uses qualifiers", "purl": "pkg:Maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources&repositorY_url=repo.spring.io/release", "canonical_purl": "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources&repository_url=repo.spring.io/release", "type": "maven", "namespace": "org.apache.xmlgraphics", "name": "batik-anim", "version": "1.9.1", "qualifiers": {"classifier": "sources", "repository_url": "repo.spring.io/release"}, "subpath": null, "is_invalid": false }, { "description": "maven pom reference", "purl": "pkg:Maven/org.apache.xmlgraphics/batik-anim@1.9.1?extension=pom&repositorY_url=repo.spring.io/release", "canonical_purl": "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?extension=pom&repository_url=repo.spring.io/release", "type": "maven", "namespace": "org.apache.xmlgraphics", "name": "batik-anim", "version": "1.9.1", "qualifiers": {"extension": "pom", "repository_url": "repo.spring.io/release"}, "subpath": null, "is_invalid": false }, { "description": "maven can come with a type qualifier", "purl": "pkg:Maven/net.sf.jacob-project/jacob@1.14.3?classifier=x86&type=dll", "canonical_purl": "pkg:maven/net.sf.jacob-project/jacob@1.14.3?classifier=x86&type=dll", "type": "maven", "namespace": "net.sf.jacob-project", "name": "jacob", "version": "1.14.3", "qualifiers": {"classifier": "x86", "type": "dll"}, "subpath": null, "is_invalid": false }, { "description": "npm can be scoped", "purl": "pkg:npm/%40angular/animation@12.3.1", "canonical_purl": "pkg:npm/%40angular/animation@12.3.1", "type": "npm", "namespace": "@angular", "name": "animation", "version": "12.3.1", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "nuget names are case sensitive", "purl": "pkg:Nuget/EnterpriseLibrary.Common@6.0.1304", "canonical_purl": "pkg:nuget/EnterpriseLibrary.Common@6.0.1304", "type": "nuget", "namespace": null, "name": "EnterpriseLibrary.Common", "version": "6.0.1304", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "pypi names have special rules and not case sensitive", "purl": "pkg:PYPI/Django_package@1.11.1.dev1", "canonical_purl": "pkg:pypi/django-package@1.11.1.dev1", "type": "pypi", "namespace": null, "name": "django-package", "version": "1.11.1.dev1", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "rpm often use qualifiers", "purl": "pkg:Rpm/fedora/curl@7.50.3-1.fc25?Arch=i386&Distro=fedora-25", "canonical_purl": "pkg:rpm/fedora/curl@7.50.3-1.fc25?arch=i386&distro=fedora-25", "type": "rpm", "namespace": "fedora", "name": "curl", "version": "7.50.3-1.fc25", "qualifiers": {"arch": "i386", "distro": "fedora-25"}, "subpath": null, "is_invalid": false }, { "description": "a scheme is always required", "purl": "EnterpriseLibrary.Common@6.0.1304", "canonical_purl": "EnterpriseLibrary.Common@6.0.1304", "type": null, "namespace": null, "name": "EnterpriseLibrary.Common", "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "a type is always required", "purl": "pkg:EnterpriseLibrary.Common@6.0.1304", "canonical_purl": "pkg:EnterpriseLibrary.Common@6.0.1304", "type": null, "namespace": null, "name": "EnterpriseLibrary.Common", "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "a name is required", "purl": "pkg:maven/@1.3.4", "canonical_purl": "pkg:maven/@1.3.4", "type": "maven", "namespace": null, "name": null, "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "slash / after scheme is not significant", "purl": "pkg:/maven/org.apache.commons/io", "canonical_purl": "pkg:maven/org.apache.commons/io", "type": "maven", "namespace": "org.apache.commons", "name": "io", "version": null, "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "double slash // after scheme is not significant", "purl": "pkg://maven/org.apache.commons/io", "canonical_purl": "pkg:maven/org.apache.commons/io", "type": "maven", "namespace": "org.apache.commons", "name": "io", "version": null, "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "slash /// after type is not significant", "purl": "pkg:///maven/org.apache.commons/io", "canonical_purl": "pkg:maven/org.apache.commons/io", "type": "maven", "namespace": "org.apache.commons", "name": "io", "version": null, "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "valid maven purl with case sensitive namespace and name", "purl": "pkg:maven/HTTPClient/HTTPClient@0.3-3", "canonical_purl": "pkg:maven/HTTPClient/HTTPClient@0.3-3", "type": "maven", "namespace": "HTTPClient", "name": "HTTPClient", "version": "0.3-3", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "valid maven purl containing a space in the version and qualifier", "purl": "pkg:maven/mygroup/myartifact@1.0.0%20Final?mykey=my%20value", "canonical_purl": "pkg:maven/mygroup/myartifact@1.0.0%20Final?mykey=my%20value", "type": "maven", "namespace": "mygroup", "name": "myartifact", "version": "1.0.0 Final", "qualifiers": {"mykey": "my value"}, "subpath": null, "is_invalid": false }, { "description": "checks for invalid qualifier keys", "purl": "pkg:npm/myartifact@1.0.0?in%20production=true", "canonical_purl": null, "type": "npm", "namespace": null, "name": "myartifact", "version": "1.0.0", "qualifiers": {"in production": "true"}, "subpath": null, "is_invalid": true }, { "description": "valid conan purl", "purl": "pkg:conan/cctz@2.3", "canonical_purl": "pkg:conan/cctz@2.3", "type": "conan", "namespace": null, "name": "cctz", "version": "2.3", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "valid conan purl with namespace and qualifier channel", "purl": "pkg:conan/bincrafters/cctz@2.3?channel=stable", "canonical_purl": "pkg:conan/bincrafters/cctz@2.3?channel=stable", "type": "conan", "namespace": "bincrafters", "name": "cctz", "version": "2.3", "qualifiers": {"channel": "stable"}, "subpath": null, "is_invalid": false }, { "description": "invalid conan purl only namespace", "purl": "pkg:conan/bincrafters/cctz@2.3", "canonical_purl": "pkg:conan/bincrafters/cctz@2.3", "type": "conan", "namespace": "bincrafters", "name": "cctz", "version": "2.3", "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "invalid conan purl only channel qualifier", "purl": "pkg:conan/cctz@2.3?channel=stable", "canonical_purl": "pkg:conan/cctz@2.3?channel=stable", "type": "conan", "namespace": null, "name": "cctz", "version": "2.3", "qualifiers": {"channel": "stable"}, "subpath": null, "is_invalid": true }, { "description": "valid conda purl with qualifiers", "purl": "pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2", "canonical_purl": "pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2", "type": "conda", "namespace": null, "name": "absl-py", "version": "0.4.1", "qualifiers": {"build": "py36h06a4308_0", "channel": "main", "subdir": "linux-64", "type": "tar.bz2"}, "subpath": null, "is_invalid": false }, { "description": "valid cran purl", "purl": "pkg:cran/A3@0.9.1", "canonical_purl": "pkg:cran/A3@0.9.1", "type": "cran", "namespace": null, "name": "A3", "version": "0.9.1", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "invalid cran purl without name", "purl": "pkg:cran/@0.9.1", "canonical_purl": "pkg:cran/@0.9.1", "type": "cran", "namespace": null, "name": null, "version": "0.9.1", "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "invalid cran purl without version", "purl": "pkg:cran/A3", "canonical_purl": "pkg:cran/A3", "type": "cran", "namespace": null, "name": "A3", "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "valid swift purl", "purl": "pkg:swift/github.com/Alamofire/Alamofire@5.4.3", "canonical_purl": "pkg:swift/github.com/Alamofire/Alamofire@5.4.3", "type": "swift", "namespace": "github.com/Alamofire", "name": "Alamofire", "version": "5.4.3", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "invalid swift purl without namespace", "purl": "pkg:swift/Alamofire@5.4.3", "canonical_purl": "pkg:swift/Alamofire@5.4.3", "type": "swift", "namespace": null, "name": "Alamofire", "version": "5.4.3", "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "invalid swift purl without name", "purl": "pkg:swift/github.com/Alamofire/@5.4.3", "canonical_purl": "pkg:swift/github.com/Alamofire/@5.4.3", "type": "swift", "namespace": "github.com/Alamofire", "name": null, "version": "5.4.3", "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "invalid swift purl without version", "purl": "pkg:swift/github.com/Alamofire/Alamofire", "canonical_purl": "pkg:swift/github.com/Alamofire/Alamofire", "type": "swift", "namespace": "github.com/Alamofire", "name": "Alamofire", "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "valid hackage purl", "purl": "pkg:hackage/AC-HalfInteger@1.2.1", "canonical_purl": "pkg:hackage/AC-HalfInteger@1.2.1", "type": "hackage", "namespace": null, "name": "AC-HalfInteger", "version": "1.2.1", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "name and version are always required", "purl": "pkg:hackage", "canonical_purl": "pkg:hackage", "type": "hackage", "namespace": null, "name": null, "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "minimal Hugging Face model", "purl": "pkg:huggingface/distilbert-base-uncased@043235d6088ecd3dd5fb5ca3592b6913fd516027", "canonical_purl": "pkg:huggingface/distilbert-base-uncased@043235d6088ecd3dd5fb5ca3592b6913fd516027", "type": "huggingface", "namespace": null, "name": "distilbert-base-uncased", "version": "043235d6088ecd3dd5fb5ca3592b6913fd516027", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "Hugging Face model with staging endpoint", "purl": "pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co", "canonical_purl": "pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co", "type": "huggingface", "namespace": "microsoft", "name": "deberta-v3-base", "version": "559062ad13d311b87b2c455e67dcd5f1c8f65111", "qualifiers": {"repository_url": "https://hub-ci.huggingface.co"}, "subpath": null, "is_invalid": false }, { "description": "Hugging Face model with various cases", "purl": "pkg:huggingface/EleutherAI/gpt-neo-1.3B@797174552AE47F449AB70B684CABCB6603E5E85E", "canonical_purl": "pkg:huggingface/EleutherAI/gpt-neo-1.3B@797174552ae47f449ab70b684cabcb6603e5e85e", "type": "huggingface", "namespace": "EleutherAI", "name": "gpt-neo-1.3B", "version": "797174552ae47f449ab70b684cabcb6603e5e85e", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "MLflow model tracked in Azure Databricks (case insensitive)", "purl": "pkg:mlflow/CreditFraud@3?repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow", "canonical_purl": "pkg:mlflow/creditfraud@3?repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow", "type": "mlflow", "namespace": null, "name": "creditfraud", "version": "3", "qualifiers": {"repository_url": "https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow"}, "subpath": null, "is_invalid": false }, { "description": "MLflow model tracked in Azure ML (case sensitive)", "purl": "pkg:mlflow/CreditFraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace", "canonical_purl": "pkg:mlflow/CreditFraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace", "type": "mlflow", "namespace": null, "name": "CreditFraud", "version": "3", "qualifiers": {"repository_url": "https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace"}, "subpath": null, "is_invalid": false }, { "description": "MLflow model with unique identifiers", "purl": "pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow", "canonical_purl": "pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a", "type": "mlflow", "namespace": null, "name": "trafficsigns", "version": "10", "qualifiers": {"model_uuid": "36233173b22f4c89b451f1228d700d49", "run_id": "410a3121-2709-4f88-98dd-dba0ef056b0a", "repository_url": "https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow"}, "subpath": null, "is_invalid": false }, { "description": "composer names are not case sensitive", "purl": "pkg:composer/Laravel/Laravel@5.5.0", "canonical_purl": "pkg:composer/laravel/laravel@5.5.0", "type": "composer", "namespace": "laravel", "name": "laravel", "version": "5.5.0", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "cpan distribution name are case sensitive", "purl": "pkg:cpan/DROLSKY/DateTime@1.55", "canonical_purl": "pkg:cpan/DROLSKY/DateTime@1.55", "type": "cpan", "namespace": "DROLSKY", "name": "DateTime", "version": "1.55", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "cpan module name are case sensitive", "purl": "pkg:cpan/URI::PackageURL@2.11", "canonical_purl": "pkg:cpan/URI::PackageURL@2.11", "type": "cpan", "namespace": null, "name": "URI::PackageURL", "version": "2.11", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "cpan module name like distribution name", "purl": "pkg:cpan/Perl-Version@1.013", "canonical_purl": "pkg:cpan/Perl-Version@1.013", "type": "cpan", "namespace": null, "name": "Perl-Version", "version": "1.013", "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "cpan distribution name like module name", "purl": "pkg:cpan/GDT/URI::PackageURL@2.11", "canonical_purl": "pkg:cpan/GDT/URI::PackageURL", "type": "cpan", "namespace": "GDT", "name": "URI::PackageURL", "version": null, "qualifiers": null, "subpath": null, "is_invalid": true }, { "description": "cpan valid module name", "purl": "pkg:cpan/DateTime@1.55", "canonical_purl": "pkg:cpan/DateTime@1.55", "type": "cpan", "namespace": null, "name": "DateTime", "version": "1.55", "qualifiers": null, "subpath": null, "is_invalid": false }, { "description": "cpan valid module name without version", "purl": "pkg:cpan/URI", "canonical_purl": "pkg:cpan/URI", "type": "cpan", "namespace": null, "name": "URI", "version": null, "qualifiers": null, "subpath": null, "is_invalid": false } ] URI-PackageURL-2.22/README.md0000644000175000017500000000601214650267645014313 0ustar peppepeppe[![Release](https://img.shields.io/github/release/giterlizzi/perl-URI-PackageURL.svg)](https://github.com/giterlizzi/perl-URI-PackageURL/releases) [![Actions Status](https://github.com/giterlizzi/perl-URI-PackageURL/workflows/linux/badge.svg)](https://github.com/giterlizzi/perl-URI-PackageURL/actions) [![License](https://img.shields.io/github/license/giterlizzi/perl-URI-PackageURL.svg)](https://github.com/giterlizzi/perl-URI-PackageURL) [![Starts](https://img.shields.io/github/stars/giterlizzi/perl-URI-PackageURL.svg)](https://github.com/giterlizzi/perl-URI-PackageURL) [![Forks](https://img.shields.io/github/forks/giterlizzi/perl-URI-PackageURL.svg)](https://github.com/giterlizzi/perl-URI-PackageURL) [![Issues](https://img.shields.io/github/issues/giterlizzi/perl-URI-PackageURL.svg)](https://github.com/giterlizzi/perl-URI-PackageURL/issues) [![Coverage Status](https://coveralls.io/repos/github/giterlizzi/perl-URI-PackageURL/badge.svg)](https://coveralls.io/github/giterlizzi/perl-URI-PackageURL) # URI::PackageURL - Perl extension for Package URL (aka "purl") ## Synopsis ```.pl use URI::PackageURL; # OO-interface # Encode components in PackageURL string $purl = URI::PackageURL->new(type => cpan, namespace => 'GDT', name => 'URI-PackageURL', version => '2.21'); say $purl; # pkg:cpan/GDT/URI-PackageURL@2.21 # Parse PackageURL string $purl = URI::PackageURL->from_string('pkg:cpan/GDT/URI-PackageURL@2.21'); # exported functions $purl = decode_purl('pkg:cpan/GDT/URI-PackageURL@2.21'); say $purl->type; # cpan $purl_string = encode_purl(type => cpan, namespace => 'GDT', name => 'URI::PackageURL', version => '2.21'); ``` ## purl-tool a CLI for URI::PackageURL module Inspect and export "purl" string in various formats (JSON, YAML, Data::Dumper, ENV): ```console $ purl-tool pkg:cpan/GDT/URI-PackageURL@2.21 --json | jq { "name": "URI-PackageURL", "namespace": "GDT", "qualifiers": {}, "subpath": null, "type": "cpan", "version": "2.21" } ``` Download package using "purl" string: ```console $ wget $(purl-tool pkg:cpan/GDT/URI-PackageURL@2.21 --download-url) ``` Use "purl" string in your shell-scripts: ```.bash #!bash set -e PURL="pkg:cpan/GDT/URI-PackageURL@2.21" eval $(purl-tool "$PURL" --env) echo "Download $PURL_NAME $PURL_VERSION" wget $PURL_DOWNLOAD_URL echo "Build and install module $PURL_NAME $PURL_VERSION" tar xvf $PURL_NAME-$PURL_VERSION.tar.gz cd $PURL_NAME-$PURL_VERSION perl Makefile.PL make && make install ``` Create on-the-fly a "purl" string: ```console $ purl-tool --type cpan \ --namespace GDT \ --name URI-PackageURL \ --version 2.21 ``` ## Install Using Makefile.PL: To install `URI::PackageURL` distribution, run the following commands. perl Makefile.PL make make test make install Using App::cpanminus: cpanm URI::PackageURL ## Documentation - `perldoc URI::PackageURL` - https://metacpan.org/release/URI-PackageURL - https://github.com/package-url/purl-spec ## Copyright - Copyright 2022-2024 © Giuseppe Di Terlizzi URI-PackageURL-2.22/META.json0000644000175000017500000000301514730116003014431 0ustar peppepeppe{ "abstract" : "Perl extension for Package URL (aka \"purl\")", "author" : [ "Giuseppe Di Terlizzi " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "URI-PackageURL", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "JSON::PP" : "0", "List::Util" : "0", "perl" : "5.010" } }, "test" : { "requires" : { "CPAN::DistnameInfo" : "0", "JSON::PP" : "0", "Test::More" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/giterlizzi/perl-URI-PackageURL/issues" }, "repository" : { "type" : "git", "url" : "git://github.com/giterlizzi/perl-URI-PackageURL", "web" : "https://github.com/giterlizzi/perl-URI-PackageURL" } }, "version" : "2.22", "x_purl" : "pkg:cpan/GDT/URI-PackageURL", "x_serialization_backend" : "JSON::PP version 4.16" } URI-PackageURL-2.22/Changes0000644000175000017500000000612314730115131014307 0ustar peppepeppeChange history for URI-PackageURL 2.22 2024-12-16 - Improved parsing of non-canonical PURL (package-url/purl-spec#363) - Improved "URI::VersionRange->constraint_contains" - Updated "maven" repository URL - FIX typo in documentation - Synced "test-suite-data.json" from "package-url/purl-spec" 2.21 2024-07-24 - Use RFC 2119 terms for CPAN purl type specification (sjn) - Added "swid" purl type support - Moved normalization and validation check in "URI::PackageURL::Util" - Dropped support for "version_prefix" qualifier for "github", "gitlab" and "bitbucket" PURL types in "purl_to_urls" util (pombredanne via giterlizzi/perl-URI-PackageURL#14) 2.20 2024-05-13 - Added support for the official "cpan" PURL type specification (giterlizzi/perl-URI-PackageURL#8) - Added "Version Range" (vers) support (giterlizzi/perl-URI-PackageURL#12) - Added "URI::VersionRange::Version::cpan" version comparator for "cpan" scheme - Added "vers-tool(1)" - FIX Apply percent-encoding in "subpath" in "to_string" method 2.11 2024-04-19 - Improved the cpan PURL type to be compatible with the 'PURL-TYPES' specification (giterlizzi/perl-URI-PackageURL#8 - https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst) - Added "luarocks" PURL type support - Improved test suite (giterlizzi/perl-URI-PackageURL#11) - Renamed CLI package in "URI::PackageURL::App" - Changed JSON module pre-requisite to "JSON::PP" to be compatible with CPAN Toolchain (giterlizzi/perl-URI-PackageURL#12) 2.04 2023-11-16 - Added "docker", "bitbuket", "golang" support to "URI::PackageURL::Util::purl_to_urls" - Added new options for create canonical purl string using "purl-tool(1)" (giterlizzi/perl-URI-PackageURL#9) - Added sample scripts - Removed empty "qualifier" keys in "URI::PackageURL->from_string" 2.03 2023-11-09 - Improved validation during encode and decode "purl" string - Fixed CPAN repository URL - FIX Qualifiers are case sensitive (giterlizzi/perl-URI-PackageURL#4) - FIX PURLs containing multiple namespaces segments parse incorrectly (giterlizzi/perl-URI-PackageURL#5) - FIX Incorrect parsing of PURLs that begin with "pkg:/" (giterlizzi/perl-URI-PackageURL#6) - Improved "t/99-official-purl-test-suite.t" test 2.02 2023-09-22 - Added core "JSON" module prerequisite in Makefile.PL (#4) 2.01 2023-09-17 - Added "repository_url" qualifier support for "cpan" PURL type 2.00 2023-06-13 - Added "purl-tool(1)" (giterlizzi/perl-URI-PackageURL#3) - Added "URI::PackageURL::Util::purl_to_urls" helper - Fixed name qualifier for "cpan" (giterlizzi/perl-URI-PackageURL#2) - Fixed some little issues 1.10 2022-08-01 - Fixed "namespace vs name" (RT#143917) - Fixed substitution warning when "version" component is not provided in URI::PackageURL->from_string 1.02 2022-07-31 - Fixed decode when "namespace" component is "undef" 1.01 2022-07-26 - Fixed documentation and test prerequisite (JSON::PP) 1.00 2022-07-25 - First release of URI::PackageURL URI-PackageURL-2.22/LICENSE0000644000175000017500000002141314267754747014053 0ustar peppepeppe The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. URI-PackageURL-2.22/lib/0000755000175000017500000000000014730116003013557 5ustar peppepeppeURI-PackageURL-2.22/lib/URI/0000755000175000017500000000000014730116003014216 5ustar peppepeppeURI-PackageURL-2.22/lib/URI/VersionRange.pm0000644000175000017500000003427014727621037017202 0ustar peppepeppepackage URI::VersionRange; use feature ':5.10'; use strict; use utf8; use warnings; use Carp (); use List::Util qw(first); use Exporter qw(import); use URI::VersionRange::Constraint; use URI::VersionRange::Version; use constant DEBUG => $ENV{VERS_DEBUG}; use constant TRUE => !!1; use constant FALSE => !!0; use overload '""' => 'to_string', fallback => 1; our $VERSION = '2.22'; our @EXPORT = qw(encode_vers decode_vers); my $VERS_REGEXP = qr{^vers:[a-z\\.\\-\\+][a-z0-9\\.\\-\\+]*/.+}; sub new { my ($class, %params) = @_; my $scheme = delete $params{scheme} or Carp::croak "Invalid Version Range: 'scheme' is required"; my $constraints = delete $params{constraints} or Carp::croak "Invalid Version Range: 'constraints' is required"; my @constraints = (); foreach my $constraint (@{$constraints}) { if (ref($constraint) ne 'URI::VersionRange::Constraint') { $constraint = URI::VersionRange::Constraint->from_string($constraint); } push @constraints, $constraint; } my $self = { scheme => lc($scheme), constraints => \@constraints, _version_class => _load_version_class_from_scheme(lc($scheme)) }; return bless $self, $class; } sub _load_version_class_from_scheme { my $scheme = shift; my @CLASSES = ( join('::', 'URI::VersionRange::Version', lc($scheme)), # Schema specific 'URI::VersionRange::Version::generic', # Generic or used-defined class 'URI::VersionRange::Version' # Fallback class ); my $loaded_version_class = undef; VERSION_CLASS: foreach my $version_class (@CLASSES) { if ($version_class->can('new') or eval "require $version_class; 1") { $loaded_version_class = $version_class; last VERSION_CLASS; } DEBUG and say STDERR "-- (E) Failed to load '$version_class' class for '$scheme' scheme ... try next class" if $@; } DEBUG and say STDERR "-- Loaded '$loaded_version_class' class for '$scheme' scheme"; return $loaded_version_class; } sub scheme { shift->{scheme} } sub constraints { shift->{constraints} } sub encode_vers { __PACKAGE__->new(@_)->to_string } sub decode_vers { __PACKAGE__->from_string(shift) } sub from_string { my ($class, $string) = @_; if ($string !~ /$VERS_REGEXP/) { Carp::croak 'Malformed Version Range string'; } my %params = (); # - Remove all spaces and tabs. # - Start from left, and split once on colon ":". # - The left hand side is the URI-scheme that must be lowercase. # Tools must validate that the URI-scheme value is vers. # - The right hand side is the specifier. $string =~ s/(\s|\t)+//g; my @s1 = split(':', $string); # $params{uri_scheme} = lc $s1[0]; # - Split the specifier from left once on a slash "/". # - The left hand side is the that must be lowercase. Tools # should validate that the is a known scheme. # - The right hand side is a list of one or more constraints. Tools must validate # that this constraints string is not empty ignoring spaces. my @s2 = split('/', $s1[1]); $params{scheme} = lc $s2[0]; # - If the constraints string is equal to "", the ```` # is "". Parsing is done and no further processing is needed for this vers. # A tool should report an error if there are extra characters beyond "*". # - Strip leading and trailing pipes "|" from the constraints string. # - Split the constraints on pipe "|". The result is a list of . # Consecutive pipes must be treated as one and leading and trailing pipes ignored. $s2[1] =~ s/(^\|)|(\|$)//g; my @s3 = split(/\|/, $s2[1]); $params{constraints} = []; # - For each : # - Determine if the starts with one of the two comparators: # - If it starts with ">=", then the comparator is ">=". # - If it starts with "<=", then the comparator is "<=". # - If it starts with "!=", then the comparator is "!=". # - If it starts with "<", then the comparator is "<". # - If it starts with ">", then the comparator is ">". # - Remove the comparator from string start. The remaining string is the version. # - Otherwise the version is the full string (which implies an equality comparator of "=") # - Tools should validate and report an error if the version is empty. # - If the version contains a percent "%" character, apply URL quoting rules to unquote this string. # - Append the parsed (comparator, version) to the constraints list. foreach (@s3) { push @{$params{constraints}}, URI::VersionRange::Constraint->from_string($_); } if (DEBUG) { say STDERR "-- S1: @s1"; say STDERR "-- S2: @s2"; say STDERR "-- S3: @s3"; } return $class->new(%params); } sub to_string { return join '', 'vers:', $_[0]->scheme, '/', join('|', @{$_[0]->constraints}); } sub constraint_contains { my ($self, $constraint, $version) = @_; return TRUE if $constraint->comparator eq '*'; my $version_class = $self->{_version_class}; my $v1 = $version_class->parse($version); my $v2 = $version_class->parse($constraint->version); return ($v1 == $v2) if ($constraint->comparator eq '='); return ($v1 != $v2) if ($constraint->comparator eq '!='); return ($v1 <= $v2) if ($constraint->comparator eq '<='); return ($v1 >= $v2) if ($constraint->comparator eq '>='); return ($v1 < $v2) if ($constraint->comparator eq '<'); return ($v1 > $v2) if ($constraint->comparator eq '>'); return FALSE; } sub contains { my ($self, $version) = @_; my @first = (); my @second = (); my $version_class = $self->{_version_class}; if (scalar @{$self->constraints} == 1) { return $self->constraint_contains($self->constraints->[0], $version); } foreach my $constraint (@{$self->constraints}) { # If the "tested version" is equal to the any of the constraint version # where the constraint comparator is for equality (any of "=", "<=", or ">=") # then the "tested version" is in the range. Check is finished. return TRUE if ((first { $constraint->comparator eq $_ } ('=', '<=', '>=')) && ($version_class->parse($version) == $version_class->parse($constraint->version))); # If the "tested version" is equal to the any of the constraint version # where the constraint comparator is "=!" then the "tested version" is NOT # in the range. Check is finished. return FALSE if ($constraint->comparator eq '!=' && ($version_class->parse($version) == $version_class->parse($constraint->version))); # Split the constraint list in two sub lists: # a first list where the comparator is "=" or "!=" # a second list where the comparator is neither "=" nor "!=" push @first, $constraint if ((first { $constraint->comparator eq $_ } ('=', '!='))); push @second, $constraint if (!(first { $constraint->comparator eq $_ } ('=', '!='))); } return FALSE unless @second; if (scalar @second == 1) { return $self->constraint_contains($second[0], $version); } # Iterate over the current and next contiguous constraints pairs (aka. pairwise) # in the second list. # For each current and next constraint: my $is_first_iteration = TRUE; my $current_constraint = undef; my $next_constraint = undef; foreach (_pairwise(@second)) { ($current_constraint, $next_constraint) = @{$_}; DEBUG and say STDERR sprintf '-- Current constraint --> %s', $current_constraint; DEBUG and say STDERR sprintf '-- Next constraint --> %s', $next_constraint; # If this is the first iteration and current comparator is "<" or <=" and # the "tested version" is less than the current version then the "tested # version" is IN the range. Check is finished. if ($is_first_iteration) { return TRUE if ((first { $current_constraint->comparator eq $_ } ('<=', '<')) && ($version_class->parse($version) < $version_class->parse($current_constraint->version))); $is_first_iteration = FALSE; } # If current comparator is ">" or >=" and next comparator is "<" or <=" # and the "tested version" is greater than the current version and the # "tested version" is less than the next version then the "tested version" # is IN the range. Check is finished. if ( (first { $current_constraint->comparator eq $_ } ('>', '>=')) && (first { $next_constraint->comparator eq $_ } ('<', '<=')) && ($version_class->parse($version) > $version_class->parse($current_constraint->version)) && ($version_class->parse($version) < $version_class->parse($next_constraint->version))) { return TRUE; } # If current comparator is "<" or <=" and next comparator is ">" or >=" # then these versions are out the range. Continue to the next iteration. elsif ((first { $current_constraint->comparator eq $_ } ('<', '<=')) && (first { $next_constraint->comparator } ('>', '>='))) { next; } } # If this is the last iteration and next comparator is ">" or >=" and the # "tested version" is greater than the next version then the "tested version" # is IN the range. Check is finished. return TRUE if ((first { $next_constraint->comparator eq $_ } ('>', '>=')) && ($version_class->parse($version) > $version_class->parse($next_constraint->version))); return FALSE; } sub TO_JSON { return {scheme => $_[0]->scheme, constraints => $_[0]->constraints}; } sub _pairwise { my @out = (); for (my $i = 0; $i < scalar @_; $i++) { push @out, [$_[$i], $_[$i + 1]] if $_[$i + 1]; } return @out; } 1; __END__ =head1 NAME URI::VersionRange - Perl extension for Version Range Specification =head1 SYNOPSIS use URI::VersionRange; # OO-interface $vers = URI::VersionRange->new( scheme => 'cpan', constraints => ['>2.00'] ); say $vers; # vers:cpan/>2.00 if ($vers->contains('2.10')) { say "The version is in range"; } # Parse "vers" string $vers = URI::VersionRange->from_string('vers:cpan/>2.00|<2.22'); # exported functions $vers = decode_vers('vers:cpan/>2.00|<2.22'); say $vers->scheme; # cpan $vers_string = encode_vers(scheme => cpan, constraints => ['>2.00']); say $vers_string; # vers:cpan/>2.00 =head1 DESCRIPTION A version range specifier (aka. "vers") is a URI string using the C URI-scheme with this syntax: vers:/||... C is the URI-scheme and is an acronym for "VErsion Range Specifier". The pipe "|" is used as a simple separator between C. Each C in this pipe-separated list contains a comparator and a version: This list of C are signposts in the version timeline of a package that specify version intervals. A C satisfies a version range specifier if it is contained within any of the intervals defined by these C. L =head2 FUNCTIONAL INTERFACE They are exported by default: =over =item $vers_string = encode_vers(%params); Converts the given C components to "vers" string. Croaks on error. This function call is functionally identical to: $vers_string = URI::VersionRange->new(%params)->to_string; =item $vers = decode_vers($vers_string); Converts the given "vers" string to L object. Croaks on error. This function call is functionally identical to: $vers = URI::VersionRange->from_string($vers_string); =back =head2 OBJECT-ORIENTED INTERFACE =over =item $vers = URI::VersionRange->new( scheme => STRING, constraints => ARRAY ) Create new B instance using provided C components (scheme, constraints). =item $vers->scheme By convention the versioning scheme should be the same as the L package C for a given package ecosystem. =item $vers->constraints C is ARRAY of L object. =item $vers->contains($version) Check if a version is contained within a range my $vers = URI::VersionRange::from_string('vers:cpan/>2.00|<2.22'); if ($vers->contains('2.10')) { say "The version is in range"; } See L. =item $vers->constraint_contains Check if a version is contained within a specific constraint. See L. =item $vers->to_string Stringify C components. =item $vers->TO_JSON Helper method for JSON modules (L, L, L, L, etc). use Mojo::JSON qw(encode_json); say encode_json($vers); # {"constraints":[{"comparator":">","version":"2.00"},{"comparator":"<","version":"2.22"}],"scheme":"cpan"} =item $vers = URI::VersionRange->from_string($vers_string); Converts the given "vers" string to L object. Croaks on error. =back =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/giterlizzi/perl-URI-PackageURL.git =head1 AUTHOR =over 4 =item * Giuseppe Di Terlizzi =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2022-2024 by Giuseppe Di Terlizzi. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut URI-PackageURL-2.22/lib/URI/PackageURL.pm0000644000175000017500000003237114730110712016501 0ustar peppepeppepackage URI::PackageURL; use feature ':5.10'; use strict; use utf8; use warnings; use Carp (); use Exporter qw(import); use URI::PackageURL::Util qw(purl_to_urls purl_components_normalize); use constant DEBUG => $ENV{PURL_DEBUG}; use overload '""' => 'to_string', fallback => 1; our $VERSION = '2.22'; our @EXPORT = qw(encode_purl decode_purl); my $PURL_REGEXP = qr{^pkg:[A-Za-z\\.\\-\\+][A-Za-z0-9\\.\\-\\+]*/.+}; sub new { my ($class, %params) = @_; my $scheme = delete $params{scheme} // 'pkg'; my $type = delete $params{type} or Carp::croak "Invalid Package URL: 'type' component is required"; my $namespace = delete $params{namespace}; my $name = delete $params{name} or Carp::croak "Invalid Package URL: 'name' component is required"; my $version = delete $params{version}; my $qualifiers = delete $params{qualifiers} // {}; my $subpath = delete $params{subpath}; return bless purl_components_normalize( scheme => $scheme, type => $type, namespace => $namespace, name => $name, version => $version, qualifiers => $qualifiers, subpath => $subpath ), $class; } sub scheme { shift->{scheme} } sub type { shift->{type} } sub namespace { shift->{namespace} } sub name { shift->{name} } sub version { shift->{version} } sub qualifiers { shift->{qualifiers} } sub subpath { shift->{subpath} } sub encode_purl { __PACKAGE__->new(@_)->to_string } sub decode_purl { __PACKAGE__->from_string(shift) } sub from_string { my ($class, $string) = @_; # Strip slash / after scheme while ($string =~ m|^pkg:/|) { $string =~ s|^pkg:/|pkg:|; } if ($string !~ /$PURL_REGEXP/) { Carp::croak 'Malformed Package URL string'; } my %components = (); # Split the purl string once from right on '#' # The left side is the remainder # Strip the right side from leading and trailing '/' # Split this on '/' # Discard any empty string segment from that split # Discard any '.' or '..' segment from that split # Percent-decode each segment # UTF-8-decode each segment if needed in your programming language # Join segments back with a '/' # This is the subpath my @s1 = split(/#([^#]+)$/, $string); if ($s1[1]) { $s1[1] =~ s/(^\/|\/$)//; my @subpath = map { _url_decode($_) } grep { $_ ne '' && $_ ne '.' && $_ ne '..' } split /\//, $s1[1]; $components{subpath} = join '/', @subpath; } # Split the remainder once from right on '?' # The left side is the remainder # The right side is the qualifiers string # Split the qualifiers on '&'. Each part is a key=value pair # For each pair, split the key=value once from left on '=': # The key is the lowercase left side # The value is the percent-decoded right side # UTF-8-decode the value if needed in your programming language # Discard any key/value pairs where the value is empty # If the key is checksums, split the value on ',' to create a list of checksums # This list of key/value is the qualifiers object my @s2 = split(/\?([^\?]+)$/, $s1[0]); if ($s2[1]) { my @qualifiers = split('&', $s2[1]); foreach my $qualifier (@qualifiers) { my ($key, $value) = split('=', $qualifier); $value = _url_decode($value); if ($key eq 'checksums') { $value = [split(',', $value)]; } $components{qualifiers}->{lc $key} = $value; } } # Split the remainder once from left on ':' # The left side lowercased is the scheme # The right side is the remainder my @s3 = split(':', $s2[0], 2); $components{scheme} = lc $s3[0]; # Strip the remainder from leading and trailing '/' # Split this once from left on '/' # The left side lowercased is the type # The right side is the remainder $s3[1] =~ s/(^\/|\/$)//; my @s4 = split('/', $s3[1], 2); $components{type} = lc $s4[0]; # Split the remainder once from right on '@' # The left side is the remainder # Percent-decode the right side. This is the version. # UTF-8-decode the version if needed in your programming language # This is the version my @s5 = split(/@([^@]+)$/, $s4[1]); $components{version} = _url_decode($s5[1]) if ($s5[1]); # Split the remainder once from right on '/' # The left side is the remainder # Percent-decode the right side. This is the name # UTF-8-decode this name if needed in your programming language # Apply type-specific normalization to the name if needed # This is the name my @s6 = split('/', $s5[0], -1); $components{name} = _url_decode(pop @s6); # Split the remainder on '/' # Discard any empty segment from that split # Percent-decode each segment # UTF-8-decode the each segment if needed in your programming language # Apply type-specific normalization to each segment if needed # Join segments back with a '/' # This is the namespace if (@s6) { $components{namespace} = join '/', map { _url_decode($_) } @s6; } if (DEBUG) { say STDERR "-- S1: @s1"; say STDERR "-- S2: @s2"; say STDERR "-- S3: @s3"; say STDERR "-- S4: @s4"; say STDERR "-- S5: @s5"; say STDERR "-- S6: @s6"; } return $class->new(%components); } sub to_string { my $self = shift; my @purl = ('pkg', ':', $self->type, '/'); # Namespace if ($self->namespace) { my @ns = map { _url_encode($_) } split(/\//, $self->namespace); push @purl, (join('/', @ns), '/'); } # Name push @purl, _url_encode($self->name); # Version push @purl, ('@', _url_encode($self->version)) if ($self->version); # Qualifiers if (my $qualifiers = $self->qualifiers) { my @qualifiers = map { sprintf('%s=%s', lc $_, _url_encode($qualifiers->{$_})) } grep { $qualifiers->{$_} } sort keys %{$qualifiers}; push @purl, ('?', join('&', @qualifiers)) if (@qualifiers); } # Subpath if ($self->subpath) { my @subpath = map { _url_encode($_) } split '/', $self->subpath; push @purl, ('#', join('/', @subpath)); } return join '', @purl; } sub to_urls { purl_to_urls(shift); } sub TO_JSON { my $self = shift; return { type => $self->type, name => $self->name, version => $self->version, namespace => $self->namespace, qualifiers => $self->qualifiers, subpath => $self->subpath, }; } sub _url_encode { my $string = shift; # RFC-3986 (but exclude "/" and ":") $string =~ s/([^A-Za-z0-9\-._~\/:])/sprintf '%%%02X', ord $1/ge; return $string; } sub _url_decode { my $string = shift; return unless $string; $string =~ s/%([0-9a-fA-F]{2})/chr hex $1/ge; return $string; } 1; __END__ =head1 NAME URI::PackageURL - Perl extension for Package URL (aka "purl") =head1 SYNOPSIS use URI::PackageURL; # OO-interface # Encode components in Package URL string $purl = URI::PackageURL->new( type => 'cpan', namespace => 'GDT', name => 'URI-PackageURL', version => '2.22' ); say $purl; # pkg:cpan/GDT/URI-PackageURL@2.22 # Parse Package URL string $purl = URI::PackageURL->from_string('pkg:cpan/GDT/URI-PackageURL@2.22'); # exported functions $purl = decode_purl('pkg:cpan/GDT/URI-PackageURL@2.22'); say $purl->type; # cpan $purl_string = encode_purl(type => cpan, name => 'URI::PackageURL', version => '2.22'); say $purl_string; # pkg:cpan/URI::PackageURL@2.22 =head1 DESCRIPTION This module converts Package URL components to "purl" string and vice versa. A Package URL (aka "purl") is a URL string used to identify and locate a software package in a mostly universal and uniform way across programing languages, package managers, packaging conventions, tools, APIs and databases. L A purl is a URL composed of seven components: scheme:type/namespace/name@version?qualifiers#subpath Components are separated by a specific character for unambiguous parsing. The definition for each components is: =over =item * "scheme": this is the URL scheme with the constant value of "pkg". One of the primary reason for this single scheme is to facilitate the future official registration of the "pkg" scheme for package URLs. Required. =item * "type": the package "type" or package "protocol" such as cpan, maven, npm, nuget, gem, pypi, etc. Required. =item * "namespace": some name prefix such as a Maven groupid, a Docker image owner, a GitHub user or organization. Optional and type-specific. =item * "name": the name of the package. Required. =item * "version": the version of the package. Optional. =item * "qualifiers": extra qualifying data for a package such as an OS, architecture, a distro, etc. Optional and type-specific. =item * "subpath": extra subpath within a package, relative to the package root. Optional. =back =head2 CPAN PURL TYPE C is an official "purl" type (L) =over =item * The default repository is C. =item * The C: =over =item * To refer to a CPAN distribution name, the C MUST be present. In this case, the namespace is the CPAN id of the author/publisher. It MUST be written uppercase, followed by the distribution name in the name component. A distribution name MUST NOT contain the string C<::>. =item * To refer to a CPAN module, the C MUST be absent. The module name MAY contain zero or more C<::> strings, and the module name MUST NOT contain a C<-> =back =item * The C is the module or distribution name and is case sensitive. =item * The C is the module or distribution version. =item * Optional qualifiers may include: =over =item * C: CPAN/MetaCPAN/BackPAN/DarkPAN repository base URL (default is https://www.cpan.org) =item * C: URL of package or distribution =item * C: extra URL for a package version control system =item * C: file extension (default is tar.gz) =back =back =head3 Examples pkg:cpan/Perl::Version@1.013 pkg:cpan/DROLSKY/DateTime@1.55 pkg:cpan/DateTime@1.55 pkg:cpan/GDT/URI-PackageURL pkg:cpan/LWP::UserAgent pkg:cpan/OALDERS/libwww-perl@6.76 pkg:cpan/URI =head2 FUNCTIONAL INTERFACE They are exported by default: =over =item $purl_string = encode_purl(%purl_components) Converts the given Package URL components to "purl" string. Croaks on error. This function call is functionally identical to: $purl_string = URI::PackageURL->new(%purl_components)->to_string; =item $purl_components = decode_purl($purl_string) Converts the given "purl" string to Package URL components. Croaks on error. This function call is functionally identical to: $purl = URI::PackageURL->from_string($purl_string); =back =head2 OBJECT-ORIENTED INTERFACE =over =item $purl = URI::PackageURL->new(%components) Create new B instance using provided Package URL components (type, name, version ,etc). =item $purl->type The package "type" or package "protocol" such as cpan, maven, npm, nuget, gem, pypi, etc. =item $purl->namespace Some name prefix such as a Maven groupid, a Docker image owner, a GitHub user or organization. Optional and type-specific. =item $purl->name The "name" of the package. =item $purl->version The "version" of the package. =item $purl->qualifiers Extra qualifying data for a package such as an OS, architecture, a distro, etc. =item $purl->subpath Extra subpath within a package, relative to the package root. =item $purl->to_string Stringify Package URL components. =item $purl->to_urls Return B and/or B URLs. =item $purl->TO_JSON Helper method for JSON modules (L, L, L, L, etc). use Mojo::JSON qw(encode_json); say encode_json($purl); # {"name":"URI-PackageURL","namespace":"GDT","qualifiers":null,"subpath":null,"type":"cpan","version":"2.22"} =item $purl = URI::PackageURL->from_string($purl_string); Converts the given "purl" string to Package URL components. Croaks on error. =back =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/giterlizzi/perl-URI-PackageURL.git =head1 AUTHOR =over 4 =item * Giuseppe Di Terlizzi =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2022-2024 by Giuseppe Di Terlizzi. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut URI-PackageURL-2.22/lib/URI/VersionRange/0000755000175000017500000000000014730116003016620 5ustar peppepeppeURI-PackageURL-2.22/lib/URI/VersionRange/Version.pm0000644000175000017500000000705214620416062020615 0ustar peppepeppepackage URI::VersionRange::Version; use feature ':5.10'; use strict; use utf8; use warnings; use version (); use overload ('cmp' => \&compare, '<=>' => \&compare, fallback => 1); sub new { my $class = shift; bless [@_], $class } sub compare { (version->parse($_[0]->[0]) <=> version->parse($_[1]->[0])) } *parse = \&new; package # hide from pause URI::VersionRange::Version::cpan; use parent 'URI::VersionRange::Version'; 1; __END__ =head1 NAME URI::VersionRange::Version - Version comparator class =head1 SYNOPSIS package URI::VersionRange::Version::generic { use Version::libversion::XS qw(version_compare2); use parent 'URI::VersionRange::Version'; use overload ('cmp' => \&compare, '<=>' => \&compare, fallback => 1); sub compare { my ($left, $right) = @_; return version_compare2($left->[0], $right->[0]); } } my $vers = URI::VersionRange->from_string('vers:generic/>v1.00|!=v2.10|<=v3.00'); if ($vers->contains('v2.50')) { # do stuff } =head1 DESCRIPTION This is a base class for the version comparator. NOTE: L provide out-of-the-box C type comparator. =head2 OBJECT-ORIENTED INTERFACE =over =item $v = URI::VersionRange::Version->new( $value ) Create new B instance using provided version C. =item $v->compare Compare the version =back =head2 HOW TO CREATE A NEW VERSION COMPARATOR =over =item * Create a new package using the naming convention C<< URI::VersionRange::Version:: >> by extending L. =item * Implements the C subroutine with the algorithm required by the C. C<$left> and C<$right> arguments of C are C and have as their first element the value of the version to be compared. =item * L C<< '<=>' >> and C operators using C subroutine (MANDATORY) =back This is an example that implements a comparator for the C scheme using L module: package URI::VersionRange::Version::generic { use Version::libversion::XS; use parent 'URI::VersionRange::Version'; use overload ('cmp' => \&compare, '<=>' => \&compare, fallback => 1); sub compare { my ($left, $right) = @_; return version_compare2($left->[0], $right->[0]); } } This is an another example for the C scheme using L module: package URI::VersionRange::Version::rpm { use RPM4; use parent 'URI::VersionRange::Version'; use overload ('cmp' => \&compare, '<=>' => \&compare, fallback => 1); sub compare { my ($left, $right) = @_; return rpmvercmp($left->[0], $right->[0]); } } =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/giterlizzi/perl-URI-PackageURL.git =head1 AUTHOR =over 4 =item * Giuseppe Di Terlizzi =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2022-2024 by Giuseppe Di Terlizzi. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut URI-PackageURL-2.22/lib/URI/VersionRange/Constraint.pm0000644000175000017500000001246714727620653021335 0ustar peppepeppepackage URI::VersionRange::Constraint; use feature ':5.10'; use strict; use utf8; use warnings; use Carp (); use Exporter qw(import); use overload '""' => 'to_string', fallback => 1; use URI::VersionRange::Version; our $VERSION = '2.22'; our %COMPARATOR = ( '=' => 'equal', '<' => 'less than', '<=' => 'less than or equal', '>' => 'greater than', '>=' => 'greater than or equal', ); sub new { my ($class, %params) = @_; my $comparator = delete $params{comparator} // '='; my $version = delete $params{version}; my $self = {comparator => $comparator, version => $version}; return bless $self, $class; } sub version { shift->{version} } sub comparator { shift->{comparator} } sub from_string { my ($class, $string) = @_; Carp::croak 'Empty version' unless $string; # - For each : # - Determine if the starts with one of the two comparators: # - If it starts with ">=", then the comparator is ">=". # - If it starts with "<=", then the comparator is "<=". # - If it starts with "!=", then the comparator is "!=". # - If it starts with "<", then the comparator is "<". # - If it starts with ">", then the comparator is ">". # - Remove the comparator from string start. The remaining string is the version. # - Otherwise the version is the full string (which implies an equality comparator of "=") # - Tools should validate and report an error if the version is empty. # - If the version contains a percent "%" character, apply URL quoting rules to unquote this string. if ($string =~ /^(>=|<=|!=|<|>)(.*)/) { my ($comparator, $version) = ($1, $2); return $class->new(comparator => $comparator, version => $version); } return $class->new(comparator => '*') if ($string eq '*'); return $class->new(comparator => '=', version => $string); } sub to_string { my $self = shift; return '*' if $self->comparator eq '*'; return $self->version if $self->comparator eq '='; return join '', $self->comparator, $self->version; } sub to_human_string { sprintf '%s %s', $COMPARATOR{$_[0]->comparator}, $_[0]->version } sub TO_JSON { {version => $_[0]->version, comparator => $_[0]->comparator} } 1; __END__ =head1 NAME URI::VersionRange::Constraint - Version Constraint for Version Range Specification =head1 SYNOPSIS use URI::VersionRange::Constraint; # OO-interface $constraint = URI::VersionRange::Constraint->new( comparator => '>', version => '2.00' ); say $constraint; # >2.00 # Parse "vers" string $constraint = URI::VersionRange::Constraint->from_string('>2.00'); =head1 DESCRIPTION A version range specifier (aka. "vers") is a URI string using the C URI-scheme with this syntax: vers:/||... C is the URI-scheme and is an acronym for "VErsion Range Specifier". The pipe "|" is used as a simple separator between C. Each C in this pipe-separated list contains a comparator and a version: This list of C are signposts in the version timeline of a package that specify version intervals. A C satisfies a version range specifier if it is contained within any of the intervals defined by these C. L =head2 OBJECT-ORIENTED INTERFACE =over =item $constraint = URI::VersionRange::Constraint->new( comparator => STRING, version => STRING ) Create new B instance. =item $constraint->comparator Return the comparator. =item $constraint->version Return the version string. =item $vers->to_string Stringify C components. =item $vers->to_human_string Convert the constraint into human-readable format. $constraint = URI::VersionRange::Constraint->new( comparator => '>=', version => '2.10' ); say $constraint->to_human_string; # greater than or equal 2.10 =item $vers->TO_JSON Helper method for JSON modules (L, L, L, L, etc). use Mojo::JSON qw(encode_json); say encode_json($constraint); # {"comparator":">","version":"2.00"} =item $vers = URI::VersionRange::Constraint->from_string($vers_string); Converts the given "constraint" string to L object. Croaks on error. =back =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/giterlizzi/perl-URI-PackageURL.git =head1 AUTHOR =over 4 =item * Giuseppe Di Terlizzi =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2022-2024 by Giuseppe Di Terlizzi. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut URI-PackageURL-2.22/lib/URI/VersionRange/App.pm0000644000175000017500000000646114727620647017731 0ustar peppepeppepackage URI::VersionRange::App; use feature ':5.10'; use strict; use warnings; use utf8; use Getopt::Long qw(GetOptionsFromArray :config gnu_compat); use Pod::Usage qw(pod2usage); use Carp (); use JSON::PP (); use Data::Dumper (); use URI::VersionRange (); our $VERSION = '2.22'; sub cli_error { my ($error) = @_; $error =~ s/ at .* line \d+.*//; print STDERR "ERROR: $error\n"; } sub run { my ($class, @args) = @_; my %options = (format => 'json'); GetOptionsFromArray( \@args, \%options, qw( help man v contains=s null|0 format=s json human-readable|h ) ) or pod2usage(-verbose => 0); pod2usage(-exitstatus => 0, -verbose => 2) if defined $options{man}; pod2usage(-exitstatus => 0, -verbose => 0) if defined $options{help}; if (defined $options{v}) { (my $progname = $0) =~ s/.*\///; say <<"VERSION"; $progname version $URI::VersionRange::VERSION Copyright 2022-2024, Giuseppe Di Terlizzi This program is part of the "URI-PackageURL" distribution and is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Complete documentation for $progname can be found using 'man $progname' or on the internet at . VERSION return 0; } my ($vers_string) = @args; pod2usage(-verbose => 1) if !$vers_string; $options{format} = 'json' if defined $options{json}; $options{format} = 'human-readable' if defined $options{'human-readable'}; my $vers = eval { URI::VersionRange->from_string($vers_string) }; if ($@) { cli_error($@); return 1; } if (defined $options{contains}) { my $vers_comparator_class = join '::', 'URI::VersionRange::Version', $vers->scheme; if (!$vers_comparator_class->can('new')) { say STDERR 'WARNING: Loaded the fallback scheme class comparator.'; say STDERR ' The comparison may not work correctly!'; } my $res = eval { $vers->contains($options{contains}) }; if ($@) { cli_error($@); return 1; } say STDERR $res ? 'TRUE' : 'FALSE'; return $res; } if ($options{format} eq 'json') { print JSON::PP->new->canonical->pretty(1)->convert_blessed(1)->encode($vers); return 0; } if ($options{format} eq 'human-readable') { say $vers->scheme; say "- " . $_->to_human_string for (@{$vers->constraints}); return 0; } } 1; __END__ =encoding utf-8 =head1 NAME URI::VersionRange::App - URL::VersionRange (vers) Command Line Interface =head1 SYNOPSIS use URI::VersionRange::App qw(run); run(\@ARGV); =head1 DESCRIPTION URI::VersionRange::App "Command Line Interface" helper module for C. =over =item URI::VersionRange->run(@args) Execute the command =item cli_error($error) Clean error =back =head1 AUTHOR L =head1 COPYRIGHT AND LICENSE Copyright © 2022-2024 L You may use and distribute this module according to the same terms that Perl is distributed under. =cut URI-PackageURL-2.22/lib/URI/PackageURL/0000755000175000017500000000000014730116003016134 5ustar peppepeppeURI-PackageURL-2.22/lib/URI/PackageURL/Util.pm0000644000175000017500000004123314727631673017437 0ustar peppepeppepackage URI::PackageURL::Util; use feature ':5.10'; use strict; use utf8; use warnings; use Exporter qw(import); our $VERSION = '2.22'; our @EXPORT = qw(purl_to_urls purl_components_normalize); sub purl_components_normalize { my (%component) = @_; my %TYPES = ( conan => \&_conan_normalize, cpan => \&_cpan_normalize, cran => \&_cran_normalize, huggingface => \&_huggingface_normalize, mlflow => \&_mlflow_normalize, pypi => \&_pypi_normalize, swid => \&_swid_normalize, swift => \&_swift_normalize, ); Carp::croak "Invalid Package URL: '$component{scheme}' is not a valid scheme" unless ($component{scheme} eq 'pkg'); $component{type} = lc $component{type}; if (grep { $_ eq $component{type} } qw(alpm apk bitbucket composer deb github gitlab hex npm oci pypi)) { $component{name} = lc $component{name}; } if (defined $component{namespace}) { if (grep { $_ eq $component{type} } qw(alpm apk bitbucket composer deb github gitlab golang hex rpm)) { $component{namespace} = lc $component{namespace}; } } foreach my $qualifier (keys %{$component{qualifiers}}) { Carp::croak "Invalid Package URL: '$qualifier' is not a valid qualifier" if ($qualifier =~ /\s/); Carp::croak "Invalid Package URL: '$qualifier' is not a valid qualifier" if ($qualifier =~ /\%/); } if (defined $TYPES{$component{type}}) { return $TYPES{$component{type}}->(%component); } return \%component; } sub _conan_normalize { my (%component) = @_; if (defined $component{namespace} && $component{namespace} ne '') { if (!defined $component{qualifiers}->{channel}) { Carp::croak "Invalid Package URL: Conan 'channel' qualifier does not exist for namespace '$component{namespace}'"; } } else { if (defined $component{qualifiers}->{channel}) { Carp::croak "Invalid Package URL: Conan 'namespace' does not exist for channel '$component{qualifiers}->{channel}'"; } } return \%component; } sub _cpan_normalize { my (%component) = @_; # To refer to a CPAN distribution name, the "namespace" MUST be present. In this # case, the "namespace" is the CPAN id of the author/publisher. It MUST be # written uppercase, followed by the distribution name in the "name" component. A # distribution name MUST NOT contain the string "::". # To refer to a CPAN module, the "namespace" MUST be absent. The module name MAY # contain zero or more "::" strings, and the module name MUST NOT contain a "-" $component{namespace} = uc $component{namespace} if (defined $component{namespace}); if ((defined $component{namespace} && defined $component{name}) && $component{namespace} =~ /\:/) { Carp::croak "Invalid Package URL: CPAN 'namespace' component must have the distribution author"; } if ((defined $component{namespace} && defined $component{name}) && $component{name} =~ /\:/) { Carp::croak "Invalid Package URL: CPAN 'name' component must have the distribution name"; } if (!defined $component{namespace} && $component{name} =~ /\-/) { Carp::croak "Invalid Package URL: CPAN 'name' component must have the module name"; } return \%component; } sub _cran_normalize { my (%component) = @_; Carp::croak "Invalid Package URL: Cran 'version' is required" unless defined $component{version}; return \%component; } sub _huggingface_normalize { my (%component) = @_; # The version is the model revision Git commit hash. It is case insensitive and # must be lowercased in the package URL. $component{version} = lc $component{version}; return \%component; } sub _mlflow_normalize { my (%component) = @_; # The "name" case sensitivity depends on the server implementation: # - Azure ML: it is case sensitive and must be kept as-is in the package URL. # - Databricks: it is case insensitive and must be lowercased in the package URL. if (defined $component{qualifiers}->{repository_url} && $component{qualifiers}->{repository_url} =~ /azuredatabricks/) { $component{name} = lc $component{name}; } return \%component; } sub _pypi_normalize { my (%component) = @_; # A PyPI package name must be lowercased and underscore "_" replaced with a dash "-". $component{name} =~ s/_/-/g; return \%component; } sub _swid_normalize { my (%component) = @_; Carp::croak "Invalid Package URL: swid 'tag_id' qualifier is required" unless defined $component{qualifiers}->{tag_id}; return \%component; } sub _swift_normalize { my (%component) = @_; Carp::croak "Invalid Package URL: Swift 'version' is required" unless defined $component{version}; Carp::croak "Invalid Package URL: Swift 'namespace' is required" unless defined $component{namespace}; return \%component; } sub purl_to_urls { my $purl = shift; if (ref $purl ne 'URI::PackageURL') { require URI::PackageURL; $purl = URI::PackageURL->from_string($purl); } my %TYPES = ( bitbucket => \&_bitbucket_urls, cargo => \&_cargo_urls, composer => \&_composer_urls, cpan => \&_cpan_urls, docker => \&_docker_urls, gem => \&_gem_urls, github => \&_github_urls, gitlab => \&_gitlab_urls, golang => \&_golang_urls, luarocks => \&_luarocks_urls, maven => \&_maven_urls, npm => \&_npm_urls, nuget => \&_nuget_urls, pypi => \&_pypi_urls, ); my $urls = {}; if (defined $TYPES{$purl->type}) { $urls = $TYPES{$purl->type}->($purl); } if (my $download_url = $purl->qualifiers->{download_url}) { $urls->{download} = $download_url; } return $urls; } sub _bitbucket_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $file_ext = $qualifiers->{ext} || 'tar.gz'; my $urls = {}; if ($name && $namespace) { $urls->{repository} = "https://bitbucket.org/$namespace/$name"; } if ($version) { $urls->{download} = "https://bitbucket.org/$namespace/$name/get/$version.$file_ext"; } return $urls; } sub _cargo_urls { my $purl = shift; my $name = $purl->name; my $version = $purl->version; if ($name && $version) { return { repository => "https://crates.io/crates/$name/$version", download => "https://crates.io/api/v1/crates/$name/$version/download" }; } return {repository => "https://crates.io/crates/$name"}; } sub _composer_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; return unless ($name && $namespace); return {repository => "https://packagist.org/packages/$namespace/$name"}; } sub _cpan_urls { my $purl = shift; my $name = $purl->name; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $author = $purl->namespace ? uc($purl->namespace) : undef; my $file_ext = $qualifiers->{ext} || 'tar.gz'; my $repository_url = $qualifiers->{repository_url} || 'https://www.cpan.org'; if ($repository_url !~ /^(http|https|file|ftp):\/\//) { $repository_url = 'https://' . $repository_url; } $name =~ s/\:\:/-/g; # TODO my $urls = {repository => "https://metacpan.org/dist/$name"}; if ($name && $version && $author) { my $author_1 = substr($author, 0, 1); my $author_2 = substr($author, 0, 2); $urls->{repository} = "https://metacpan.org/release/$author/$name-$version"; $urls->{download} = "$repository_url/authors/id/$author_1/$author_2/$author/$name-$version.$file_ext"; } return $urls; } sub _docker_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $repository_url = $qualifiers->{repository_url} || 'https://hub.docker.com'; if ($repository_url !~ /^(http|https):\/\//) { $repository_url = 'https://' . $repository_url; } my $urls = {}; if ($repository_url !~ /hub.docker.com/) { return $urls; } if (!$namespace) { $urls->{repository} = "$repository_url/_/$name"; } if ($name && $namespace) { $urls->{repository} = "$repository_url/r/$namespace/$name"; } return $urls; } sub _gem_urls { my $purl = shift; my $name = $purl->name; my $version = $purl->version; if ($name && $version) { return { repository => "https://rubygems.org/gems/$name/versions/$version", download => "https://rubygems.org/downloads/$name-$version.gem" }; } return {repository => "https://rubygems.org/gems/$name"}; } sub _github_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $file_ext = $qualifiers->{ext} || 'tar.gz'; my $urls = {}; if ($name && $namespace) { $urls->{repository} = "https://github.com/$namespace/$name"; } if ($version) { my $is_sha1 = ($version =~ /^[a-fA-F0-9]{40}$/); if ($is_sha1) { $urls->{download} = "https://github.com/$namespace/$name/archive/$version.$file_ext"; } else { $urls->{download} = "https://github.com/$namespace/$name/archive/refs/tags/$version.$file_ext"; } } return $urls; } sub _gitlab_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $file_ext = $qualifiers->{ext} || 'tar.gz'; my $urls = {}; if ($name && $namespace) { $urls->{repository} = "https://gitlab.com/$namespace/$name"; } if ($version) { $urls->{download} = "https://gitlab.com/$namespace/$name/-/archive/$version/$name-$version.$file_ext"; } return $urls; } sub _golang_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; my $version = $purl->version; my $urls = {}; if ($name && $namespace) { $urls->{repository} = "https://pkg.go.dev/$namespace/$name"; } # TODO ??? # if ($name && $namespace && $version) { # $urls->{repository} = "https://pkg.go.dev/$namespace/$name\@v$version"; # } return $urls; } sub _luarocks_urls { my $purl = shift; my $name = $purl->name; my $namespace = $purl->namespace; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $repository_url = $qualifiers->{repository_url} || 'https://luarocks.org'; if ($repository_url !~ /^(http|https):\/\//) { $repository_url = 'https://' . $repository_url; } my $urls = {}; if (!$namespace) { $urls->{repository} = "$repository_url/modules/$name"; } if ($name && $namespace) { $urls->{repository} = "$repository_url/modules/$namespace/$name"; } return $urls; } sub _maven_urls { my $purl = shift; my $namespace = $purl->namespace; my $name = $purl->name; my $version = $purl->version; my $qualifiers = $purl->qualifiers; my $extension = $qualifiers->{extension} // 'jar'; my $repository_url = $qualifiers->{repository_url} // 'https://repo.maven.apache.org/maven2'; if ($repository_url !~ /^(http|https):\/\//) { $repository_url = 'https://' . $repository_url; } if ($namespace && $name && $version) { (my $ns_url = $namespace) =~ s/\./\//g; return { repository => "https://mvnrepository.com/artifact/$namespace/$name/$version", download => "$repository_url/$ns_url/$name/$version/$name-$version.$extension" }; } if ($namespace && $name) { return {repository => "https://mvnrepository.com/artifact/$namespace/$name"}; } } sub _npm_urls { my $purl = shift; my $namespace = $purl->namespace; my $name = $purl->name; my $version = $purl->version; if ($namespace && $name && $version) { return { repository => "https://www.npmjs.com/package/$namespace/$name/v/$version", download => "https://registry.npmjs.org/$namespace/$name/-/$name-$version.tgz" }; } if ($name && $version) { return { repository => "https://www.npmjs.com/package/$name/v/$version", download => "https://registry.npmjs.org/$name/-/$name-$version.tgz" }; } if ($namespace && $name) { return {repository => "https://www.npmjs.com/package/$namespace/$name"}; } return {repository => "https://www.npmjs.com/package/$name"}; } sub _nuget_urls { my $purl = shift; my $name = $purl->name; my $version = $purl->version; if ($name && $version) { return { repository => "https://www.nuget.org/packages/$name/$version", download => "https://www.nuget.org/api/v2/package/$name/$version" }; } return {repository => "https://www.nuget.org/packages/$name"}; } sub _pypi_urls { my $purl = shift; my $name = $purl->name; my $version = $purl->version; if ($name && $version) { return {repository => "https://pypi.org/project/$name/$version"}; } return {repository => "https://pypi.org/project/$name"}; } 1; __END__ =head1 NAME URI::PackageURL::Util - Utility for URI::PackageURL =head1 SYNOPSIS use URI::PackageURL::Util qw(purl_to_urls); $urls = purl_to_urls('pkg:cpan/GDT/URI-PackageURL@2.22'); $filename = basename($urls->{download}); $ua->mirror($urls->{download}, "/tmp/$filename"); =head1 DESCRIPTION URL::PackageURL::Util is the utility package for URL::PackageURL. =over =item %normalized_purl_components = purl_components_normalize(%purl_components) Normalize the given Package URL components =item $urls = purl_to_urls($purl_string | URI::PackageURL) Converts the given Package URL string or L instance and return the hash with C and/or C URL. B: This utility support few purl types (C, C, C, C, C, C, C, C, C, C, C, C, C). +-----------+------------+--------------+ | Type | Repository | Download (*) | +-----------+------------+--------------| | bitbucket | YES | YES | | cargo | YES | YES | | composer | YES | NO | | cpan | YES | YES | | docker | YES | NO | | gem | YES | YES | | generic | NO | YES (**) | | github | YES | YES | | gitlab | YES | YES | | luarocks | YES | NO | | maven | YES | YES | | npm | YES | YES | | nuget | YES | YES | | pypi | YES | NO | |-----------|------------|--------------+ (*) Only with B component (**) Only if B qualifier is provided $urls = purl_to_urls('pkg:cpan/GDT/URI-PackageURL@2.22'); print Dumper($urls); # $VAR1 = { # 'repository' => 'https://metacpan.org/release/GDT/URI-PackageURL-2.22', # 'download' => 'http://www.cpan.org/authors/id/G/GD/GDT/URI-PackageURL-2.22.tar.gz' # }; =back =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/giterlizzi/perl-URI-PackageURL.git =head1 AUTHOR =over 4 =item * Giuseppe Di Terlizzi =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2022-2024 by Giuseppe Di Terlizzi. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut URI-PackageURL-2.22/lib/URI/PackageURL/App.pm0000644000175000017500000001324114727620670017233 0ustar peppepeppepackage URI::PackageURL::App; use feature ':5.10'; use strict; use warnings; use utf8; use Getopt::Long qw(GetOptionsFromArray :config gnu_compat); use Pod::Usage qw(pod2usage); use Carp (); use JSON::PP (); use Data::Dumper (); use URI::PackageURL (); our $VERSION = '2.22'; sub cli_error { my ($error) = @_; $error =~ s/ at .* line \d+.*//; print STDERR "ERROR: $error\n"; } sub run { my ($class, @args) = @_; my %options = (format => 'json'); GetOptionsFromArray( \@args, \%options, qw( help|h man v download-url repository-url type=s namespace=s name=s version=s qualifiers|qualifier=s% subpath=s null|0 format=s json yaml dumper env ) ) or pod2usage(-verbose => 0); pod2usage(-exitstatus => 0, -verbose => 2) if defined $options{man}; pod2usage(-exitstatus => 0, -verbose => 0) if defined $options{help}; if (defined $options{v}) { (my $progname = $0) =~ s/.*\///; say <<"VERSION"; $progname version $URI::PackageURL::VERSION Copyright 2022-2024, Giuseppe Di Terlizzi This program is part of the "URI-PackageURL" distribution and is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Complete documentation for $progname can be found using 'man $progname' or on the internet at . VERSION return 0; } if (defined $options{type}) { my $purl = eval { URI::PackageURL->new( type => $options{type}, namespace => $options{namespace}, name => $options{name}, version => $options{version}, qualifiers => $options{qualifiers}, subpath => $options{subpath}, ); }; if ($@) { cli_error($@); return 1; } print "$purl\n"; return 0; } my ($purl_string) = @args; pod2usage(-verbose => 1) if !$purl_string; $options{format} = 'json' if defined $options{json}; $options{format} = 'yaml' if defined $options{yaml}; $options{format} = 'dumper' if defined $options{dumper}; $options{format} = 'env' if defined $options{env}; my $purl = eval { URI::PackageURL->from_string($purl_string) }; if ($@) { cli_error($@); return 1; } my $purl_urls = $purl->to_urls; if ($options{'download-url'}) { return 2 unless defined $purl_urls->{download}; print $purl_urls->{download} . (defined $options{null} ? "\0" : "\n"); return 0; } if ($options{'repository-url'}) { return 2 unless defined $purl_urls->{repository}; print $purl_urls->{repository} . ($options{null} ? "\0" : "\n"); return 0; } if ($options{format} eq 'json') { print JSON::PP->new->canonical->pretty(1)->convert_blessed(1)->encode($purl); return 0; } if ($options{format} eq 'dumper') { print Data::Dumper->new([$purl])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump; return 0; } if ($options{format} eq 'yaml') { if (eval { require YAML::XS }) { print YAML::XS::Dump($purl); return 0; } if (eval { require YAML }) { print YAML::Dump($purl); return 0; } cli_error 'YAML or YAML::XS module are missing'; return 255; } if ($options{format} eq 'env') { my %PURL_ENVS = ( PURL => $purl->to_string, PURL_TYPE => $purl->type, PURL_NAMESPACE => $purl->namespace, PURL_NAME => $purl->name, PURL_VERSION => $purl->version, PURL_SUBPATH => $purl->subpath, PURL_QUALIFIERS => (join ' ', sort keys %{$purl->qualifiers}), ); # Preserve order my @PURL_ENVS = qw(PURL PURL_TYPE PURL_NAMESPACE PURL_NAME PURL_VERSION PURL_SUBPATH PURL_QUALIFIERS); my $qualifiers = $purl->qualifiers; foreach my $qualifier (sort keys %{$qualifiers}) { my $key = "PURL_QUALIFIER_$qualifier"; push @PURL_ENVS, $key; $PURL_ENVS{$key} = $qualifiers->{$qualifier}; } if ($purl_urls) { if (defined $purl_urls->{download}) { push @PURL_ENVS, 'PURL_DOWNLOAD_URL'; $PURL_ENVS{PURL_DOWNLOAD_URL} = $purl_urls->{download}; } if (defined $purl_urls->{repository}) { push @PURL_ENVS, 'PURL_REPOSITORY_URL'; $PURL_ENVS{PURL_REPOSITORY_URL} = $purl_urls->{repository}; } } foreach my $key (@PURL_ENVS) { print sprintf qq{%s="%s"\n}, $key, $PURL_ENVS{$key} || q{}; } return 0; } } 1; __END__ =encoding utf-8 =head1 NAME URI::PackageURL::App - URL::PackageURL (purl) Command Line Interface =head1 SYNOPSIS use URI::PackageURL::App qw(run); run(\@ARGV); =head1 DESCRIPTION URI::PackageURL::App "Command Line Interface" helper module for C. =over =item URI::PackageURL->run(@args) Execute the command =item cli_error($error) Clean error =back =head1 AUTHOR L =head1 COPYRIGHT AND LICENSE Copyright © 2022-2024 L You may use and distribute this module according to the same terms that Perl is distributed under. =cut URI-PackageURL-2.22/bin/0000755000175000017500000000000014730116003013561 5ustar peppepeppeURI-PackageURL-2.22/bin/vers-tool0000644000175000017500000000327514617672755015476 0ustar peppepeppe#!/usr/bin/perl use strict; use warnings; use utf8; use URI::VersionRange::App; exit URI::VersionRange::App->run(@ARGV) unless caller(); 1; __END__ =encoding utf-8 =head1 NAME vers-tool - Version Range tool =head1 SYNOPSIS vers-tool [OPTIONS]...STRING vers-tool STRING --contains STRING vers-tool [--help|--man|-v] Options: --help Brief help message --man Full documentation -v Print version --contains=VERSION Check if a version is contained within a range --format=FORMAT Output format --json JSON output format (--format=json) -h, --human-readable Human-readable format (--format=human-readable) Examples: Decode a "vers" string: vers-tool "vers:cpan/1.00|>=2.00|<5.00" | jq Check if a version is contained within a range: vers-tool "vers:cpan/1.00|>=2.00|<5.00" --contains "2.20" Humanize "vers": vers-tool "vers:cpan/1.00|>=2.00|<5.00" --human-readable =head1 DESCRIPTION C Version Range tool =head1 EXAMPLES Decode a "vers" string: vers-tool "vers:cpan/1.00|>=2.00|<5.00" | jq Check if a version is contained within a range: vers-tool "vers:cpan/1.00|>=2.00|<5.00" --contains "2.20" Humanize "vers": vers-tool "vers:cpan/1.00|>=2.00|<5.00" --human-readable =head1 AUTHOR L =head1 COPYRIGHT AND LICENSE Copyright © 2022-2024 L You may use and distribute this module according to the same terms that Perl is distributed under. URI-PackageURL-2.22/bin/purl-tool0000644000175000017500000000525714610711345015461 0ustar peppepeppe#!/usr/bin/perl use strict; use warnings; use utf8; use URI::PackageURL::App; exit URI::PackageURL::App->run(@ARGV) unless caller(); 1; __END__ =encoding utf-8 =head1 NAME purl-tool - Package URL tool =head1 SYNOPSIS purl-tool [OPTIONS]...STRING purl-tool --type STRING [--namespace STRING] --name STRING [--version STRING] [--subpath STRING] [--qualifier KEY=VALUE [...]] purl-tool [--help|--man|-v] Options: --help Brief help message --man Full documentation -v Print version "purl" string encode options: --type=STRING Type --namespace=STRING Namespace (optional) --name=STRING Name --version=STRING Version (optional) --qualifier KEY=VALUE Qualifier key-value (optional) "purl" string decode options: --download-url Download URL --repository-url Repository URL -0, --null Return NULL char instead of new line --format=FORMAT Output format --dumper Data::Dumper format (--format=dumper) --json JSON output format (--format=json) --yaml YAML output format (--format=yaml) --env ENV output format (--format=env) Examples: Parse the given Package URL string and return JSON and send the STDOUT to jq: purl-tool pkg:cpan/GDT/URI-PackageURL@2.11 --json | jq Download the package from the repository using Package URL string: wget $(purl-tool pkg:cpan/GDT/URI-PackageURL@2.11 --download-url) Create a canonical Package URL string purl-tool --type cpan \ --namespace GDT \ --name URI-PackageURL \ --version 2.11 =head1 DESCRIPTION C Package URL tool =head1 EXAMPLES Parse the given Package URL string and return JSON and send the STDOUT to L: purl-tool pkg:cpan/GDT/URI-PackageURL@2.11 --json | jq Download the package from the repository using Package URL string: wget $(purl-tool pkg:cpan/GDT/URI-PackageURL@2.11 --download-url) Create a canonical Package URL string: purl-tool --type cpan \ --namespace GDT \ --name URI-PackageURL \ --version 2.11 =head1 AUTHOR L =head1 COPYRIGHT AND LICENSE Copyright © 2022-2024 L You may use and distribute this module according to the same terms that Perl is distributed under. URI-PackageURL-2.22/INSTALL.md0000644000175000017500000000540014442172755014460 0ustar peppepeppe# URI::PackageURL - Perl extension for Package URL (aka "purl") The INSTALL is used to introduce the module and provide instructions on how to install the module, any machine dependencies it may have (for example C compilers and installed libraries) and any other information that should be provided before the module is installed. ## INSTALLATION Using Makefile.PL: To install this module, run the following commands. perl Makefile.PL make make test make install Using App::cpanminus: cpanm URI::PackageURL ## SUPPORT AND DOCUMENTATION After installing, you can find documentation for this module with the perldoc command. perldoc URI::PackageURL You can also look for information at: * GitHub issues (report bugs here) https://github.com/giterlizzi/perl-URI-PackageURL/issues ## LICENSE AND COPYRIGHT Copyright (C) 2022-2023 Giuseppe Di Terlizzi This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: http://www.perlfoundation.org/artistic_license_2_0 Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. URI-PackageURL-2.22/Makefile.PL0000644000175000017500000000233514617672755015017 0ustar peppepeppe#!perl use strict; use warnings; use ExtUtils::MakeMaker; WriteMakefile( NAME => 'URI::PackageURL', AUTHOR => q{Giuseppe Di Terlizzi }, VERSION_FROM => 'lib/URI/PackageURL.pm', ABSTRACT_FROM => 'lib/URI/PackageURL.pm', LICENSE => 'artistic_2', EXE_FILES => ['bin/purl-tool'], MIN_PERL_VERSION => 5.010, PL_FILES => {}, CONFIGURE_REQUIRES => {'ExtUtils::MakeMaker' => '0'}, TEST_REQUIRES => {'Test::More' => '0', 'JSON::PP' => '0', 'CPAN::DistnameInfo' => 0}, PREREQ_PM => {'JSON::PP' => '0', 'List::Util' => '0'}, META_MERGE => { 'meta-spec' => {version => 2}, 'resources' => { bugtracker => {web => 'https://github.com/giterlizzi/perl-URI-PackageURL/issues'}, repository => { type => 'git', url => 'git://github.com/giterlizzi/perl-URI-PackageURL', web => 'https://github.com/giterlizzi/perl-URI-PackageURL' }, }, x_purl => 'pkg:cpan/GDT/URI-PackageURL' }, dist => {COMPRESS => 'gzip -9f', SUFFIX => 'gz',}, clean => {FILES => 'URI-PackageURL-*'}, ); URI-PackageURL-2.22/MANIFEST0000644000175000017500000000143714730116003014147 0ustar peppepeppebin/purl-tool bin/vers-tool Changes examples/cpan-dist-download-and-test.sh examples/rpm-to-purl.sh examples/version-comparators.pl INSTALL.md lib/URI/PackageURL.pm lib/URI/PackageURL/App.pm lib/URI/PackageURL/Util.pm lib/URI/VersionRange.pm lib/URI/VersionRange/App.pm lib/URI/VersionRange/Constraint.pm lib/URI/VersionRange/Version.pm LICENSE Makefile.PL MANIFEST This list of files README.md t/00-load.t t/10-encode.t t/20-decode.t t/30-util.t t/40-cli.t t/50-version-range.t t/90-cpan-distname-info.t t/99-official-purl-test-suite.t t/manifest.t t/pod-coverage.t t/pod.t t/sync-purl-test-suite-data.sh t/test-suite-data.json META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) URI-PackageURL-2.22/examples/0000755000175000017500000000000014730116003014627 5ustar peppepeppeURI-PackageURL-2.22/examples/rpm-to-purl.sh0000644000175000017500000000117114610552121017363 0ustar peppepeppe#!/bin/bash # rpm-to-purl - Convert all installed RPM packages in purl string # (C) 2023, Giuseppe Di Terlizzi # License MIT . /etc/os-release PACKAGES=$(rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n') while IFS= read -r LINE; do PKG_NAME=$(echo $LINE | cut -d ' ' -f1) PKG_VER=$(echo $LINE | cut -d ' ' -f2) PKG_ARCH=$(echo $LINE | cut -d ' ' -f3) purl-tool --type rpm \ --namespace $ID \ --name $PKG_NAME \ --version $PKG_VER \ --qualifier distro=$ID-$VERSION_ID \ --qualifier arch=$PKG_ARCH done <<< "$PACKAGES" URI-PackageURL-2.22/examples/version-comparators.pl0000644000175000017500000000114514617672755021233 0ustar peppepeppepackage URI::VersionRange::Version::generic { use Version::libversion::XS; use parent 'URI::VersionRange::Version'; use overload ('cmp' => \&compare, '<=>' => \&compare, fallback => 1); sub compare { my ($left, $right) = @_; return version_compare2($left->[0], $right->[0]); } } package URI::VersionRange::Version::rpm { use RPM4; use parent 'URI::VersionRange::Version'; use overload ('cmp' => \&compare, '<=>' => \&compare, fallback => 1); sub compare { my ($left, $right) = @_; return rpmvercmp($left->[0], $right->[0]); } } 1; URI-PackageURL-2.22/examples/cpan-dist-download-and-test.sh0000644000175000017500000000144114610552121022371 0ustar peppepeppe#!/bin/bash # cpan-dist-download-and-test - Download and test the provided cpan distribution using "purl" string # (C) 2023, Giuseppe Di Terlizzi # License MIT set -e PURL=$1 if [[ -z "$PURL" ]]; then echo "Usage: $0 PURL" echo "" echo " Example:" echo " $0 pkg:cpan/GDT/URI-PackageURL@2.04" echo "" exit 1 fi eval $(purl-tool "$PURL" --env) if [[ "$PURL_TYPE" != "cpan" ]]; then echo "[ERROR] Not 'cpan' type component" exit 1 fi if [[ -z "$PURL_DOWNLOAD_URL" ]]; then echo "[ERROR] Missing PURL_DOWNLOAD_URL" exit 1 fi echo "Download $PURL_NAME $PURL_VERSION" wget $PURL_DOWNLOAD_URL echo "Build and test module $PURL_NAME $PURL_VERSION" tar xvf $PURL_NAME-$PURL_VERSION.tar.gz cd $PURL_NAME-$PURL_VERSION perl Makefile.PL make && make test URI-PackageURL-2.22/META.yml0000644000175000017500000000152614730116003014266 0ustar peppepeppe--- abstract: 'Perl extension for Package URL (aka "purl")' author: - 'Giuseppe Di Terlizzi ' build_requires: CPAN::DistnameInfo: '0' ExtUtils::MakeMaker: '0' JSON::PP: '0' Test::More: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: URI-PackageURL no_index: directory: - t - inc requires: JSON::PP: '0' List::Util: '0' perl: '5.010' resources: bugtracker: https://github.com/giterlizzi/perl-URI-PackageURL/issues repository: git://github.com/giterlizzi/perl-URI-PackageURL version: '2.22' x_purl: pkg:cpan/GDT/URI-PackageURL x_serialization_backend: 'CPAN::Meta::YAML version 0.018'