Module: Purl

Defined in:
lib/purl.rb,
lib/purl.rb,
lib/purl/errors.rb,
lib/purl/lookup.rb,
lib/purl/version.rb,
lib/purl/advisory.rb,
lib/purl/package_url.rb,
lib/purl/download_url.rb,
lib/purl/registry_url.rb,
lib/purl/ecosystems_url.rb,
lib/purl/lookup_formatter.rb,
lib/purl/advisory_formatter.rb,
sig/purl.rbs

Overview

The main PURL (Package URL) module providing functionality to parse, validate, and generate package URLs according to the PURL specification.

A Package URL is a mostly universal standard to reference a software package in a uniform way across many tools, programming languages and ecosystems.

Examples:

Basic usage

purl = Purl.parse("pkg:gem/[email protected]")
puts purl.type     # "gem"
puts purl.name     # "rails"
puts purl.version  # "7.0.0"

Registry URL conversion

purl = Purl.from_registry_url("https://rubygems.org/gems/rails")
puts purl.to_s     # "pkg:gem/rails"

Package information lookup

purl = Purl.parse("pkg:cargo/[email protected]")
info = purl.lookup
puts info[:package][:description]
puts info[:version][:published_at] if info[:version]

See Also:

Defined Under Namespace

Classes: Advisory, AdvisoryError, AdvisoryFormatter, DownloadURL, EcosystemsURL, Error, InvalidNameError, InvalidNamespaceError, InvalidQualifierError, InvalidSchemeError, InvalidSubpathError, InvalidTypeError, InvalidVersionError, Lookup, LookupError, LookupFormatter, MalformedUrlError, MissingRegistryInfoError, MissingVersionError, PackageURL, ParseError, RegistryError, RegistryURL, UnsupportedTypeError, ValidationError

Constant Summary collapse

KNOWN_TYPES =

Known PURL types loaded from JSON configuration

load_types_config["types"].keys.sort.freeze
KNOWN_TYPES_SET =
Set.new(KNOWN_TYPES).freeze
InvalidPackageURL =
Deprecated.

Use ParseError instead

Legacy compatibility - matches packageurl-ruby's exception name

ParseError
VERSION =

Returns:

  • (String)
"1.8.1"

Class Method Summary collapse

Class Method Details

.all_type_infoHash<String, Hash>

Get comprehensive information about all types

Examples:

all_info = Purl.all_type_info
gem_info = all_info["gem"]

Returns:

  • (Hash<String, Hash>)

    hash mapping type names to their information

See Also:

  • for structure of individual type information


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/purl.rb', line 193

def self.all_type_info
  result = {}
  
  # Start with known types
  KNOWN_TYPES.each do |type|
    result[type] = type_info(type)
  end
  
  # Add any registry-supported types not in known list
  RegistryURL.supported_types.each do |type|
    unless result.key?(type)
      result[type] = type_info(type)
    end
  end
  
  result
end

.deep_freeze(obj) ⇒ Object

Deep-freeze a parsed JSON structure so callers don't need defensive dups



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/purl.rb', line 34

def self.deep_freeze(obj)
  case obj
  when Hash
    obj.each_value { |v| deep_freeze(v) }
    obj.freeze
  when Array
    obj.each { |v| deep_freeze(v) }
    obj.freeze
  when String
    obj.freeze
  end
  obj
end

.default_registry(type) ⇒ String?

Get default registry URL for a type

Examples:

registry = Purl.default_registry("gem")
puts registry  # "https://rubygems.org"

Parameters:

  • type (String, Symbol)

    the type to get default registry for

Returns:

  • (String, nil)

    default registry URL or nil if not available



271
272
273
274
275
276
# File 'lib/purl.rb', line 271

def self.default_registry(type)
  config = type_config(type)
  return nil unless config
  
  config["default_registry"]
end

.download_supported_typesArray<String>

Returns types that have download URL support

Examples:

types = Purl.download_supported_types
puts types.include?("gem")  # true if gem has download URL support

Returns:

  • (Array<String>)

    sorted array of types that can generate download URLs



137
138
139
# File 'lib/purl.rb', line 137

def self.download_supported_types
  DownloadURL.supported_types
end

.from_registry_url(registry_url, type: nil) ⇒ Object

Convenience method for parsing registry URLs back to PURLs

Parameters:

  • registry_url (String)

    The registry URL to parse

  • type (String, Symbol, nil) (defaults to: nil)

    Optional type hint for custom domains



93
94
95
# File 'lib/purl.rb', line 93

def self.from_registry_url(registry_url, type: nil)
  RegistryURL.from_url(registry_url, type: type)
end

.known_type?(type) ⇒ Boolean

Check if a type is known/valid

Examples:

Purl.known_type?("gem")     # true
Purl.known_type?("unknown") # false

Parameters:

  • type (String, Symbol)

    the type to check

Returns:

  • (Boolean)

    true if type is known, false otherwise



149
150
151
# File 'lib/purl.rb', line 149

def self.known_type?(type)
  KNOWN_TYPES_SET.include?(type.to_s.downcase)
end

.known_typesArray<String>

Returns all known PURL types

Examples:

types = Purl.known_types
puts types.include?("gem")  # true

Returns:

  • (Array<String>)

    sorted array of known PURL type names



104
105
106
# File 'lib/purl.rb', line 104

def self.known_types
  KNOWN_TYPES.dup
end

.load_types_configObject

Load PURL types configuration from JSON file



