Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions app/models/ecosystem/pecl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# frozen_string_literal: true

module Ecosystem
class Pecl < Base
def registry_url(package, version = nil)
if version
"https://pecl.php.net/package/#{package.name}/#{version.number}"
else
"https://pecl.php.net/package/#{package.name}"
end
end

def install_command(package, version = nil)
"pecl install #{package.name}" + (version ? "-#{version}" : "")
end

def check_status_url(package)
"https://pecl.php.net/rest/p/#{package.name.downcase}/info.xml"
end

def all_package_names
packages = get_xml("#{registry_url_base}/rest/p/packages.xml")
packages.xpath("//*[local-name()='p']").map(&:text).reject(&:blank?)
rescue
[]
end

def fetch_package_metadata_uncached(name)
info = get_xml("#{registry_url_base}/rest/p/#{name.downcase}/info.xml")
releases = get_xml("#{registry_url_base}/rest/r/#{name.downcase}/allreleases.xml")
{
info: info,
releases: releases
}
rescue
{}
end

def map_package_metadata(package)
info = package[:info]
return nil if info.blank?

name = xml_text(info, "n")
return nil if name.blank?

{
name: name,
description: xml_text(info, "s"),
homepage: "https://pecl.php.net/package/#{name}",
repository_url: nil,
licenses: xml_text(info, "l"),
metadata: {
category: xml_text(info, "ca"),
summary: xml_text(info, "s"),
description: xml_text(info, "d")
}
}
end

def versions_metadata(pkg_metadata, existing_version_numbers = [])
releases = pkg_metadata[:releases]
return [] if releases.blank?

releases.xpath("//*[local-name()='r']").map do |release|
version = xml_text(release, "v")
next if version.blank?

details = fetch_release_metadata(pkg_metadata[:name], version)
{
number: version,
published_at: details[:published_at],
metadata: {
status: xml_text(release, "s"),
license: details[:license],
summary: details[:summary],
description: details[:description],
notes: details[:notes]
}.compact
}
end.compact
end

def purl_type
"pear"
end

private

def registry_url_base
registry_url.to_s.delete_suffix("/")
end

def xml_text(document, name)
document.at_xpath(".//*[local-name()='#{name}']")&.text
end

def fetch_release_metadata(name, version)
release = get_xml("#{registry_url_base}/rest/r/#{name.downcase}/#{version}.xml")
{
published_at: xml_text(release, "da"),
license: xml_text(release, "l"),
summary: xml_text(release, "s"),
description: xml_text(release, "d"),
notes: xml_text(release, "n")
}
rescue
{}
end
end
end
1 change: 1 addition & 0 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
{name: 'npmjs.org', url: 'https://registry.npmjs.org', ecosystem: 'npm', github: 'npm', default: true},
{name: 'nuget.org', url: 'https://www.nuget.org', ecosystem: 'nuget', github: 'nuget', default: true},
{name: 'packagist.org', url: 'https://packagist.org', ecosystem: 'packagist', github: 'packagist', default: true},
{name: 'pecl.php.net', url: 'https://pecl.php.net', ecosystem: 'pecl', github: 'php', default: true},
{name: 'pub.dev', url: 'https://pub.dev', ecosystem: 'pub', github: 'dart-lang', default: true},
{name: 'pypi.org', url: 'https://pypi.org', ecosystem: 'pypi', github: 'pypi', default: true},
{name: 'rubygems.org', url: 'https://rubygems.org', ecosystem: 'rubygems', github: 'rubygems', default: true},
Expand Down
124 changes: 124 additions & 0 deletions test/models/ecosystem/pecl_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
require "test_helper"

class PeclTest < ActiveSupport::TestCase
setup do
@registry = Registry.new(name: "pecl.php.net", url: "https://pecl.php.net", ecosystem: "pecl", default: true)
@ecosystem = Ecosystem::Pecl.new(@registry)
@package = Package.new(ecosystem: "pecl", name: "redis")
@version = @package.versions.build(number: "6.2.0")
end

