purl

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 10 Imported by: 15

README

purl

Go library for working with Package URLs (PURLs). Wraps packageurl-go with additional helpers for registry URL generation, type configuration, and version cleaning.

Installation

go get github.com/git-pkgs/purl

Usage

import "github.com/git-pkgs/purl"

// Parse a PURL
p, _ := purl.Parse("pkg:npm/%40babel/core@7.24.0")
fmt.Println(p.FullName())  // @babel/core
fmt.Println(p.Type)        // npm
fmt.Println(p.Version)     // 7.24.0

// Create a PURL
p := purl.New("npm", "@babel", "core", "7.24.0", nil)
fmt.Println(p.String())  // pkg:npm/%40babel/core@7.24.0

// Generate registry URL
url, _ := p.RegistryURL()
fmt.Println(url)  // https://www.npmjs.com/package/@babel/core

// Parse registry URL back to PURL
p, _ := purl.ParseRegistryURL("https://crates.io/crates/serde")
fmt.Println(p.String())  // pkg:cargo/serde

// Check if using private registry
p, _ := purl.Parse("pkg:npm/lodash?repository_url=https://npm.example.com")
fmt.Println(p.IsPrivateRegistry())  // true

// Get type configuration
cfg := purl.TypeInfo("npm")
fmt.Println(*cfg.DefaultRegistry)  // https://registry.npmjs.org

// Clean version constraints
version := purl.CleanVersion("^1.2.3", "npm")
fmt.Println(version)  // 1.2.3

// Compare two spellings of the same package
a, _ := purl.Parse("pkg:pypi/zope.interface@6.0")
b, _ := purl.Parse("pkg:pypi/Zope_Interface@6.0")
fmt.Println(a.ComparisonForm().String() == b.ComparisonForm().String())  // true

ComparisonForm applies ecosystem rules that Parse does not, so String() output stays stable for callers that store it:

Type Rule
pypi name lowercased, runs of -, _, . become - (PEP 503)
nuget name and version lowercased
composer leading v/V before a digit dropped from the version
conda channel given as namespace or channel/name moved to the channel qualifier
oci namespace folded into the name (library/alpine)
swift registry identities (scope without . or /) lowercased

Type Configuration

Type information is embedded in types.json. Type names and namespace requirements are checked against the pinned purl-spec submodule. Default registries and URL templates are maintained locally.

The local types bioconductor, clojars, deno, and elm are retained. bsd follows the FreeBSD type proposed in purl-spec#496; the freebsd ecosystem maps to pkg:bsd/freebsd/.... The legacy homebrew type keeps its existing PURLs and registry behavior; use brew explicitly for the registered type. Recognition of a type does not imply full support for its normalization or validation rules.

purl.KnownTypes()           // []string of all known PURL types
purl.IsKnownType("npm")     // true
purl.DefaultRegistry("npm") // https://registry.npmjs.org

Development

Initialize the spec submodule before running tests:

git submodule update --init
go test ./...

Dependabot checks for spec updates weekly. The metadata test reports missing types, namespace differences, and unregistered types without a local allowance. Update types.json when those checks fail, preserving local registry settings.

License

MIT.

Documentation

Overview

Package purl provides utilities for working with Package URLs (PURLs).

It wraps github.com/package-url/packageurl-go with additional helpers for registry URL generation, type configuration, and version cleaning.

Parsing and Creating PURLs

// Parse a PURL string
p, err := purl.Parse("pkg:npm/%40babel/core@7.24.0")
fmt.Println(p.Type)      // npm
fmt.Println(p.Namespace) // @babel
fmt.Println(p.Name)      // core
fmt.Println(p.Version)   // 7.24.0
fmt.Println(p.FullName()) // @babel/core

// Create a PURL from components
p := purl.New("npm", "@babel", "core", "7.24.0", nil)
fmt.Println(p.String()) // pkg:npm/%40babel/core@7.24.0

Registry URLs

// Generate a registry URL from a PURL
p, _ := purl.Parse("pkg:npm/lodash@4.17.21")
url, _ := p.RegistryURLWithVersion()
fmt.Println(url) // https://www.npmjs.com/package/lodash/v/4.17.21

// Parse a registry URL back to a PURL
p, _ := purl.ParseRegistryURL("https://crates.io/crates/serde")
fmt.Println(p.String()) // pkg:cargo/serde

Type Configuration

Type information comes from an embedded purl-types.json file.

purl.KnownTypes()           // []string of all known PURL types
purl.IsKnownType("npm")     // true
purl.DefaultRegistry("npm") // https://registry.npmjs.org

cfg := purl.TypeInfo("maven")
fmt.Println(cfg.NamespaceRequired()) // true

Private Registries

p, _ := purl.Parse("pkg:npm/lodash?repository_url=https://npm.example.com")
fmt.Println(p.IsPrivateRegistry()) // true
fmt.Println(p.RepositoryURL())     // https://npm.example.com

Package purl provides utilities for working with Package URLs (PURLs).

It wraps github.com/package-url/packageurl-go with additional helpers for ecosystem-specific name formatting, registry URL generation, and PURL construction from ecosystem-native package identifiers.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoMatch = errors.New("URL does not match any known registry pattern")

