{"id":29873766,"url":"https://github.com/andrew/purl","last_synced_at":"2026-02-10T04:32:01.447Z","repository":{"id":306206798,"uuid":"1025424738","full_name":"andrew/purl","owner":"andrew","description":"A Ruby library for parsing, validating, and generating Package URLs (PURLs) as defined by the PURL specification","archived":false,"fork":false,"pushed_at":"2026-02-03T11:14:52.000Z","size":309,"stargazers_count":10,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-06T04:38:12.012Z","etag":null,"topics":["dependencies","package-url","purl","ruby","ruby-gem","sbom"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/purl","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrew.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"andrew","ko_fi":"andrewnez","buy_me_a_coffee":"andrewnez"}},"created_at":"2025-07-24T08:28:40.000Z","updated_at":"2026-02-03T11:14:48.000Z","dependencies_parsed_at":"2025-07-24T10:28:20.540Z","dependency_job_id":"d725d3dc-001d-455d-935f-0cc8240eb136","html_url":"https://github.com/andrew/purl","commit_stats":null,"previous_names":["andrew/purl"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/andrew/purl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrew%2Fpurl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrew%2Fpurl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrew%2Fpurl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrew%2Fpurl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrew","download_url":"https://codeload.github.com/andrew/purl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrew%2Fpurl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29210573,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T21:35:21.898Z","status":"ssl_error","status_checked_at":"2026-02-07T21:35:20.106Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependencies","package-url","purl","ruby","ruby-gem","sbom"],"created_at":"2025-07-30T23:20:41.288Z","updated_at":"2026-02-10T04:32:01.419Z","avatar_url":"https://github.com/andrew.png","language":"Ruby","readme":"# Purl - Package URL Parser for Ruby\n\nA Ruby library for parsing, validating, and generating Package URLs (PURLs) as defined by the [PURL specification](https://github.com/package-url/purl-spec).\n\nThis library features comprehensive error handling with namespaced error types, bidirectional registry URL conversion, and JSON-based configuration for cross-language compatibility.\n\n[![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-red.svg)](https://www.ruby-lang.org/)\n[![Gem Version](https://badge.fury.io/rb/purl.svg)](https://rubygems.org/gems/purl)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n**[Available on RubyGems](https://rubygems.org/gems/purl)** | **[API Documentation](https://rdoc.info/github/andrew/purl)**\n\n## Related Libraries\n\n- **[Vers](https://github.com/andrew/vers)** - A Ruby library for working with version ranges that supports the VERS specification\n\n## Features\n\n- **Command-line interface** with parse, validate, convert, download, generate, info, lookup, and advisories commands plus JSON output\n- **Comprehensive PURL parsing and validation** with 37 package types (32 official + 5 additional ecosystems)\n- **Better error handling** with namespaced error classes and contextual information\n- **Bidirectional registry URL conversion** - generate registry URLs from PURLs and parse PURLs from registry URLs\n- **Download URL generation** for 18 package ecosystems (gem, npm, cargo, maven, etc.)\n- **Security advisory lookup** - query security advisories from advisories.ecosyste.ms\n- **Package information lookup** - query package metadata from ecosyste.ms\n- **Type-specific validation** for conan, cran, and swift packages\n- **Registry URL generation** for 20 package ecosystems (npm, gem, maven, pypi, etc.)\n- **Rails-style route patterns** for registry URL templates\n- **100% compliance** with official PURL specification test suite (59/59 tests passing)\n- **Cross-language compatibility** with JSON-based configuration\n- **Comprehensive documentation** and examples\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'purl'\n```\n\nAnd then execute:\n\n```bash\nbundle install\n```\n\nOr install it yourself as:\n\n```bash\ngem install purl\n```\n\n## Command Line Interface\n\nThe purl gem includes a command-line interface that provides convenient access to all parsing, validation, conversion, and generation functionality.\n\n### Installation\n\nThe CLI is automatically available after installing the gem:\n\n```bash\ngem install purl\npurl --help\n```\n\n### Available Commands\n\n```bash\npurl parse \u003cpurl-string\u003e              # Parse and display PURL components\npurl validate \u003cpurl-string\u003e           # Validate a PURL (exit code indicates success)\npurl convert \u003cregistry-url\u003e           # Convert registry URL to PURL\npurl url \u003cpurl-string\u003e                # Convert PURL to registry URL\npurl download \u003cpurl-string\u003e           # Get download URL for package version\npurl generate [options]               # Generate PURL from components\npurl info [type]                      # Show information about PURL types\npurl lookup \u003cpurl-string\u003e             # Look up package information from ecosyste.ms\npurl advisories \u003cpurl-string\u003e         # Look up security advisories from advisories.ecosyste.ms\n```\n\n### JSON Output\n\nAll commands support JSON output with the `--json` flag:\n\n```bash\npurl --json parse \"pkg:gem/rails@7.0.0\"\npurl --json info gem\npurl --json lookup \"pkg:cargo/rand\"\npurl --json advisories \"pkg:npm/lodash@4.17.19\"\n```\n\n### Command Examples\n\n#### Parse a PURL\n```bash\n$ purl parse \"pkg:gem/rails@7.0.0\"\nValid PURL: pkg:gem/rails@7.0.0\nComponents:\n  Type:       gem\n  Namespace:  (none)\n  Name:       rails\n  Version:    7.0.0\n  Qualifiers: (none)\n  Subpath:    (none)\n\n$ purl --json parse \"pkg:npm/@babel/core@7.0.0\"\n{\n  \"success\": true,\n  \"purl\": \"pkg:npm/%40babel/core@7.0.0\",\n  \"components\": {\n    \"type\": \"npm\",\n    \"namespace\": \"@babel\",\n    \"name\": \"core\",\n    \"version\": \"7.0.0\",\n    \"qualifiers\": {},\n    \"subpath\": null\n  }\n}\n```\n\n#### Validate a PURL\n```bash\n$ purl validate \"pkg:gem/rails@7.0.0\"\nValid PURL\n\n$ purl validate \"invalid-purl\"\nInvalid PURL: PURL must start with 'pkg:'\n```\n\n#### Convert Registry URL to PURL\n```bash\n$ purl convert \"https://rubygems.org/gems/rails\"\npkg:gem/rails\n\n$ purl convert \"https://www.npmjs.com/package/@babel/core\"\npkg:npm/@babel/core\n```\n\n#### Convert PURL to Registry URL\n```bash\n$ purl url \"pkg:gem/rails@7.0.0\"\nhttps://rubygems.org/gems/rails\n\n$ purl url \"pkg:npm/@babel/core@7.0.0\"\nhttps://www.npmjs.com/package/@babel/core\n\n$ purl --json url \"pkg:gem/rails@7.0.0\"\n{\n  \"success\": true,\n  \"purl\": \"pkg:gem/rails@7.0.0\",\n  \"registry_url\": \"https://rubygems.org/gems/rails\",\n  \"type\": \"gem\"\n}\n```\n\n#### Get Download URL\n```bash\n$ purl download \"pkg:gem/rails@7.0.0\"\nhttps://rubygems.org/downloads/rails-7.0.0.gem\n\n$ purl download \"pkg:npm/@babel/core@7.20.0\"\nhttps://registry.npmjs.org/@babel/core/-/core-7.20.0.tgz\n\n$ purl download \"pkg:cargo/serde@1.0.152\"\nhttps://static.crates.io/crates/serde/serde-1.0.152.crate\n\n$ purl --json download \"pkg:maven/org.apache.commons/commons-lang3@3.12.0\"\n{\n  \"success\": true,\n  \"purl\": \"pkg:maven/org.apache.commons/commons-lang3@3.12.0\",\n  \"download_url\": \"https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar\",\n  \"type\": \"maven\"\n}\n```\n\n#### Generate a PURL\n```bash\n$ purl generate --type gem --name rails --version 7.0.0\npkg:gem/rails@7.0.0\n\n$ purl generate --type npm --namespace @babel --name core --version 7.0.0 --qualifiers \"arch=x64,os=linux\"\npkg:npm/%40babel/core@7.0.0?arch=x64\u0026os=linux\n```\n\n#### Show Type Information\n```bash\n$ purl info gem\nType: gem\nKnown: Yes\nDescription: RubyGems\nDefault registry: https://rubygems.org\nRegistry URL generation: Yes\nReverse parsing: Yes\nExamples:\n  pkg:gem/ruby-advisory-db-check@0.12.4\n  pkg:gem/rails@7.0.4\n  pkg:gem/bundler@2.3.26\nRegistry URL patterns:\n  https://rubygems.org/gems/:name\n  https://rubygems.org/gems/:name/versions/:version\n\n$ purl info  # Shows all types\nKnown PURL types:\n\n  alpm\n    Description: Arch Linux packages\n    Registry support: No\n    Reverse parsing: No\n  ...\nTotal types: 37\nRegistry supported: 20\n```\n\n#### Look Up Package Information\n```bash\n$ purl lookup \"pkg:cargo/rand\"\nPackage: rand (cargo)\nDescription: Random number generators and other randomness functionality.\nHomepage: https://rust-random.github.io/book\nRepository: https://github.com/rust-random/rand\nLicense: MIT OR Apache-2.0\nDownloads: 145,678,901\nLatest Version: 0.8.5\nPublished: 2023-01-13T17:47:01.870Z\n\n$ purl --json lookup \"pkg:cargo/rand@0.8.5\"\n{\n  \"success\": true,\n  \"purl\": \"pkg:cargo/rand@0.8.5\",\n  \"package\": {\n    \"name\": \"rand\",\n    \"ecosystem\": \"cargo\",\n    \"description\": \"Random number generators and other randomness functionality.\",\n    \"homepage\": \"https://rust-random.github.io/book\",\n    \"repository_url\": \"https://github.com/rust-random/rand\",\n    \"registry_url\": \"https://crates.io/crates/rand\",\n    \"licenses\": \"MIT OR Apache-2.0\",\n    \"latest_version\": \"0.8.5\",\n    \"latest_version_published_at\": \"2023-01-13T17:47:01.870Z\",\n    \"versions_count\": 89,\n    \"maintainers\": [\n      {\n        \"login\": \"dhardy\",\n        \"name\": \"Diggory Hardy\"\n      }\n    ]\n  },\n  \"version\": {\n    \"number\": \"0.8.5\",\n    \"published_at\": \"2023-01-13T17:47:01.870Z\",\n    \"registry_url\": \"https://crates.io/crates/rand/0.8.5\",\n    \"downloads\": 5678901,\n    \"size\": 102400\n  }\n}\n```\n\n#### Look Up Security Advisories\n```bash\n$ purl advisories \"pkg:npm/lodash@4.17.19\"\nSecurity Advisories for pkg:npm/lodash@4.17.19\n================================================================================\n\nAdvisory #1: Regular Expression Denial of Service (ReDoS) in lodash\nIdentifiers: GHSA-x5rq-j2xg-h7qm, CVE-2019-1010266\nSeverity: MODERATE\n\nDescription:\n  lodash prior to 4.7.11 is affected by: CWE-400: Uncontrolled Resource\n  Consumption. The impact is: Denial of service. The component is: Date\n  handler. The attack vector is: Attacker provides very long strings, which\n  the library attempts to match using a regular expression. The fixed version\n  is: 4.7.11.\n\nAffected Packages:\n  Package: npm/lodash\n  Vulnerable: \u003e= 4.7.0, \u003c 4.17.11\n  Patched: 4.17.11\n\nSource: github | Origin: UNSPECIFIED | Published: 2019-07-19T16:13:07.000Z\nAdvisory URL: https://github.com/advisories/GHSA-x5rq-j2xg-h7qm\n\nTotal advisories found: 3\n\n$ purl --json advisories \"pkg:npm/lodash@4.17.19\"\n{\n  \"success\": true,\n  \"purl\": \"pkg:npm/lodash@4.17.19\",\n  \"advisories\": [\n    {\n      \"id\": \"MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLXg1cnEtajJ4Zy1oN3Ft\",\n      \"title\": \"Regular Expression Denial of Service (ReDoS) in lodash\",\n      \"description\": \"lodash prior to 4.7.11 is affected by...\",\n      \"severity\": \"MODERATE\",\n      \"url\": \"https://github.com/advisories/GHSA-x5rq-j2xg-h7qm\",\n      \"published_at\": \"2019-07-19T16:13:07.000Z\",\n      \"affected_packages\": [\n        {\n          \"ecosystem\": \"npm\",\n          \"name\": \"lodash\",\n          \"vulnerable_version_range\": \"\u003e= 4.7.0, \u003c 4.17.11\",\n          \"first_patched_version\": \"4.17.11\"\n        }\n      ],\n      \"identifiers\": [\"GHSA-x5rq-j2xg-h7qm\", \"CVE-2019-1010266\"]\n    }\n  ],\n  \"count\": 3\n}\n```\n\n### Generate Options\n\nThe `generate` command supports all PURL components:\n\n```bash\npurl generate --help\nUsage: purl generate [options]\n    --type TYPE                      Package type (required)\n    --name NAME                      Package name (required)\n    --namespace NAMESPACE            Package namespace\n    --version VERSION                Package version\n    --qualifiers QUALIFIERS          Qualifiers as key=value,key2=value2\n    --subpath SUBPATH                Package subpath\n    -h, --help                       Show this help\n```\n\n### Exit Codes\n\nThe CLI uses standard exit codes:\n- `0` - Success\n- `1` - Error (invalid PURL, unsupported operation, etc.)\n\nThis makes the CLI suitable for use in scripts and CI/CD pipelines:\n\n```bash\nif purl validate \"pkg:gem/rails@7.0.0\"; then\n  echo \"Valid PURL\"\nelse\n  echo \"Invalid PURL\"\n  exit 1\nfi\n```\n\n## Library Usage\n\n### Basic PURL Parsing\n\n```ruby\nrequire 'purl'\n\n# Parse a PURL string\npurl = Purl.parse(\"pkg:gem/rails@7.0.0\")\nputs purl.type        # =\u003e \"gem\"\nputs purl.name        # =\u003e \"rails\"\nputs purl.version     # =\u003e \"7.0.0\"\nputs purl.namespace   # =\u003e nil\n\n# Parse with namespace and qualifiers\npurl = Purl.parse(\"pkg:npm/@babel/core@7.0.0?arch=x86_64\")\nputs purl.type        # =\u003e \"npm\"\nputs purl.namespace   # =\u003e \"@babel\"\nputs purl.name        # =\u003e \"core\"\nputs purl.version     # =\u003e \"7.0.0\"\nputs purl.qualifiers  # =\u003e {\"arch\" =\u003e \"x86_64\"}\n```\n\n### Creating PURLs\n\n```ruby\n# Create a PURL object\npurl = Purl::PackageURL.new(\n  type: \"maven\",\n  namespace: \"org.apache.commons\",\n  name: \"commons-lang3\",\n  version: \"3.12.0\"\n)\n\nputs purl.to_s  # =\u003e \"pkg:maven/org.apache.commons/commons-lang3@3.12.0\"\n```\n\n### Modifying PURL Objects\n\nPURL objects are immutable by design, but you can create new objects with modified attributes using the `with` method:\n\n```ruby\n# Create original PURL\noriginal = Purl::PackageURL.new(\n  type: \"npm\",\n  namespace: \"@babel\", \n  name: \"core\",\n  version: \"7.20.0\",\n  qualifiers: { \"arch\" =\u003e \"x64\" }\n)\n\n# Create new PURL with updated version\nupdated = original.with(version: \"7.21.0\")\nputs updated.to_s  # =\u003e \"pkg:npm/@babel/core@7.21.0?arch=x64\"\n\n# Update qualifiers\nwith_new_qualifiers = original.with(\n  qualifiers: { \"arch\" =\u003e \"arm64\", \"os\" =\u003e \"linux\" }\n)\nputs with_new_qualifiers.to_s  # =\u003e \"pkg:npm/@babel/core@7.20.0?arch=arm64\u0026os=linux\"\n\n# Update multiple attributes at once\nfully_updated = original.with(\n  version: \"8.0.0\",\n  qualifiers: { \"dev\" =\u003e \"true\" },\n  subpath: \"lib/index.js\"\n)\nputs fully_updated.to_s  # =\u003e \"pkg:npm/@babel/core@8.0.0#lib/index.js?dev=true\"\n\n# Original remains unchanged\nputs original.to_s  # =\u003e \"pkg:npm/@babel/core@7.20.0?arch=x64\"\n```\n\n### Registry URL Generation\n\n```ruby\n# Generate registry URLs from PURLs\npurl = Purl.parse(\"pkg:gem/rails@7.0.0\")\nputs purl.registry_url                # =\u003e \"https://rubygems.org/gems/rails\"\nputs purl.registry_url_with_version   # =\u003e \"https://rubygems.org/gems/rails/versions/7.0.0\"\n\n# Check if registry URL generation is supported\nputs purl.supports_registry_url?      # =\u003e true\n\n# NPM with scoped packages\npurl = Purl.parse(\"pkg:npm/@babel/core@7.0.0\")\nputs purl.registry_url                # =\u003e \"https://www.npmjs.com/package/@babel/core\"\n```\n\n### Download URL Generation\n\nGenerate direct download URLs for package artifacts:\n\n```ruby\n# Generate download URLs from PURLs\npurl = Purl.parse(\"pkg:gem/rails@7.0.0\")\nputs purl.download_url  # =\u003e \"https://rubygems.org/downloads/rails-7.0.0.gem\"\n\n# Check if download URL generation is supported\nputs purl.supports_download_url?  # =\u003e true\n\n# NPM with scoped packages\npurl = Purl.parse(\"pkg:npm/@babel/core@7.20.0\")\nputs purl.download_url  # =\u003e \"https://registry.npmjs.org/@babel/core/-/core-7.20.0.tgz\"\n\n# Maven packages\npurl = Purl.parse(\"pkg:maven/org.apache.commons/commons-lang3@3.12.0\")\nputs purl.download_url  # =\u003e \"https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar\"\n\n# Custom registry via repository_url qualifier\npurl = Purl.parse(\"pkg:npm/lodash@4.17.21?repository_url=https://npm.mycompany.com\")\nputs purl.download_url  # =\u003e \"https://npm.mycompany.com/lodash/-/lodash-4.17.21.tgz\"\n\n# Custom registry via parameter\npurl = Purl.parse(\"pkg:gem/rails@7.0.0\")\nputs purl.download_url(base_url: \"https://gems.internal.com/downloads\")\n# =\u003e \"https://gems.internal.com/downloads/rails-7.0.0.gem\"\n\n# Get list of supported types\nputs Purl.download_supported_types\n# =\u003e [\"bioconductor\", \"bitbucket\", \"cargo\", \"clojars\", \"cran\", \"elm\", \"gem\",\n#     \"github\", \"gitlab\", \"golang\", \"hackage\", \"hex\", \"luarocks\", \"maven\",\n#     \"npm\", \"nuget\", \"pub\", \"swift\"]\n```\n\n### ecosyste.ms API URLs\n\nGenerate API URLs for the packages.ecosyste.ms service:\n\n```ruby\n# Get the ecosyste.ms registry name for a package type\npurl = Purl.parse(\"pkg:gem/rake@13.3.1\")\npurl.ecosystems_registry  # =\u003e \"rubygems.org\"\n\n# Generate API URLs\npurl.ecosystems_api_url          # =\u003e \"https://packages.ecosyste.ms/api/v1/registries/rubygems.org/packages/rake/versions/13.3.1\"\npurl.ecosystems_package_api_url  # =\u003e \"https://packages.ecosyste.ms/api/v1/registries/rubygems.org/packages/rake\"\npurl.ecosystems_version_api_url  # =\u003e \"https://packages.ecosyste.ms/api/v1/registries/rubygems.org/packages/rake/versions/13.3.1\"\n\n# Works with namespaced packages\npurl = Purl.parse(\"pkg:npm/@babel/core@7.20.0\")\npurl.ecosystems_registry  # =\u003e \"npmjs.org\"\npurl.ecosystems_api_url   # =\u003e \"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/%40babel%2Fcore/versions/7.20.0\"\n\n# Without version, returns package URL\npurl = Purl.parse(\"pkg:cargo/serde\")\npurl.ecosystems_api_url  # =\u003e \"https://packages.ecosyste.ms/api/v1/registries/crates.io/packages/serde\"\n```\n\n### Reverse Parsing: Registry URLs to PURLs\n\n```ruby\n# Parse registry URLs back to PURLs\npurl = Purl.from_registry_url(\"https://rubygems.org/gems/rails/versions/7.0.0\")\nputs purl.to_s  # =\u003e \"pkg:gem/rails@7.0.0\"\n\n# Works with various registries\npurl = Purl.from_registry_url(\"https://www.npmjs.com/package/@babel/core\")\nputs purl.to_s  # =\u003e \"pkg:npm/@babel/core\"\n\npurl = Purl.from_registry_url(\"https://pypi.org/project/django/4.0.0/\")\nputs purl.to_s  # =\u003e \"pkg:pypi/django@4.0.0\"\n```\n\n### Custom Registry Domains\n\nYou can parse registry URLs from custom domains or generate URLs for private registries:\n\n```ruby\n# Parse from custom domain (specify type to help with parsing)\npurl = Purl.from_registry_url(\"https://npm.company.com/package/@babel/core\", type: \"npm\")\nputs purl.to_s  # =\u003e \"pkg:npm/@babel/core\"\n\n# Generate URLs for custom registries\npurl = Purl.parse(\"pkg:gem/rails@7.0.0\")\ncustom_url = purl.registry_url(base_url: \"https://gems.internal.com/gems\")\nputs custom_url  # =\u003e \"https://gems.internal.com/gems/rails\"\n\n# With version-specific URLs\nwith_version = purl.registry_url_with_version(base_url: \"https://gems.internal.com/gems\")\nputs with_version  # =\u003e \"https://gems.internal.com/gems/rails/versions/7.0.0\"\n\n# Works with all supported package types\ncomposer_purl = Purl.parse(\"pkg:composer/symfony/console@5.4.0\")\nprivate_composer = composer_purl.registry_url(base_url: \"https://packagist.company.com/packages\")\nputs private_composer  # =\u003e \"https://packagist.company.com/packages/symfony/console\"\n```\n\n### Route Patterns\n\n```ruby\n# Get route patterns for a package type (Rails-style)\npatterns = Purl::RegistryURL.route_patterns_for(\"gem\")\n# =\u003e [\"https://rubygems.org/gems/:name\", \"https://rubygems.org/gems/:name/versions/:version\"]\n\n# Get all route patterns\nall_patterns = Purl::RegistryURL.all_route_patterns\nputs all_patterns[\"npm\"]\n# =\u003e [\"https://www.npmjs.com/package/:namespace/:name\", \"https://www.npmjs.com/package/:name\", ...]\n```\n\n### Working with Qualifiers\n\nQualifiers are key-value pairs that provide additional metadata about packages:\n\n```ruby\n# Create PURL with qualifiers\npurl = Purl::PackageURL.new(\n  type: \"apk\",\n  name: \"curl\",\n  version: \"7.83.0-r0\",\n  qualifiers: {\n    \"distro\" =\u003e \"alpine-3.16\",\n    \"arch\" =\u003e \"x86_64\",\n    \"repository_url\" =\u003e \"https://dl-cdn.alpinelinux.org\"\n  }\n)\nputs purl.to_s  # =\u003e \"pkg:apk/curl@7.83.0-r0?arch=x86_64\u0026distro=alpine-3.16\u0026repository_url=https://dl-cdn.alpinelinux.org\"\n\n# Access qualifiers\nputs purl.qualifiers[\"distro\"]  # =\u003e \"alpine-3.16\"\nputs purl.qualifiers[\"arch\"]    # =\u003e \"x86_64\"\n\n# Parse PURL with qualifiers\nparsed = Purl.parse(\"pkg:rpm/httpd@2.4.53?distro=fedora-36\u0026arch=x86_64\")\nputs parsed.qualifiers  # =\u003e {\"distro\" =\u003e \"fedora-36\", \"arch\" =\u003e \"x86_64\"}\n\n# Add qualifiers to existing PURL\nwith_qualifiers = purl.with(qualifiers: purl.qualifiers.merge(\"signed\" =\u003e \"true\"))\n```\n\n### Package Type Information\n\n```ruby\n# Get all known PURL types\nputs Purl.known_types.length          # =\u003e 37\nputs Purl.known_types.include?(\"gem\") # =\u003e true\n\n# Check type support\nputs Purl.known_type?(\"gem\")                    # =\u003e true\nputs Purl.registry_supported_types              # =\u003e [\"cargo\", \"gem\", \"maven\", \"npm\", ...]\nputs Purl.reverse_parsing_supported_types       # =\u003e [\"bioconductor\", \"cargo\", \"clojars\", ...]\nputs Purl.download_supported_types              # =\u003e [\"cargo\", \"gem\", \"maven\", \"npm\", ...]\n\n# Get default registry for a type\nputs Purl.default_registry(\"gem\")               # =\u003e \"https://rubygems.org\"\nputs Purl.default_registry(\"npm\")               # =\u003e \"https://registry.npmjs.org\"\nputs Purl.default_registry(\"golang\")             # =\u003e nil (no default)\n\n# Get official examples for a type\nputs Purl.type_examples(\"gem\")                  # =\u003e [\"pkg:gem/rails@7.0.4\", \"pkg:gem/bundler@2.3.26\", ...]\nputs Purl.type_examples(\"npm\")                  # =\u003e [\"pkg:npm/lodash@4.17.21\", \"pkg:npm/@babel/core@7.20.0\", ...]\nputs Purl.type_examples(\"unknown\")              # =\u003e []\n\n# Get detailed type information\ninfo = Purl.type_info(\"gem\")\nputs info[:known]                     # =\u003e true\nputs info[:description]               # =\u003e \"RubyGems\"\nputs info[:default_registry]          # =\u003e \"https://rubygems.org\"\nputs info[:examples]                  # =\u003e [\"pkg:gem/rails@7.0.4\", ...]\nputs info[:registry_url_generation]   # =\u003e true\nputs info[:reverse_parsing]           # =\u003e true\nputs info[:download_url_generation]   # =\u003e true\nputs info[:route_patterns]            # =\u003e [\"https://rubygems.org/gems/:name\", ...]\n```\n\n### Security Advisory Lookup\n\nLook up security advisories for packages using the advisories.ecosyste.ms API:\n\n```ruby\n# Look up advisories for a package\npurl = Purl.parse(\"pkg:npm/lodash@4.17.19\")\nadvisories = purl.advisories\n\n# Display advisory information\nadvisories.each do |advisory|\n  puts \"Title: #{advisory[:title]}\"\n  puts \"Severity: #{advisory[:severity]}\"\n  puts \"Description: #{advisory[:description]}\"\n  puts \"URL: #{advisory[:url]}\"\n\n  # Show affected packages\n  advisory[:affected_packages].each do |pkg|\n    puts \"  Package: #{pkg[:ecosystem]}/#{pkg[:name]}\"\n    puts \"  Vulnerable: #{pkg[:vulnerable_version_range]}\"\n    puts \"  Patched: #{pkg[:first_patched_version]}\" if pkg[:first_patched_version]\n  end\n\n  # Show identifiers (CVE, GHSA, etc.)\n  puts \"Identifiers: #{advisory[:identifiers].join(', ')}\"\n  puts\nend\n\n# Look up advisories for any version of a package\npurl = Purl.parse(\"pkg:npm/lodash\")\nall_advisories = purl.advisories\n\n# Use custom user agent and timeout\nadvisories = purl.advisories(user_agent: \"my-app/1.0\", timeout: 5)\n```\n\n### Error Handling\n\n```ruby\n# Detailed error types with context\nbegin\n  Purl.parse(\"invalid-purl\")\nrescue Purl::InvalidSchemeError =\u003e e\n  puts \"Scheme error: #{e.message}\"\nrescue Purl::ParseError =\u003e e\n  puts \"Parse error: #{e.message}\"\n  puts \"Component: #{e.component}\"\n  puts \"Value: #{e.value}\"\nend\n\n# Type-specific validation errors\nbegin\n  Purl::PackageURL.new(type: \"swift\", name: \"Alamofire\")  # Swift requires namespace\nrescue Purl::ValidationError =\u003e e\n  puts e.message  # =\u003e \"Swift PURLs require a namespace to be unambiguous\"\nend\n```\n\n### Supported Package Types\n\nThe library supports 37 package types (32 official + 5 additional ecosystems):\n\n**Registry URL Generation (20 types):**\n- `bioconductor` (R/Bioconductor) - bioconductor.org\n- `cargo` (Rust) - crates.io\n- `clojars` (Clojure) - clojars.org\n- `cocoapods` (iOS) - cocoapods.org\n- `composer` (PHP) - packagist.org\n- `conda` (Python) - anaconda.org\n- `cpan` (Perl) - metacpan.org\n- `deno` (Deno) - deno.land/x\n- `elm` (Elm) - package.elm-lang.org\n- `gem` (Ruby) - rubygems.org  \n- `golang` (Go) - pkg.go.dev\n- `hackage` (Haskell) - hackage.haskell.org\n- `hex` (Elixir) - hex.pm\n- `homebrew` (macOS) - formulae.brew.sh\n- `maven` (Java) - mvnrepository.com\n- `npm` (Node.js) - npmjs.com\n- `nuget` (.NET) - nuget.org\n- `pub` (Dart) - pub.dev\n- `pypi` (Python) - pypi.org\n- `swift` (Swift) - swiftpackageindex.com\n\n**Reverse Parsing (20 types):**\n- `bioconductor`, `cargo`, `clojars`, `cocoapods`, `composer`, `conda`, `cpan`, `deno`, `elm`, `gem`, `golang`, `hackage`, `hex`, `homebrew`, `maven`, `npm`, `nuget`, `pub`, `pypi`, `swift`\n\n**Download URL Generation (18 types):**\n- `bioconductor` - bioconductor.org/packages/release/bioc/src/contrib\n- `bitbucket` - bitbucket.org archive downloads\n- `cargo` - static.crates.io/crates\n- `clojars` - repo.clojars.org (Maven-style)\n- `cran` - cran.r-project.org/src/contrib\n- `elm` - GitHub archive downloads\n- `gem` - rubygems.org/downloads\n- `github` - GitHub archive downloads\n- `gitlab` - GitLab archive downloads\n- `golang` - proxy.golang.org\n- `hackage` - hackage.haskell.org/package\n- `hex` - repo.hex.pm/tarballs\n- `luarocks` - luarocks.org/manifests\n- `maven` - repo.maven.apache.org/maven2\n- `npm` - registry.npmjs.org\n- `nuget` - api.nuget.org/v3-flatcontainer\n- `pub` - pub.dev/packages\n- `swift` - GitHub/GitLab/Bitbucket (derived from namespace)\n\n**All 37 Supported Types:**\n`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`\n\n## Specification Compliance\n\n- **100% compliance** with the official PURL specification test suite (59/59 tests passing)\n- **All 32 official package types** plus 5 additional ecosystem types supported\n- **Type-specific validation** for conan, cran, swift, cpan, and mlflow packages\n- **Proper error handling** for invalid PURLs that should be rejected\n\n## JSON Configuration\n\nPackage types and registry patterns are stored in `purl-types.json` for easy contribution and cross-language compatibility:\n\n```json\n{\n  \"version\": \"1.0.0\",\n  \"description\": \"PURL types and registry URL patterns for package ecosystems\",\n  \"source\": \"https://github.com/package-url/purl-spec/blob/main/PURL-TYPES.rst\",\n  \"last_updated\": \"2025-07-24\",\n  \"types\": {\n    \"gem\": {\n      \"description\": \"RubyGems\",\n      \"default_registry\": \"https://rubygems.org\",\n      \"registry_config\": {\n        \"path_template\": \"/gems/:name\",\n        \"version_path_template\": \"/gems/:name/versions/:version\",\n        \"reverse_regex\": \"/gems/([^/?#]+)(?:/versions/([^/?#]+))?\",\n        \"components\": {\n          \"namespace\": false,\n          \"version_in_url\": true,\n          \"version_path\": \"/versions/\"\n        }\n      }\n    }\n  }\n}\n```\n\n**Key Configuration Improvements:**\n- **Domain-agnostic patterns**: `path_template` without hardcoded domains enables custom registries\n- **Flexible URL generation**: Combine `default_registry` + `path_template` for any domain\n- **Cleaner JSON**: Reduced duplication and easier maintenance\n- **Cross-registry compatibility**: Same URL structure works with public and private registries\n\n## JSON Schema Validation\n\nThe library includes JSON schemas for validation and documentation:\n\n- **`schemas/purl-types.schema.json`** - Schema for the PURL types configuration file\n- **`schemas/test-suite-data.schema.json`** - Schema for the official test suite data\n\nThese schemas provide:\n- **Structure validation** - Ensure JSON files conform to expected format\n- **Documentation** - Self-documenting configuration with descriptions\n- **IDE support** - Enable autocomplete and validation in editors\n- **CI/CD integration** - Validate configuration in automated pipelines\n\nValidate JSON files against their schemas:\n```bash\nrake spec:validate_schemas\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then:\n\n```bash\n# Run tests\nrake test\n\n# Run specification compliance tests\nrake spec:compliance\n\n# Update test cases from official PURL spec\nrake spec:update\n\n# Show type information\nrake spec:types\n\n# Verify types against official specification\nrake spec:verify_types\n```\n\n### Testing Against Official Specification\n\nThis library includes the official [purl-spec](https://github.com/package-url/purl-spec) repository as a git submodule for testing and validation:\n\n```bash\n# Initialize submodule (first time only)\ngit submodule update --init --recursive\n\n# Update submodule to latest spec\ngit submodule update --remote purl-spec\n```\n\nThe tests use files from the submodule to:\n- **Schema validation**: Validate our `purl-types.json` against the official schema in `purl-spec/schemas/`\n- **Type compliance**: Ensure our supported types match the official types in `purl-spec/types/`\n- **Test data**: Access official test cases and examples from `purl-spec/tests/`\n\nThe 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.\n\n### Rake Tasks\n\n- `rake spec:update` - Fetch latest test cases from official PURL spec repository\n- `rake spec:compliance` - Run compliance tests against official test suite  \n- `rake spec:types` - Show information about all PURL types and their support\n- `rake spec:verify_types` - Verify our types list against official specification\n- `rake spec:validate_schemas` - Validate JSON files against their schemas\n- `rake spec:debug` - Show detailed info about failing test cases\n\n## Funding\n\nIf you find this project useful, please consider supporting its development:\n\n- [GitHub Sponsors](https://github.com/sponsors/andrew)\n- [Ko-fi](https://ko-fi.com/andrewnez)\n- [Buy Me a Coffee](https://www.buymeacoffee.com/andrewnez)\n\nYour support helps maintain and improve this library for the entire Ruby community.\n\n## Contributing\n\nBug 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](http://contributor-covenant.org) code of conduct.\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Make your changes\n4. Add tests for your changes\n5. Ensure all tests pass (`rake test`)\n6. Commit your changes (`git commit -am 'Add some feature'`)\n7. Push to the branch (`git push origin my-new-feature`)\n8. Create new Pull Request\n\n### Adding New Package Types\n\nTo add support for a new package type:\n\n1. Update `purl-types.json` with the new type configuration\n2. Add registry URL patterns if applicable\n3. Add type-specific validation rules if needed in `lib/purl/package_url.rb`\n4. Add tests for the new functionality\n\n## License\n\nThis gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes and releases.\n\n## Code of Conduct\n\nEveryone interacting in the Purl project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).","funding_links":["https://github.com/sponsors/andrew","https://ko-fi.com/andrewnez","https://buymeacoffee.com/andrewnez","https://www.buymeacoffee.com/andrewnez"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrew%2Fpurl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrew%2Fpurl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrew%2Fpurl/lists"}