test "registry_url" do
assert_equal "https://pecl.php.net/package/redis", @ecosystem.registry_url(@package)
end

test "registry_url with version" do
assert_equal "https://pecl.php.net/package/redis/6.2.0", @ecosystem.registry_url(@package, @version)
end

test "install_command" do
assert_equal "pecl install redis", @ecosystem.install_command(@package)
assert_equal "pecl install redis-6.2.0", @ecosystem.install_command(@package, "6.2.0")
end

test "check_status_url" do
assert_equal "https://pecl.php.net/rest/p/redis/info.xml", @ecosystem.check_status_url(@package)
end

test "all_package_names" do
stub_request(:get, "https://pecl.php.net/rest/p/packages.xml")
.to_return(
status: 200,
body: <<~XML,
<?xml version="1.0" encoding="UTF-8" ?>
<a xmlns="http://pear.php.net/dtd/rest.allpackages">
<c>pecl.php.net</c>
<p>redis</p>
<p>amqp</p>
</a>
XML
headers: { "Content-Type" => "application/xml" }
)

assert_equal ["redis", "amqp"], @ecosystem.all_package_names
end

test "package_metadata" do
stub_request(:get, "https://pecl.php.net/rest/p/redis/info.xml")
.to_return(
status: 200,
body: <<~XML,
<?xml version="1.0" encoding="UTF-8" ?>
<p xmlns="http://pear.php.net/dtd/rest.package">
<n>redis</n>
<c>pecl.php.net</c>
<ca>Database</ca>
<l>PHP</l>
<s>PHP extension for Redis</s>
<d>Communicates with RESP-based key-value stores.</d>
</p>
XML
headers: { "Content-Type" => "application/xml" }
)
stub_request(:get, "https://pecl.php.net/rest/r/redis/allreleases.xml")
.to_return(
status: 200,
body: <<~XML,
<?xml version="1.0" encoding="UTF-8" ?>
<a xmlns="http://pear.php.net/dtd/rest.allreleases">
<p>redis</p>
<c>pecl.php.net</c>
<r><v>6.2.0</v><s>stable</s></r>
</a>
XML
headers: { "Content-Type" => "application/xml" }
)

metadata = @ecosystem.package_metadata("redis")

assert_equal "redis", metadata[:name]
assert_equal "PHP extension for Redis", metadata[:description]
assert_equal "https://pecl.php.net/package/redis", metadata[:homepage]
assert_equal "Database", metadata[:metadata][:category]
end

test "versions_metadata" do
stub_request(:get, "https://pecl.php.net/rest/r/redis/6.2.0.xml")
.to_return(
status: 200,
body: <<~XML,
<?xml version="1.0" encoding="UTF-8" ?>
<r xmlns="http://pear.php.net/dtd/rest.release">
<p>redis</p>
<v>6.2.0</v>
<st>stable</st>
<l>PHP</l>
<s>PHP extension for Redis</s>
<d>Communicates with RESP-based key-value stores.</d>
<da>2025-03-24 19:05:36</da>
<n>Release notes</n>
</r>
XML
headers: { "Content-Type" => "application/xml" }
)

releases = Nokogiri::XML(<<~XML)
<?xml version="1.0" encoding="UTF-8" ?>
<a xmlns="http://pear.php.net/dtd/rest.allreleases">
<r><v>6.2.0</v><s>stable</s></r>
</a>
XML

versions = @ecosystem.versions_metadata({ name: "redis", releases: releases })

assert_equal 1, versions.length
assert_equal "6.2.0", versions.first[:number]
assert_equal "2025-03-24 19:05:36", versions.first[:published_at]
assert_equal "stable", versions.first[:metadata][:status]
assert_equal "Release notes", versions.first[:metadata][:notes]
end

test "purl uses pear type" do
assert_equal "pear", @ecosystem.purl_type
end
end