A Ruby library for parsing, validating, and generating Package URLs (PURLs) as defined by the PURL specification.
This library features comprehensive error handling with namespaced error types, bidirectional registry URL conversion, and JSON-based configuration for cross-language compatibility.
Available on RubyGems | API Documentation
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
The purl gem includes a command-line interface that provides convenient access to all parsing, validation, conversion, and generation functionality.
The CLI is automatically available after installing the gem:
gem install purl purl --help
purl parse <purl-string> # Parse and display PURL components purl validate <purl-string> # Validate a PURL (exit code indicates success) purl convert <registry-url> # Convert registry URL to PURL purl url <purl-string> # Convert PURL to registry URL purl generate [options] # Generate PURL from components purl info [type] # Show information about PURL types purl lookup <purl-string> # Look up package information from ecosyste.ms
All commands support JSON output with the --json
flag:
purl --json parse "pkg:gem/rails@7.0.0" purl --json info gem purl --json lookup "pkg:cargo/rand"
$ purl parse "pkg:gem/rails@7.0.0" Valid PURL: pkg:gem/rails@7.0.0 Components: Type: gem Namespace: (none) Name: rails Version: 7.0.0 Qualifiers: (none) Subpath: (none) $ purl --json parse "pkg:npm/@babel/core@7.0.0" { "success": true, "purl": "pkg:npm/%40babel/core@7.0.0", "components": { "type": "npm", "namespace": "@babel", "name": "core", "version": "7.0.0", "qualifiers": {}, "subpath": null } }
$ purl validate "pkg:gem/rails@7.0.0" Valid PURL $ purl validate "invalid-purl" Invalid PURL: PURL must start with 'pkg:'Convert Registry URL to PURL
$ purl convert "https://rubygems.org/gems/rails" pkg:gem/rails $ purl convert "https://www.npmjs.com/package/@babel/core" pkg:npm/@babel/coreConvert PURL to Registry URL
$ purl url "pkg:gem/rails@7.0.0" https://rubygems.org/gems/rails $ purl url "pkg:npm/@babel/core@7.0.0" https://www.npmjs.com/package/@babel/core $ purl --json url "pkg:gem/rails@7.0.0" { "success": true, "purl": "pkg:gem/rails@7.0.0", "registry_url": "https://rubygems.org/gems/rails", "type": "gem" }
$ purl generate --type gem --name rails --version 7.0.0 pkg:gem/rails@7.0.0 $ purl generate --type npm --namespace @babel --name core --version 7.0.0 --qualifiers "arch=x64,os=linux" pkg:npm/%40babel/core@7.0.0?arch=x64&os=linux
$ purl info gem Type: gem Known: Yes Description: RubyGems Default registry: https://rubygems.org Registry URL generation: Yes Reverse parsing: Yes Examples: pkg:gem/ruby-advisory-db-check@0.12.4 pkg:gem/rails@7.0.4 pkg:gem/bundler@2.3.26 Registry URL patterns: https://rubygems.org/gems/:name https://rubygems.org/gems/:name/versions/:version $ purl info # Shows all types Known PURL types: alpm Description: Arch Linux packages Registry support: No Reverse parsing: No ... Total types: 37 Registry supported: 20Look Up Package Information
$ purl lookup "pkg:cargo/rand" Package: rand (cargo) Description: Random number generators and other randomness functionality. Homepage: https://rust-random.github.io/book Repository: https://github.com/rust-random/rand License: MIT OR Apache-2.0 Downloads: 145,678,901 Latest Version: 0.8.5 Published: 2023-01-13T17:47:01.870Z $ purl --json lookup "pkg:cargo/rand@0.8.5" { "success": true, "purl": "pkg:cargo/rand@0.8.5", "package": { "name": "rand", "ecosystem": "cargo", "description": "Random number generators and other randomness functionality.", "homepage": "https://rust-random.github.io/book", "repository_url": "https://github.com/rust-random/rand", "registry_url": "https://crates.io/crates/rand", "licenses": "MIT OR Apache-2.0", "latest_version": "0.8.5", "latest_version_published_at": "2023-01-13T17:47:01.870Z", "versions_count": 89, "maintainers": [ { "login": "dhardy", "name": "Diggory Hardy" } ] }, "version": { "number": "0.8.5", "published_at": "2023-01-13T17:47:01.870Z", "registry_url": "https://crates.io/crates/rand/0.8.5", "downloads": 5678901, "size": 102400 } }
The generate
command supports all PURL components:
purl generate --help Usage: purl generate [options] --type TYPE Package type (required) --name NAME Package name (required) --namespace NAMESPACE Package namespace --version VERSION Package version --qualifiers QUALIFIERS Qualifiers as key=value,key2=value2 --subpath SUBPATH Package subpath -h, --help Show this help
The CLI uses standard exit codes:
0
- Success1
- Error (invalid PURL, unsupported operation, etc.)This makes the CLI suitable for use in scripts and CI/CD pipelines:
if purl validate "pkg:gem/rails@7.0.0"; then echo "Valid PURL" else echo "Invalid PURL" exit 1 fi
require 'purl' # Parse a PURL string purl = Purl.parse("pkg:gem/rails@7.0.0") puts purl.type # => "gem" puts purl.name # => "rails" puts purl.version # => "7.0.0" puts purl.namespace # => nil # Parse with namespace and qualifiers purl = Purl.parse("pkg:npm/@babel/core@7.0.0?arch=x86_64") puts purl.type # => "npm" puts purl.namespace # => "@babel" puts purl.name # => "core" puts purl.version # => "7.0.0" puts purl.qualifiers # => {"arch" => "x86_64"}
# Create a PURL object purl = Purl::PackageURL.new( type: "maven", namespace: "org.apache.commons", name: "commons-lang3", version: "3.12.0" ) puts purl.to_s # => "pkg:maven/org.apache.commons/commons-lang3@3.12.0"
PURL objects are immutable by design, but you can create new objects with modified attributes using the with
method:
# Create original PURL original = Purl::PackageURL.new( type: "npm", namespace: "@babel", name: "core", version: "7.20.0", qualifiers: { "arch" => "x64" } ) # Create new PURL with updated version updated = original.with(version: "7.21.0") puts updated.to_s # => "pkg:npm/@babel/core@7.21.0?arch=x64" # Update qualifiers with_new_qualifiers = original.with( qualifiers: { "arch" => "arm64", "os" => "linux" } ) puts with_new_qualifiers.to_s # => "pkg:npm/@babel/core@7.20.0?arch=arm64&os=linux" # Update multiple attributes at once fully_updated = original.with( version: "8.0.0", qualifiers: { "dev" => "true" }, subpath: "lib/index.js" ) puts fully_updated.to_s # => "pkg:npm/@babel/core@8.0.0#lib/index.js?dev=true" # Original remains unchanged puts original.to_s # => "pkg:npm/@babel/core@7.20.0?arch=x64"
# Generate registry URLs from PURLs purl = Purl.parse("pkg:gem/rails@7.0.0") puts purl.registry_url # => "https://rubygems.org/gems/rails" puts purl.registry_url_with_version # => "https://rubygems.org/gems/rails/versions/7.0.0" # Check if registry URL generation is supported puts purl.supports_registry_url? # => true # NPM with scoped packages purl = Purl.parse("pkg:npm/@babel/core@7.0.0") puts purl.registry_url # => "https://www.npmjs.com/package/@babel/core"Reverse Parsing: Registry URLs to PURLs
# Parse registry URLs back to PURLs purl = Purl.from_registry_url("https://rubygems.org/gems/rails/versions/7.0.0") puts purl.to_s # => "pkg:gem/rails@7.0.0" # Works with various registries purl = Purl.from_registry_url("https://www.npmjs.com/package/@babel/core") puts purl.to_s # => "pkg:npm/@babel/core" purl = Purl.from_registry_url("https://pypi.org/project/django/4.0.0/") puts purl.to_s # => "pkg:pypi/django@4.0.0"
You can parse registry URLs from custom domains or generate URLs for private registries:
# Parse from custom domain (specify type to help with parsing) purl = Purl.from_registry_url("https://npm.company.com/package/@babel/core", type: "npm") puts purl.to_s # => "pkg:npm/@babel/core" # Generate URLs for custom registries purl = Purl.parse("pkg:gem/rails@7.0.0") custom_url = purl.registry_url(base_url: "https://gems.internal.com/gems") puts custom_url # => "https://gems.internal.com/gems/rails" # With version-specific URLs with_version = purl.registry_url_with_version(base_url: "https://gems.internal.com/gems") puts with_version # => "https://gems.internal.com/gems/rails/versions/7.0.0" # Works with all supported package types composer_purl = Purl.parse("pkg:composer/symfony/console@5.4.0") private_composer = composer_purl.registry_url(base_url: "https://packagist.company.com/packages") puts private_composer # => "https://packagist.company.com/packages/symfony/console"
# Get route patterns for a package type (Rails-style) patterns = Purl::RegistryURL.route_patterns_for("gem") # => ["https://rubygems.org/gems/:name", "https://rubygems.org/gems/:name/versions/:version"] # Get all route patterns all_patterns = Purl::RegistryURL.all_route_patterns puts all_patterns["npm"] # => ["https://www.npmjs.com/package/:namespace/:name", "https://www.npmjs.com/package/:name", ...]
Qualifiers are key-value pairs that provide additional metadata about packages:
# Create PURL with qualifiers purl = Purl::PackageURL.new( type: "apk", name: "curl", version: "7.83.0-r0", qualifiers: { "distro" => "alpine-3.16", "arch" => "x86_64", "repository_url" => "https://dl-cdn.alpinelinux.org" } ) puts purl.to_s # => "pkg:apk/curl@7.83.0-r0?arch=x86_64&distro=alpine-3.16&repository_url=https://dl-cdn.alpinelinux.org" # Access qualifiers puts purl.qualifiers["distro"] # => "alpine-3.16" puts purl.qualifiers["arch"] # => "x86_64" # Parse PURL with qualifiers parsed = Purl.parse("pkg:rpm/httpd@2.4.53?distro=fedora-36&arch=x86_64") puts parsed.qualifiers # => {"distro" => "fedora-36", "arch" => "x86_64"} # Add qualifiers to existing PURL with_qualifiers = purl.with(qualifiers: purl.qualifiers.merge("signed" => "true"))
# Get all known PURL types puts Purl.known_types.length # => 37 puts Purl.known_types.include?("gem") # => true # Check type support puts Purl.known_type?("gem") # => true puts Purl.registry_supported_types # => ["cargo", "gem", "maven", "npm", ...] puts Purl.reverse_parsing_supported_types # => ["bioconductor", "cargo", "clojars", ...] # Get default registry for a type puts Purl.default_registry("gem") # => "https://rubygems.org" puts Purl.default_registry("npm") # => "https://registry.npmjs.org" puts Purl.default_registry("golang") # => nil (no default) # Get official examples for a type puts Purl.type_examples("gem") # => ["pkg:gem/rails@7.0.4", "pkg:gem/bundler@2.3.26", ...] puts Purl.type_examples("npm") # => ["pkg:npm/lodash@4.17.21", "pkg:npm/@babel/core@7.20.0", ...] puts Purl.type_examples("unknown") # => [] # Get detailed type information info = Purl.type_info("gem") puts info[:known] # => true puts info[:description] # => "RubyGems" puts info[:default_registry] # => "https://rubygems.org" puts info[:examples] # => ["pkg:gem/rails@7.0.4", ...] puts info[:registry_url_generation] # => true puts info[:reverse_parsing] # => true puts info[:route_patterns] # => ["https://rubygems.org/gems/:name", ...]
# Detailed error types with context begin Purl.parse("invalid-purl") rescue Purl::InvalidSchemeError => e puts "Scheme error: #{e.message}" rescue Purl::ParseError => e puts "Parse error: #{e.message}" puts "Component: #{e.component}" puts "Value: #{e.value}" end # Type-specific validation errors begin Purl::PackageURL.new(type: "swift", name: "Alamofire") # Swift requires namespace rescue Purl::ValidationError => e puts e.message # => "Swift PURLs require a namespace to be unambiguous" end
The library supports 37 package types (32 official + 5 additional ecosystems):
Registry URL Generation (20 types):
bioconductor
(R/Bioconductor) - bioconductor.orgcargo
(Rust) - crates.ioclojars
(Clojure) - clojars.orgcocoapods
(iOS) - cocoapods.orgcomposer
(PHP) - packagist.orgconda
(Python) - anaconda.orgcpan
(Perl) - metacpan.orgdeno
(Deno) - deno.land/xelm
(Elm) - package.elm-lang.orggem
(Ruby) - rubygems.orggolang
(Go) - pkg.go.devhackage
(Haskell) - hackage.haskell.orghex
(Elixir) - hex.pmhomebrew
(macOS) - formulae.brew.shmaven
(Java) - mvnrepository.comnpm
(Node.js) - npmjs.comnuget
(.NET) - nuget.orgpub
(Dart) - pub.devpypi
(Python) - pypi.orgswift
(Swift) - swiftpackageindex.comReverse Parsing (20 types):
bioconductor
, cargo
, clojars
, cocoapods
, composer
, conda
, cpan
, deno
, elm
, gem
, golang
, hackage
, hex
, homebrew
, maven
, npm
, nuget
, pub
, pypi
, swift
All 37 Supported Types: alpm
, apk
, bioconductor
, bitbucket
, bitnami
, cargo
, clojars
, cocoapods
, composer
, conan
, conda
, cpan
, cran
, deb
, deno
, docker
, elm
, gem
, generic
, github
, golang
, hackage
, hex
, homebrew
, huggingface
, luarocks
, maven
, mlflow
, npm
, nuget
, oci
, pub
, pypi
, qpkg
, rpm
, swid
, swift
Package types and registry patterns are stored in purl-types.json
for easy contribution and cross-language compatibility:
{ "version": "1.0.0", "description": "PURL types and registry URL patterns for package ecosystems", "source": "https://github.com/package-url/purl-spec/blob/main/PURL-TYPES.rst", "last_updated": "2025-07-24", "types": { "gem": { "description": "RubyGems", "default_registry": "https://rubygems.org", "registry_config": { "path_template": "/gems/:name", "version_path_template": "/gems/:name/versions/:version", "reverse_regex": "/gems/([^/?#]+)(?:/versions/([^/?#]+))?", "components": { "namespace": false, "version_in_url": true, "version_path": "/versions/" } } } } }
Key Configuration Improvements:
path_template
without hardcoded domains enables custom registriesdefault_registry
+ path_template
for any domainThe library includes JSON schemas for validation and documentation:
schemas/purl-types.schema.json
- Schema for the PURL types configuration fileschemas/test-suite-data.schema.json
- Schema for the official test suite dataThese schemas provide:
Validate JSON files against their schemas:
rake spec:validate_schemas
After checking out the repo, run bin/setup
to install dependencies. Then:
# Run tests rake test # Run specification compliance tests rake spec:compliance # Update test cases from official PURL spec rake spec:update # Show type information rake spec:types # Verify types against official specification rake spec:verify_typesTesting Against Official Specification
This library includes the official purl-spec repository as a git submodule for testing and validation:
# Initialize submodule (first time only) git submodule update --init --recursive # Update submodule to latest spec git submodule update --remote purl-spec
The tests use files from the submodule to:
purl-types.json
against the official schema in purl-spec/schemas/
purl-spec/types/
purl-spec/tests/
The submodule is automatically updated weekly via Dependabot, ensuring tests stay current with the latest specification changes. When the submodule updates, you can review and merge the PR to adopt new spec requirements.
rake spec:update
- Fetch latest test cases from official PURL spec repositoryrake spec:compliance
- Run compliance tests against official test suiterake spec:types
- Show information about all PURL types and their supportrake spec:verify_types
- Verify our types list against official specificationrake spec:validate_schemas
- Validate JSON files against their schemasrake spec:debug
- Show detailed info about failing test casesIf you find this project useful, please consider supporting its development:
Your support helps maintain and improve this library for the entire Ruby community.
Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
git checkout -b my-new-feature
)rake test
)git commit -am 'Add some feature'
)git push origin my-new-feature
)To add support for a new package type:
purl-types.json
with the new type configurationlib/purl/package_url.rb
This gem is available as open source under the terms of the MIT License.
See CHANGELOG.md for a detailed history of changes and releases.
Everyone interacting in the Purl project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the Contributor Covenant Code of Conduct.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4