49
50
51
52
53
54
55
# File 'lib/purl.rb', line 49

def self.load_types_config
  @types_config ||= begin
    config_path = File.join(__dir__, "..", "purl-types.json")
    require "json"
    deep_freeze(JSON.parse(File.read(config_path)))
  end
end

.parse(purl_string) ⇒ PackageURL

Convenience method for parsing PURL strings

Examples:

purl = Purl.parse("pkg:gem/[email protected]")
puts purl.name  # "rails"

Parameters:

  • purl_string (String)

    a PURL string starting with "pkg:"

Returns:

Raises:



86
87
88
# File 'lib/purl.rb', line 86

def self.parse(purl_string)
  PackageURL.parse(purl_string)
end

.registry_config(type) ⇒ Hash?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get registry configuration for a type

Parameters:

  • type (String, Symbol)

    the type to get registry config for

Returns:

  • (Hash, nil)

    registry configuration hash or nil if not available



256
257
258
259
260
261
# File 'lib/purl.rb', line 256

def self.registry_config(type)
  config = type_config(type)
  return nil unless config
  
  config["registry_config"]
end

.registry_supported_typesArray<String>

Returns types that have registry URL support

Examples:

types = Purl.registry_supported_types
puts types.include?("npm")  # true if npm has registry support

Returns:

  • (Array<String>)

    sorted array of types that can generate registry URLs



115
116
117
# File 'lib/purl.rb', line 115

def self.registry_supported_types
  RegistryURL.supported_types
end

.reverse_parsing_supported_typesArray<String>

Returns types that support reverse parsing from registry URLs

Examples:

types = Purl.reverse_parsing_supported_types
puts types.include?("gem")  # true if gem has reverse parsing support

Returns:

  • (Array<String>)

    sorted array of types that can parse registry URLs back to PURLs



126
127
128
# File 'lib/purl.rb', line 126

def self.reverse_parsing_supported_types
  RegistryURL.supported_reverse_types
end

.type_config(type) ⇒ Hash?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get type configuration from JSON

Parameters:

  • type (String, Symbol)

    the type to get configuration for

Returns:

  • (Hash, nil)

    configuration hash or nil if type not found



216
217
218
219
220
221
# File 'lib/purl.rb', line 216

def self.type_config(type)
  config = load_types_config["types"][type.to_s.downcase]
  return nil unless config

  config
end

.type_description(type) ⇒ String?

Get human-readable description for a type

Examples:

desc = Purl.type_description("gem")
puts desc  # "Ruby gems from RubyGems.org"

Parameters:

  • type (String, Symbol)

    the type to get description for

Returns:

  • (String, nil)

    description string or nil if not available



231
232
233
234
# File 'lib/purl.rb', line 231

def self.type_description(type)
  config = type_config(type)
  config ? config["description"] : nil
end

.type_examples(type) ⇒ Array<String>

Get example PURLs for a type

Examples:

examples = Purl.type_examples("gem")
puts examples.first  # "pkg:gem/[email protected]"

Parameters:

  • type (String, Symbol)

    the type to get examples for

Returns:

  • (Array<String>)

    array of example PURL strings



244
245
246
247
248
249
# File 'lib/purl.rb', line 244

def self.type_examples(type)
  config = type_config(type)
  return [] unless config
  
  config["examples"] || []
end

.type_info(type) ⇒ Hash

Get comprehensive type information including registry support

Examples:

info = Purl.type_info("gem")
puts info[:description]  # "Ruby gems from RubyGems.org"

Parameters:

  • type (String, Symbol)

    the type to get information for

Returns:

  • (Hash)

    hash containing type information with keys:

    • :type: normalized type name
    • :known: whether type is known
    • :description: human-readable description
    • :default_registry: default registry URL
    • :examples: array of example PURLs
    • :registry_url_generation: whether registry URL generation is supported
    • :reverse_parsing: whether reverse parsing is supported
    • :route_patterns: array of URL patterns for this type


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/purl.rb', line 169

def self.type_info(type)
  normalized_type = type.to_s.downcase
  config = type_config(normalized_type)
  {
    type: normalized_type,
    known: known_type?(normalized_type),
    description: config ? config["description"] : nil,
    default_registry: config ? config["default_registry"] : nil,
    examples: config ? (config["examples"] || []) : [],
    registry_url_generation: RegistryURL.supports?(normalized_type),
    reverse_parsing: RegistryURL.supported_reverse_types.include?(normalized_type),
    download_url_generation: DownloadURL.supports?(normalized_type),
    route_patterns: RegistryURL.route_patterns_for(normalized_type)
  }
end

.types_config_metadataHash

Get metadata about the types configuration

Examples:

 = Purl.
puts "Total types: #{metadata[:total_types]}"

Returns:

  • (Hash)

    metadata hash with keys:

    • :version: configuration version
    • :description: configuration description
    • :source: source of the configuration
    • :last_updated: when configuration was last updated
    • :total_types: total number of types
    • :registry_supported_types: number of types with registry support
    • :types_with_default_registry: number of types with default registry


292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/purl.rb', line 292

def self.
  config = load_types_config
  {
    version: config["version"],
    description: config["description"],
    source: config["source"],
    last_updated: config["last_updated"],
    total_types: config["types"].keys.length,
    registry_supported_types: config["types"].select { |_, v| v["registry_config"] }.keys.length,
    types_with_default_registry: config["types"].select { |_, v| v["default_registry"] }.keys.length
  }
end