https://github.com/andrew/purl
A Ruby library for parsing, validating, and generating Package URLs (PURLs) as defined by the PURL specification
Science Score: 26.0%
This score indicates how likely this project is to be science-related based on various indicators:
-
○CITATION.cff file
-
✓codemeta.json file
Found codemeta.json file -
✓.zenodo.json file
Found .zenodo.json file -
○DOI references
-
○Academic publication links
-
○Committers with academic emails
-
○Institutional organization owner
-
○JOSS paper metadata
-
○Scientific vocabulary similarity
Low similarity (11.7%) to scientific vocabulary
Keywords
Keywords from Contributors
Repository
A Ruby library for parsing, validating, and generating Package URLs (PURLs) as defined by the PURL specification
Basic Info
- Host: GitHub
- Owner: andrew
- License: mit
- Language: Ruby
- Default Branch: main
- Homepage: https://rubygems.org/gems/purl
- Size: 158 KB
Statistics
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 2
- Releases: 11
Topics
Metadata Files
README.md
Purl - Package URL Parser for Ruby
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
Related Libraries
- Vers - A Ruby library for working with version ranges that supports the VERS specification
Features
- Command-line interface with parse, validate, convert, generate, and info commands plus JSON output
- Comprehensive PURL parsing and validation with 37 package types (32 official + 5 additional ecosystems)
- Better error handling with namespaced error classes and contextual information
- Bidirectional registry URL conversion - generate registry URLs from PURLs and parse PURLs from registry URLs
- Type-specific validation for conan, cran, and swift packages
- Registry URL generation for 20 package ecosystems (npm, gem, maven, pypi, etc.)
- Rails-style route patterns for registry URL templates
- 100% compliance with official PURL specification test suite (59/59 tests passing)
- Cross-language compatibility with JSON-based configuration
- Comprehensive documentation and examples
Installation
Add this line to your application's Gemfile:
ruby
gem 'purl'
And then execute:
bash
bundle install
Or install it yourself as:
bash
gem install purl
Command Line Interface
The purl gem includes a command-line interface that provides convenient access to all parsing, validation, conversion, and generation functionality.
Installation
The CLI is automatically available after installing the gem:
bash
gem install purl
purl --help
Available Commands
bash
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
JSON Output
All commands support JSON output with the --json flag:
bash
purl --json parse "pkg:gem/rails@7.0.0"
purl --json info gem
purl --json lookup "pkg:cargo/rand"
Command Examples
Parse a PURL
```bash $ 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 } } ```
Validate a PURL
```bash $ 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
```bash $ purl convert "https://rubygems.org/gems/rails" pkg:gem/rails
$ purl convert "https://www.npmjs.com/package/@babel/core" pkg:npm/@babel/core ```
Convert PURL to Registry URL
```bash $ 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" } ```
Generate a PURL
```bash $ 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 ```
Show Type Information
```bash $ 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: 20 ```
Look Up Package Information
```bash $ 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", "repositoryurl": "https://github.com/rust-random/rand", "registryurl": "https://crates.io/crates/rand", "licenses": "MIT OR Apache-2.0", "latestversion": "0.8.5", "latestversionpublishedat": "2023-01-13T17:47:01.870Z", "versionscount": 89, "maintainers": [ { "login": "dhardy", "name": "Diggory Hardy" } ] }, "version": { "number": "0.8.5", "publishedat": "2023-01-13T17:47:01.870Z", "registry_url": "https://crates.io/crates/rand/0.8.5", "downloads": 5678901, "size": 102400 } } ```
Generate Options
The generate command supports all PURL components:
bash
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
Exit Codes
The CLI uses standard exit codes:
- 0 - Success
- 1 - Error (invalid PURL, unsupported operation, etc.)
This makes the CLI suitable for use in scripts and CI/CD pipelines:
bash
if purl validate "pkg:gem/rails@7.0.0"; then
echo "Valid PURL"
else
echo "Invalid PURL"
exit 1
fi
Library Usage
Basic PURL Parsing
```ruby 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=x8664") puts purl.type # => "npm" puts purl.namespace # => "@babel" puts purl.name # => "core" puts purl.version # => "7.0.0" puts purl.qualifiers # => {"arch" => "x8664"} ```
Creating PURLs
```ruby
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" ```
Modifying PURL Objects
PURL objects are immutable by design, but you can create new objects with modified attributes using the with method:
```ruby
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
withnewqualifiers = original.with( qualifiers: { "arch" => "arm64", "os" => "linux" } ) puts withnewqualifiers.to_s # => "pkg:npm/@babel/core@7.20.0?arch=arm64&os=linux"
Update multiple attributes at once
fullyupdated = original.with( version: "8.0.0", qualifiers: { "dev" => "true" }, subpath: "lib/index.js" ) puts fullyupdated.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" ```
Registry URL Generation
```ruby
Generate registry URLs from PURLs
purl = Purl.parse("pkg:gem/rails@7.0.0") puts purl.registryurl # => "https://rubygems.org/gems/rails" puts purl.registryurlwithversion # => "https://rubygems.org/gems/rails/versions/7.0.0"
Check if registry URL generation is supported
puts purl.supportsregistryurl? # => 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
```ruby
Parse registry URLs back to PURLs
purl = Purl.fromregistryurl("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.fromregistryurl("https://www.npmjs.com/package/@babel/core") puts purl.to_s # => "pkg:npm/@babel/core"
purl = Purl.fromregistryurl("https://pypi.org/project/django/4.0.0/") puts purl.to_s # => "pkg:pypi/django@4.0.0" ```
Custom Registry Domains
You can parse registry URLs from custom domains or generate URLs for private registries:
```ruby
Parse from custom domain (specify type to help with parsing)
purl = Purl.fromregistryurl("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") customurl = purl.registryurl(baseurl: "https://gems.internal.com/gems") puts customurl # => "https://gems.internal.com/gems/rails"
With version-specific URLs
withversion = purl.registryurlwithversion(baseurl: "https://gems.internal.com/gems") puts withversion # => "https://gems.internal.com/gems/rails/versions/7.0.0"
Works with all supported package types
composerpurl = Purl.parse("pkg:composer/symfony/console@5.4.0") privatecomposer = composerpurl.registryurl(baseurl: "https://packagist.company.com/packages") puts privatecomposer # => "https://packagist.company.com/packages/symfony/console" ```
Route Patterns
```ruby
Get route patterns for a package type (Rails-style)
patterns = Purl::RegistryURL.routepatternsfor("gem")
=> ["https://rubygems.org/gems/:name", "https://rubygems.org/gems/:name/versions/:version"]
Get all route patterns
allpatterns = Purl::RegistryURL.allroutepatterns puts allpatterns["npm"]
=> ["https://www.npmjs.com/package/:namespace/:name", "https://www.npmjs.com/package/:name", ...]
```
Working with Qualifiers
Qualifiers are key-value pairs that provide additional metadata about packages:
```ruby
Create PURL with qualifiers
purl = Purl::PackageURL.new( type: "apk", name: "curl", version: "7.83.0-r0", qualifiers: { "distro" => "alpine-3.16", "arch" => "x8664", "repositoryurl" => "https://dl-cdn.alpinelinux.org" } ) puts purl.tos # => "pkg:apk/curl@7.83.0-r0?arch=x8664&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=x8664") puts parsed.qualifiers # => {"distro" => "fedora-36", "arch" => "x8664"}
Add qualifiers to existing PURL
with_qualifiers = purl.with(qualifiers: purl.qualifiers.merge("signed" => "true")) ```
Package Type Information
```ruby
Get all known PURL types
puts Purl.knowntypes.length # => 37 puts Purl.knowntypes.include?("gem") # => true
Check type support
puts Purl.knowntype?("gem") # => true puts Purl.registrysupportedtypes # => ["cargo", "gem", "maven", "npm", ...] puts Purl.reverseparsingsupportedtypes # => ["bioconductor", "cargo", "clojars", ...]
Get default registry for a type
puts Purl.defaultregistry("gem") # => "https://rubygems.org" puts Purl.defaultregistry("npm") # => "https://registry.npmjs.org" puts Purl.default_registry("golang") # => nil (no default)
Get official examples for a type
puts Purl.typeexamples("gem") # => ["pkg:gem/rails@7.0.4", "pkg:gem/bundler@2.3.26", ...] puts Purl.typeexamples("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.typeinfo("gem") puts info[:known] # => true puts info[:description] # => "RubyGems" puts info[:defaultregistry] # => "https://rubygems.org" puts info[:examples] # => ["pkg:gem/rails@7.0.4", ...] puts info[:registryurlgeneration] # => true puts info[:reverseparsing] # => true puts info[:routepatterns] # => ["https://rubygems.org/gems/:name", ...] ```
Error Handling
```ruby
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 ```
Supported Package Types
The library supports 37 package types (32 official + 5 additional ecosystems):
Registry URL Generation (20 types):
- bioconductor (R/Bioconductor) - bioconductor.org
- cargo (Rust) - crates.io
- clojars (Clojure) - clojars.org
- cocoapods (iOS) - cocoapods.org
- composer (PHP) - packagist.org
- conda (Python) - anaconda.org
- cpan (Perl) - metacpan.org
- deno (Deno) - deno.land/x
- elm (Elm) - package.elm-lang.org
- gem (Ruby) - rubygems.org
- golang (Go) - pkg.go.dev
- hackage (Haskell) - hackage.haskell.org
- hex (Elixir) - hex.pm
- homebrew (macOS) - formulae.brew.sh
- maven (Java) - mvnrepository.com
- npm (Node.js) - npmjs.com
- nuget (.NET) - nuget.org
- pub (Dart) - pub.dev
- pypi (Python) - pypi.org
- swift (Swift) - swiftpackageindex.com
Reverse 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
Specification Compliance
- 100% compliance with the official PURL specification test suite (59/59 tests passing)
- All 32 official package types plus 5 additional ecosystem types supported
- Type-specific validation for conan, cran, swift, cpan, and mlflow packages
- Proper error handling for invalid PURLs that should be rejected
JSON Configuration
Package types and registry patterns are stored in purl-types.json for easy contribution and cross-language compatibility:
json
{
"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:
- Domain-agnostic patterns: path_template without hardcoded domains enables custom registries
- Flexible URL generation: Combine default_registry + path_template for any domain
- Cleaner JSON: Reduced duplication and easier maintenance
- Cross-registry compatibility: Same URL structure works with public and private registries
JSON Schema Validation
The 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 data
These schemas provide: - Structure validation - Ensure JSON files conform to expected format - Documentation - Self-documenting configuration with descriptions - IDE support - Enable autocomplete and validation in editors - CI/CD integration - Validate configuration in automated pipelines
Validate JSON files against their schemas:
bash
rake spec:validate_schemas
Development
After checking out the repo, run bin/setup to install dependencies. Then:
```bash
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_types ```
Testing Against Official Specification
This library includes the official purl-spec repository as a git submodule for testing and validation:
```bash
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:
- Schema validation: Validate our purl-types.json against the official schema in purl-spec/schemas/
- Type compliance: Ensure our supported types match the official types in purl-spec/types/
- Test data: Access official test cases and examples from 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 Tasks
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 cases
Funding
If you find this project useful, please consider supporting its development:
Your support helps maintain and improve this library for the entire Ruby community.
Contributing
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.
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Make your changes
- Add tests for your changes
- Ensure all tests pass (
rake test) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request
Adding New Package Types
To add support for a new package type:
- Update
purl-types.jsonwith the new type configuration - Add registry URL patterns if applicable
- Add type-specific validation rules if needed in
lib/purl/package_url.rb - Add tests for the new functionality
License
This gem is available as open source under the terms of the MIT License.
Changelog
See CHANGELOG.md for a detailed history of changes and releases.
Code of Conduct
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.
Owner
- Name: Andrew Nesbitt
- Login: andrew
- Kind: user
- Location: Bristol, UK
- Company: @ecosyste-ms and @octobox
- Website: https://nesbitt.io
- Twitter: teabass
- Repositories: 357
- Profile: https://github.com/andrew
Working on mapping the world of open source software @ecosyste-ms and empowering developers with @octobox
GitHub Events
Total
- Create event: 7
- Release event: 6
- Issues event: 3
- Watch event: 1
- Delete event: 2
- Issue comment event: 2
- Push event: 26
- Pull request event: 5
Last Year
- Create event: 7
- Release event: 6
- Issues event: 3
- Watch event: 1
- Delete event: 2
- Issue comment event: 2
- Push event: 26
- Pull request event: 5
Committers
Last synced: 6 months ago
Top Committers
| Name | Commits | |
|---|---|---|
| Andrew Nesbitt | a****z@g****m | 52 |
| dependabot[bot] | 4****]@u****m | 4 |
Issues and Pull Requests
Last synced: 6 months ago
All Time
- Total issues: 0
- Total pull requests: 0
- Average time to close issues: N/A
- Average time to close pull requests: N/A
- Total issue authors: 0
- Total pull request authors: 0
- Average comments per issue: 0
- Average comments per pull request: 0
- Merged pull requests: 0
- Bot issues: 0
- Bot pull requests: 0
Past Year
- Issues: 0
- Pull requests: 0
- Average time to close issues: N/A
- Average time to close pull requests: N/A
- Issue authors: 0
- Pull request authors: 0
- Average comments per issue: 0
- Average comments per pull request: 0
- Merged pull requests: 0
- Bot issues: 0
- Bot pull requests: 0
Top Authors
Issue Authors
- andrew (3)
Pull Request Authors
- dependabot[bot] (2)
- andrew (1)
Top Labels
Issue Labels
Pull Request Labels
Packages
- Total packages: 1
-
Total downloads:
- rubygems 2,466 total
- Total dependent packages: 0
- Total dependent repositories: 0
- Total versions: 11
- Total maintainers: 1
rubygems.org: purl
This library features comprehensive error handling with namespaced error types, bidirectional registry URL conversion, and JSON-based configuration for cross-language compatibility. It supports 37 package types (32 official + 5 additional ecosystems) and is fully compliant with the official PURL specification test suite.
- Homepage: https://github.com/andrew/purl
- Documentation: http://www.rubydoc.info/gems/purl/
- License: mit
-
Latest release: 1.5.2
published 7 months ago
Rankings
Maintainers (1)
Dependencies
- actions/checkout v4 composite
- ruby/setup-ruby v1 composite
- irb >= 0
- minitest ~> 5.16
- rake ~> 13.0