ErrNoMatch is returned when a URL doesn't match the reverse regex.

View Source
var ErrNoRegistryConfig = errors.New("no registry configuration for this type")

ErrNoRegistryConfig is returned when a PURL type has no registry configuration.

Functions

func BuildPURLString added in v0.1.8

func BuildPURLString(ecosystem, name, version, registryURL string) string

BuildPURLString builds a PURL string from ecosystem-native identifiers. It returns an empty string when the identifier cannot be represented as a PURL.

func CleanVersion

func CleanVersion(version, scheme string) string

CleanVersion returns plain versions unchanged. For version constraints, it uses the vers library to extract the lowest included version. If no included minimum can be identified, it returns the original string.

func DefaultRegistry

func DefaultRegistry(purlType string) string

DefaultRegistry returns the default registry URL for a PURL type. Returns empty string if the type has no default registry.

func EcosystemToOSV

func EcosystemToOSV(ecosystem string) string

EcosystemToOSV converts an ecosystem name to the OSV ecosystem name. OSV uses specific capitalization and naming conventions.

func EcosystemToPURLType

func EcosystemToPURLType(ecosystem string) string

EcosystemToPURLType converts an ecosystem name to the corresponding PURL type. Returns the input unchanged if no mapping exists.

func IsDefaultRegistry

func IsDefaultRegistry(purlType, registryURL string) bool

IsDefaultRegistry returns true if the registryURL matches the default registry for the type.

func IsKnownType

func IsKnownType(purlType string) bool

IsKnownType returns true if the PURL type is defined in types.json.

func IsNonDefaultRegistry

func IsNonDefaultRegistry(purlType, registryURL string) bool

IsNonDefaultRegistry returns true if the registryURL is not the default registry for the type.

func IsValidEcosystem

func IsValidEcosystem(ecosystem string) bool

IsValidEcosystem returns true if the ecosystem is recognized.

func KnownTypes

func KnownTypes() []string

KnownTypes returns a sorted list of all known PURL types.

func MakePURLString

func MakePURLString(ecosystem, name, version string) string

MakePURLString is like MakePURL but returns the PURL as a string. It returns an empty string when the package identifier cannot be represented as a PURL.

func NormalizeEcosystem

func NormalizeEcosystem(ecosystem string) string

NormalizeEcosystem returns the canonical ecosystem name. Handles aliases like "go" -> "golang", "gem" -> "rubygems".

func PURLTypeToDepsdev added in v0.1.5

func PURLTypeToDepsdev(purlType string) string

PURLTypeToDepsdev converts a PURL type to the deps.dev system name. Returns empty string if the type is not supported by deps.dev.

func PURLTypeToEcosystem

func PURLTypeToEcosystem(purlType string) string

PURLTypeToEcosystem converts a PURL type back to an ecosystem name. This is the inverse of EcosystemToPURLType.

func PURLTypeToOSV added in v0.1.14

func PURLTypeToOSV(purlType string) (string, bool)

PURLTypeToOSV converts a PURL type to the OSV ecosystem name and reports whether the type is one OSV recognises. Unlike EcosystemToOSV, it takes the PURL type directly (skipping the ecosystem-name normalisation when the caller already has a parsed PURL) and returns ok=false on a miss rather than passing the input through, so callers that emit OSV records can fall back to a GIT range instead of writing an ecosystem the OSV schema will reject.

func SupportedEcosystems

func SupportedEcosystems() []string

SupportedEcosystems returns a list of all supported ecosystem names. This includes both PURL types and common aliases.

func TypesVersion

func TypesVersion() string

TypesVersion returns the version of the types.json data.

Types

type PURL

type PURL struct {
	packageurl.PackageURL
}

PURL wraps packageurl.PackageURL with additional helpers.

func MakePURL

func MakePURL(ecosystem, name, version string) *PURL

MakePURL constructs a PURL from ecosystem-native package identifiers.

It handles namespace extraction for ecosystems:

  • npm: @scope/pkg -> namespace="@scope", name="pkg"
  • maven: group:artifact -> namespace="group", name="artifact"
  • golang: github.com/foo/bar -> namespace="github.com/foo", name="bar"
  • git: codeberg.org/forgejo/forgejo -> namespace="codeberg.org", name="forgejo/forgejo"
  • composer: vendor/package -> namespace="vendor", name="package"
  • alpine: pkg -> namespace="alpine", name="pkg"
  • arch: pkg -> namespace="arch", name="pkg"
  • freebsd: pkg -> namespace="freebsd", name="pkg"
  • swift: host/owner/package -> namespace="host/owner", name="package" or scope.package -> namespace="scope", name="package"

func New

func New(purlType, namespace, name, version string, qualifiers map[string]string) *PURL

New creates a new PURL from components.

