Skip to content

Commit 10e10b8

Browse files
author
jahanzaib
committed
Add basic PEAR package.xml support
1 parent 7dbb3c7 commit 10e10b8

File tree

6 files changed

+335
-0
lines changed

6 files changed

+335
-0
lines changed

src/packagedcode/phpcomposer.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import io
1111
import json
12+
import xml.etree.ElementTree as ET
1213
from functools import partial
1314

1415
from packagedcode import models
@@ -29,6 +30,7 @@ def assemble(cls, package_data, resource, codebase, package_adder):
2930
datafile_name_patterns = (
3031
'composer.json',
3132
'composer.lock',
33+
'package.xml',
3234
)
3335

3436
if resource.has_parent():
@@ -208,6 +210,121 @@ def parse(cls, location, package_only=False):
208210
yield package
209211

210212

213+
class PhpPearPackageXmlHandler(BasePhpComposerHandler):
214+
datasource_id = 'php_pear_package_xml'
215+
path_patterns = ('*package.xml',)
216+
default_package_type = 'pear'
217+
default_primary_language = 'PHP'
218+
description = 'PEAR package.xml manifest'
219+
documentation_url = 'https://pear.php.net/manual/en/guide.developers.package2.intro.php'
220+
221+
@classmethod
222+
def parse(cls, location, package_only=False):
223+
package = build_pear_package_data(location=location, package_only=package_only)
224+
if package:
225+
yield package
226+
227+
228+
PEAR_V2_NS = {'p': 'http://pear.php.net/dtd/package-2.0'}
229+
PEAR_MAINTAINER_ROLES = (
230+
'lead',
231+
'developer',
232+
'contributor',
233+
'helper',
234+
)
235+
236+
237+
def build_pear_package_data(location, package_only=False):
238+
"""
239+
Return a PackageData built from a PEAR package.xml file at ``location``.
240+
"""
241+
root = ET.parse(location).getroot()
242+
243+
name = find_text(root, 'name')
244+
channel = find_text(root, 'channel')
245+
summary = find_text(root, 'summary')
246+
description = find_text(root, 'description')
247+
homepage_url = find_text(root, 'uri')
248+
license_statement = find_text(root, 'license')
249+
version = find_text(root, 'version/release') or find_text(root, 'release/version')
250+
251+
package_mapping = dict(
252+
datasource_id=PhpPearPackageXmlHandler.datasource_id,
253+
type=PhpPearPackageXmlHandler.default_package_type,
254+
namespace=channel,
255+
name=name,
256+
version=version,
257+
primary_language=PhpPearPackageXmlHandler.default_primary_language,
258+
description=build_description(summary=summary, description=description),
259+
homepage_url=homepage_url,
260+
extracted_license_statement=license_statement,
261+
repository_homepage_url=get_pear_repository_homepage_url(channel=channel, name=name),
262+
parties=list(get_pear_parties(root)),
263+
)
264+
package = models.PackageData.from_data(package_mapping, package_only)
265+
266+
if not package_only:
267+
package.populate_license_fields()
268+
269+
return package
270+
271+
272+
def get_pear_repository_homepage_url(channel, name):
273+
if channel and name:
274+
return f'https://{channel}/package/{name}'
275+
if name:
276+
return f'https://pear.php.net/package/{name}'
277+
278+
279+
def build_description(summary=None, description=None):
280+
summary = summary and summary.strip() or None
281+
description = description and description.strip() or None
282+
283+
if summary and description:
284+
if summary == description:
285+
return description
286+
return f'{summary}\n{description}'
287+
288+
return description or summary
289+
290+
291+
def get_pear_parties(root):
292+
for role in PEAR_MAINTAINER_ROLES:
293+
for maintainer in findall_elements(root, role):
294+
name = find_text(maintainer, 'name') or find_text(maintainer, 'user')
295+
email = find_text(maintainer, 'email')
296+
if name or email:
297+
yield models.Party(
298+
type=models.party_person,
299+
name=name,
300+
email=email,
301+
role=role,
302+
)
303+
304+
305+
def find_text(element, path):
306+
found = find_element(element, path)
307+
if found is not None and found.text:
308+
text = found.text.strip()
309+
return text or None
310+
311+
312+
def find_element(element, path):
313+
namespaced_path = '/'.join(f'p:{part}' for part in path.split('/'))
314+
found = element.find(namespaced_path, PEAR_V2_NS)
315+
if found is not None:
316+
return found
317+
return element.find(path)
318+
319+
320+
def findall_elements(element, path):
321+
namespaced_path = '/'.join(f'p:{part}' for part in path.split('/'))
322+
found = element.findall(namespaced_path, PEAR_V2_NS)
323+
if found:
324+
return found
325+
return element.findall(path)
326+
327+
211328
def licensing_mapper(licenses, package, is_private=False):
212329
"""
213330
Update package licensing and return package.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package version="2.0" xmlns="http://pear.php.net/dtd/package-2.0">
3+
<name>Example_Package</name>
4+
<channel>pear.php.net</channel>
5+
<summary>Example PEAR package</summary>
6+
<description>A small test package for ScanCode PEAR parsing.</description>
7+
<lead>
8+
<name>Jane Doe</name>
9+
<user>janedoe</user>
10+
<email>jane@example.com</email>
11+
<active>yes</active>
12+
</lead>
13+
<license>MIT</license>
14+
<version>
15+
<release>1.2.3</release>
16+
<api>1.2.0</api>
17+
</version>
18+
</package>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
[
2+
{
3+
"type": "pear",
4+
"namespace": "pear.php.net",
5+
"name": "Example_Package",
6+
"version": "1.2.3",
7+
"qualifiers": {},
8+
"subpath": null,
9+
"primary_language": "PHP",
10+
"description": "Example PEAR package\nA small test package for ScanCode PEAR parsing.",
11+
"release_date": null,
12+
"parties": [
13+
{
14+
"type": "person",
15+
"role": "lead",
16+
"name": "Jane Doe",
17+
"email": "jane@example.com",
18+
"url": null
19+
}
20+
],
21+
"keywords": [],
22+
"homepage_url": null,
23+
"download_url": null,
24+
"size": null,
25+
"sha1": null,
26+
"md5": null,
27+
"sha256": null,
28+
"sha512": null,
29+
"bug_tracking_url": null,
30+
"code_view_url": null,
31+
"vcs_url": null,
32+
"copyright": null,
33+
"holder": null,
34+
"declared_license_expression": "mit",
35+
"declared_license_expression_spdx": "MIT",
36+
"license_detections": [
37+
{
38+
"license_expression": "mit",
39+
"license_expression_spdx": "MIT",
40+
"matches": [
41+
{
42+
"license_expression": "mit",
43+
"license_expression_spdx": "MIT",
44+
"from_file": null,
45+
"start_line": 1,
46+
"end_line": 1,
47+
"matcher": "1-spdx-id",
48+
"score": 100.0,
49+
"matched_length": 1,
50+
"match_coverage": 100.0,
51+
"rule_relevance": 100,
52+
"rule_identifier": "spdx-license-identifier-mit-5da48780aba670b0860c46d899ed42a0f243ff06",
53+
"rule_url": null,
54+
"matched_text": "MIT"
55+
}
56+
],
57+
"identifier": "mit-a822f434-d61f-f2b1-c792-8b8cb9e7b9bf"
58+
}
59+
],
60+
"other_license_expression": null,
61+
"other_license_expression_spdx": null,
62+
"other_license_detections": [],
63+
"extracted_license_statement": "MIT",
64+
"notice_text": null,
65+
"source_packages": [],
66+
"file_references": [],
67+
"is_private": false,
68+
"is_virtual": false,
69+
"extra_data": {},
70+
"dependencies": [],
71+
"repository_homepage_url": "https://pear.php.net/package/Example_Package",
72+
"repository_download_url": null,
73+
"api_data_url": null,
74+
"datasource_id": "php_pear_package_xml",
75+
"purl": "pkg:pear/pear.php.net/Example_Package@1.2.3"
76+
}
77+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package version="2.0" xmlns="http://pear.php.net/dtd/package-2.0">
3+
<name>Net_URL2</name>
4+
<channel>pear.example.org</channel>
5+
<summary>URL parsing helpers</summary>
6+
<description>Utilities for working with URLs in legacy PEAR packages.</description>
7+
<uri>https://example.org/pear/Net_URL2</uri>
8+
<lead>
9+
<user>leaddev</user>
10+
<email>lead@example.org</email>
11+
<active>yes</active>
12+
</lead>
13+
<developer>
14+
<name>John Smith</name>
15+
<email>john.smith@example.org</email>
16+
<active>yes</active>
17+
</developer>
18+
<license>MIT</license>
19+
<version>
20+
<release>2.0.0</release>
21+
<api>2.0.0</api>
22+
</version>
23+
</package>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
[
2+
{
3+
"type": "pear",
4+
"namespace": "pear.example.org",
5+
"name": "Net_URL2",
6+
"version": "2.0.0",
7+
"qualifiers": {},
8+
"subpath": null,
9+
"primary_language": "PHP",
10+
"description": "URL parsing helpers\nUtilities for working with URLs in legacy PEAR packages.",
11+
"release_date": null,
12+
"parties": [
13+
{
14+
"type": "person",
15+
"role": "lead",
16+
"name": "leaddev",
17+
"email": "lead@example.org",
18+
"url": null
19+
},
20+
{
21+
"type": "person",
22+
"role": "developer",
23+
"name": "John Smith",
24+
"email": "john.smith@example.org",
25+
"url": null
26+
}
27+
],
28+
"keywords": [],
29+
"homepage_url": "https://example.org/pear/Net_URL2",
30+
"download_url": null,
31+
"size": null,
32+
"sha1": null,
33+
"md5": null,
34+
"sha256": null,
35+
"sha512": null,
36+
"bug_tracking_url": null,
37+
"code_view_url": null,
38+
"vcs_url": null,
39+
"copyright": null,
40+
"holder": null,
41+
"declared_license_expression": "mit",
42+
"declared_license_expression_spdx": "MIT",
43+
"license_detections": [
44+
{
45+
"license_expression": "mit",
46+
"license_expression_spdx": "MIT",
47+
"matches": [
48+
{
49+
"license_expression": "mit",
50+
"license_expression_spdx": "MIT",
51+
"from_file": null,
52+
"start_line": 1,
53+
"end_line": 1,
54+
"matcher": "1-spdx-id",
55+
"score": 100.0,
56+
"matched_length": 1,
57+
"match_coverage": 100.0,
58+
"rule_relevance": 100,
59+
"rule_identifier": "spdx-license-identifier-mit-5da48780aba670b0860c46d899ed42a0f243ff06",
60+
"rule_url": null,
61+
"matched_text": "MIT"
62+
}
63+
],
64+
"identifier": "mit-a822f434-d61f-f2b1-c792-8b8cb9e7b9bf"
65+
}
66+
],
67+
"other_license_expression": null,
68+
"other_license_expression_spdx": null,
69+
"other_license_detections": [],
70+
"extracted_license_statement": "MIT",
71+
"notice_text": null,
72+
"source_packages": [],
73+
"file_references": [],
74+
"is_private": false,
75+
"is_virtual": false,
76+
"extra_data": {},
77+
"dependencies": [],
78+
"repository_homepage_url": "https://pear.example.org/package/Net_URL2",
79+
"repository_download_url": null,
80+
"api_data_url": null,
81+
"datasource_id": "php_pear_package_xml",
82+
"purl": "pkg:pear/pear.example.org/Net_URL2@2.0.0"
83+
}
84+
]

tests/packagedcode/test_phpcomposer.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,19 @@ def test_parse_composer_lock(self):
8686
expected_loc = self.get_test_loc('phpcomposer/composer.lock-expected.json')
8787
packages = phpcomposer.PhpComposerLockHandler.parse(test_file)
8888
self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)
89+
90+
def test_is_manifest_php_pear_package_xml(self):
91+
test_file = self.get_test_loc('phpcomposer/pear_basic/package.xml')
92+
assert phpcomposer.PhpPearPackageXmlHandler.is_datafile(test_file)
93+
94+
def test_parse_php_pear_package_xml_basic(self):
95+
test_file = self.get_test_loc('phpcomposer/pear_basic/package.xml')
96+
expected_loc = self.get_test_loc('phpcomposer/pear_basic/package.xml.expected')
97+
packages = phpcomposer.PhpPearPackageXmlHandler.parse(test_file)
98+
self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)
99+
100+
def test_parse_php_pear_package_xml_with_uri_and_multiple_parties(self):
101+
test_file = self.get_test_loc('phpcomposer/pear_with_uri/package.xml')
102+
expected_loc = self.get_test_loc('phpcomposer/pear_with_uri/package.xml.expected')
103+
packages = phpcomposer.PhpPearPackageXmlHandler.parse(test_file)
104+
self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)

0 commit comments

Comments
 (0)