The result is normalized with the same per-type rules Parse applies (packageurl-go's Normalize), so New and Parse produce the same string for equivalent inputs. Normalization errors are ignored; any adjustments Normalize applied before erroring are kept.

func Parse

func Parse(s string) (*PURL, error)

Parse parses a Package URL string into a PURL.

func ParseRegistryURL

func ParseRegistryURL(url string) (*PURL, error)

ParseRegistryURL attempts to parse a registry URL into a PURL. It tries all known types to find a match.

func ParseRegistryURLWithType

func ParseRegistryURLWithType(url, purlType string) (*PURL, error)

ParseRegistryURLWithType parses a registry URL using a specific PURL type.

func (*PURL) ComparisonForm added in v0.1.24

func (p *PURL) ComparisonForm() *PURL

ComparisonForm returns a copy of the PURL in a form where two spellings of the same package compare equal, following each ecosystem's own rules:

  • pypi: name lowercased with runs of "-", "_" and "." folded to "-" (PEP 503)
  • nuget: name and version lowercased
  • composer: a leading "v" or "V" before a digit dropped from the version
  • conda: a channel given as namespace or as a "channel/name" name moved into the channel qualifier
  • oci: a namespace folded into the name, so library/alpine is one name
  • swift: registry identities (a scope without "." or "/") lowercased

Parse and String are left as they are, since callers derive storage keys from them; use this form only to decide whether two PURLs name the same package or version.

func (*PURL) FullName

func (p *PURL) FullName() string

FullName returns the package name combining namespace and name. If there's no namespace, returns just the name. Maven uses ":" and Swift registry identities use "." as the separator. Swift source coordinates and other types use "/".

func (*PURL) IsPrivateRegistry

func (p *PURL) IsPrivateRegistry() bool

IsPrivateRegistry returns true if the PURL has a non-default repository_url.

func (*PURL) Qualifier

func (p *PURL) Qualifier(key string) string

Qualifier returns the value of a qualifier, or empty string if not present.

func (*PURL) RegistryURL

func (p *PURL) RegistryURL() (string, error)

RegistryURL returns the human-readable registry URL for the package. For example, pkg:npm/lodash returns "https://www.npmjs.com/package/lodash".

func (*PURL) RegistryURLWithVersion

func (p *PURL) RegistryURLWithVersion() (string, error)

RegistryURLWithVersion returns the registry URL including version. Falls back to RegistryURL if version URLs aren't supported.

func (*PURL) RepositoryURL

func (p *PURL) RepositoryURL() string

RepositoryURL returns the repository_url qualifier value, if present.

func (*PURL) String

func (p *PURL) String() string

String returns the PURL as a string.

func (*PURL) ToString added in v0.1.22

func (p *PURL) ToString() string

ToString returns the PURL as a string.

func (*PURL) WithQualifier

func (p *PURL) WithQualifier(key, value string) *PURL

WithQualifier returns a copy of the PURL with the qualifier set. If the qualifier already exists, it is replaced.

func (*PURL) WithVersion

func (p *PURL) WithVersion(version string) *PURL

WithVersion returns a copy of the PURL with a different version.

func (*PURL) WithoutVersion

func (p *PURL) WithoutVersion() *PURL

WithoutVersion returns a copy of the PURL without a version.

type RegistryComponents

type RegistryComponents struct {
	Namespace         bool   `json:"namespace"`
	NamespaceRequired bool   `json:"namespace_required"`
	NamespacePrefix   string `json:"namespace_prefix"`
	VersionInURL      bool   `json:"version_in_url"`
	VersionPath       string `json:"version_path"`
	VersionPrefix     string `json:"version_prefix"`
	VersionSeparator  string `json:"version_separator"`
	DefaultVersion    string `json:"default_version"`
	TrailingSlash     bool   `json:"trailing_slash"`
	SpecialHandling   string `json:"special_handling"`
}

RegistryComponents describes which PURL components are used in registry URLs.

type RegistryConfig

type RegistryConfig struct {
	BaseURL                    string             `json:"base_url"`
	ReverseRegex               string             `json:"reverse_regex"`
	URITemplate                string             `json:"uri_template"`
	URITemplateNoNamespace     string             `json:"uri_template_no_namespace"`
	URITemplateWithVersion     string             `json:"uri_template_with_version"`
	URITemplateWithVersionNoNS string             `json:"uri_template_with_version_no_namespace"`
	Components                 RegistryComponents `json:"components"`
}

RegistryConfig contains URL templates and patterns for registry URLs.

type TypeConfig

type TypeConfig struct {
	Description          string          `json:"description"`
	DefaultRegistry      *string         `json:"default_registry"`
	NamespaceRequirement string          `json:"namespace_requirement"`
	Examples             []string        `json:"examples"`
	RegistryConfig       *RegistryConfig `json:"registry_config"`
}

TypeConfig contains configuration for a PURL type.

func TypeInfo

func TypeInfo(purlType string) *TypeConfig

TypeInfo returns configuration for a PURL type, or nil if unknown.

func (*TypeConfig) NamespaceProhibited

func (t *TypeConfig) NamespaceProhibited() bool

NamespaceProhibited returns true if the type prohibits namespaces.

func (*TypeConfig) NamespaceRequired

func (t *TypeConfig) NamespaceRequired() bool

NamespaceRequired returns true if the type requires a namespace.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL