Skip to content

Commit 8d12b2c

Browse files
committed
Add support for componentjs aboutcode-org#4107
Signed-off-by: NucleonGodX <racerpro41@gmail.com>
1 parent e795bc6 commit 8d12b2c

File tree

13 files changed

+1194
-1
lines changed

13 files changed

+1194
-1
lines changed

src/packagedcode/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from packagedcode import build_gradle
1616
from packagedcode import cargo
1717
from packagedcode import chef
18+
from packagedcode import componentjs
1819
from packagedcode import debian
1920
from packagedcode import debian_copyright
2021
from packagedcode import distro
@@ -81,7 +82,7 @@
8182
conan.ConanDataHandler,
8283

8384
cran.CranDescriptionFileHandler,
84-
85+
componentjs.ComponentJSONMetadataHandler,
8586
debian_copyright.DebianCopyrightFileInPackageHandler,
8687
debian_copyright.StandaloneDebianCopyrightFileHandler,
8788
debian.DebianDscFileHandler,

src/packagedcode/componentjs.py

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import json
2+
from packagedcode import models
3+
from packageurl import PackageURL
4+
import yaml
5+
6+
class ComponentJSONMetadataHandler(models.NonAssemblableDatafileHandler):
7+
"""
8+
Handle JSON metadata files for package analysis.
9+
"""
10+
datasource_id = "json_metadata"
11+
path_patterns = ("*component.json",)
12+
default_package_type = "library"
13+
description = "JSON package metadata file"
14+
15+
@classmethod
16+
def parse(cls, location, package_only=False):
17+
"""
18+
Parse the JSON metadata file at `location` and yield PackageData.
19+
"""
20+
with open(location, "r", encoding="utf-8") as f:
21+
data = json.load(f)
22+
23+
name = data.get('name') or data.get('repo', '').split('/')[-1]
24+
if not name:
25+
return
26+
27+
namespace = None
28+
if 'repo' in data and '/' in data['repo']:
29+
namespace, name = data['repo'].split('/', 1)
30+
31+
package_data = dict(
32+
datasource_id=cls.datasource_id,
33+
type=cls.default_package_type,
34+
name=name,
35+
namespace=namespace,
36+
version=data.get('version'),
37+
description=data.get('description', ''),
38+
homepage_url=cls._extract_homepage(data),
39+
keywords=data.get('keywords', []),
40+
dependencies=cls._process_dependencies(data),
41+
extracted_license_statement=cls._extract_license_statement(data),
42+
extra_data=cls._extract_extra_data(data)
43+
)
44+
45+
if namespace and name:
46+
try:
47+
package_data['purl'] = PackageURL(
48+
type='generic',
49+
namespace=namespace,
50+
name=name,
51+
version=package_data.get('version')
52+
).to_string()
53+
except Exception:
54+
pass
55+
56+
yield models.PackageData.from_data(package_data, package_only)
57+
58+
@staticmethod
59+
def _extract_homepage(data):
60+
"""
61+
Extract homepage URL from various possible sources.
62+
"""
63+
if data.get('homepage'):
64+
return data['homepage']
65+
66+
if data.get('repo'):
67+
return f'https://github.com/{data["repo"]}'
68+
69+
desc = data.get('description', '')
70+
if 'http' in desc:
71+
urls = [word for word in desc.split() if word.startswith('http')]
72+
return urls[0] if urls else None
73+
74+
return None
75+
76+
@staticmethod
77+
def _process_dependencies(data):
78+
"""
79+
Process dependencies into DependentPackage objects.
80+
"""
81+
dependencies = []
82+
83+
for dep_name, dep_version in data.get('dependencies', {}).items():
84+
try:
85+
if '/' in dep_name:
86+
namespace, name = dep_name.split('/', 1)
87+
else:
88+
namespace, name = None, dep_name
89+
90+
purl = PackageURL(
91+
type='generic',
92+
namespace=namespace,
93+
name=name,
94+
version=dep_version
95+
).to_string()
96+
97+
dependencies.append(
98+
models.DependentPackage(
99+
purl=purl,
100+
scope='runtime',
101+
is_runtime=True,
102+
is_optional=False
103+
)
104+
)
105+
except Exception:
106+
continue
107+
108+
return dependencies
109+
110+
@classmethod
111+
def _extract_license_statement(cls, data):
112+
"""
113+
Extract license statement similar to BuildpackHandler.
114+
115+
Handles various license formats:
116+
- Simple string license
117+
- Multiple licenses
118+
- Complex license strings
119+
"""
120+
license_field = data.get('license')
121+
if not license_field:
122+
return None
123+
124+
if isinstance(license_field, str):
125+
return yaml.dump({"type": license_field.strip()}).strip()
126+
127+
if isinstance(license_field, list):
128+
license_statements = [
129+
yaml.dump({"type": lic.strip()}).strip()
130+
for lic in license_field
131+
if lic.strip()
132+
]
133+
return "\n".join(license_statements) if license_statements else None
134+
135+
return None
136+
137+
@staticmethod
138+
def _extract_extra_data(data):
139+
"""
140+
Extract additional metadata not in core package data.
141+
"""
142+
extra_fields = [
143+
'main', 'scripts', 'styles', 'bin',
144+
'repository', 'private', 'dev', 'development'
145+
]
146+
147+
return {
148+
field: data[field]
149+
for field in extra_fields
150+
if field in data
151+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "angular-ui-sortable",
3+
"version": "0.0.1",
4+
"description": "This directive allows you to jQueryUI Sortable.",
5+
"author": "https://github.com/angular-ui/ui-sortable/graphs/contributors",
6+
"license": "MIT",
7+
"homepage": "http://angular-ui.github.com",
8+
"main": "./src/sortable.js",
9+
"ignore": [
10+
"**/.*",
11+
"node_modules",
12+
"components",
13+
"test*",
14+
"demo*",
15+
"gruntFile.js",
16+
"package.json"
17+
],
18+
"dependencies": {
19+
"angular": "~1.x",
20+
"jquery-ui": ">= 1.9"
21+
},
22+
"devDependencies": {
23+
"angular-mocks": "~1.x"
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"packages": [],
3+
"dependencies": [],
4+
"files": [
5+
{
6+
"path": "component.json",
7+
"type": "file",
8+
"package_data": [
9+
{
10+
"type": "library",
11+
"namespace": null,
12+
"name": "angular-ui-sortable",
13+
"version": "0.0.1",
14+
"qualifiers": {},
15+
"subpath": null,
16+
"primary_language": null,
17+
"description": "This directive allows you to jQueryUI Sortable.",
18+
"release_date": null,
19+
"parties": [],
20+
"keywords": [],
21+
"homepage_url": "http://angular-ui.github.com",
22+
"download_url": null,
23+
"size": null,
24+
"sha1": null,
25+
"md5": null,
26+
"sha256": null,
27+
"sha512": null,
28+
"bug_tracking_url": null,
29+
"code_view_url": null,
30+
"vcs_url": null,
31+
"copyright": null,
32+
"holder": null,
33+
"declared_license_expression": "mit",
34+
"declared_license_expression_spdx": "MIT",
35+
"license_detections": [
36+
{
37+
"license_expression": "mit",
38+
"license_expression_spdx": "MIT",
39+
"matches": [
40+
{
41+
"license_expression": "mit",
42+
"license_expression_spdx": "MIT",
43+
"from_file": "component.json",
44+
"start_line": 1,
45+
"end_line": 1,
46+
"matcher": "1-hash",
47+
"score": 16.0,
48+
"matched_length": 3,
49+
"match_coverage": 100.0,
50+
"rule_relevance": 16,
51+
"rule_identifier": "mit_1301.RULE",
52+
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_1301.RULE",
53+
"matched_text": "license type: MIT"
54+
}
55+
],
56+
"identifier": "mit-1c9cba21-81d2-7522-ac3e-dfde6630f8d1"
57+
}
58+
],
59+
"other_license_expression": null,
60+
"other_license_expression_spdx": null,
61+
"other_license_detections": [],
62+
"extracted_license_statement": "type: MIT",
63+
"notice_text": null,
64+
"source_packages": [],
65+
"file_references": [],
66+
"is_private": false,
67+
"is_virtual": false,
68+
"extra_data": {
69+
"main": "./src/sortable.js"
70+
},
71+
"dependencies": [
72+
{
73+
"purl": "pkg:generic/angular@~1.x",
74+
"extracted_requirement": null,
75+
"scope": "runtime",
76+
"is_runtime": true,
77+
"is_optional": false,
78+
"is_pinned": false,
79+
"is_direct": true,
80+
"resolved_package": {},
81+
"extra_data": {}
82+
},
83+
{
84+
"purl": "pkg:generic/jquery-ui@%3E%3D%201.9",
85+
"extracted_requirement": null,
86+
"scope": "runtime",
87+
"is_runtime": true,
88+
"is_optional": false,
89+
"is_pinned": false,
90+
"is_direct": true,
91+
"resolved_package": {},
92+
"extra_data": {}
93+
}
94+
],
95+
"repository_homepage_url": null,
96+
"repository_download_url": null,
97+
"api_data_url": null,
98+
"datasource_id": "json_metadata",
99+
"purl": "pkg:library/angular-ui-sortable@0.0.1"
100+
}
101+
],
102+
"for_packages": [],
103+
"scan_errors": []
104+
}
105+
]
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "chai"
3+
, "repo": "chaijs/chai"
4+
, "version": "2.1.2"
5+
, "description": "BDD/TDD assertion library for node.js and the browser. Test framework agnostic."
6+
, "license": "MIT"
7+
, "keywords": [
8+
"test"
9+
, "assertion"
10+
, "assert"
11+
, "testing"
12+
, "chai"
13+
]
14+
, "main": "index.js"
15+
, "scripts": [
16+
"index.js"
17+
, "lib/chai.js"
18+
, "lib/chai/assertion.js"
19+
, "lib/chai/config.js"
20+
, "lib/chai/core/assertions.js"
21+
, "lib/chai/interface/assert.js"
22+
, "lib/chai/interface/expect.js"
23+
, "lib/chai/interface/should.js"
24+
, "lib/chai/utils/addChainableMethod.js"
25+
, "lib/chai/utils/addMethod.js"
26+
, "lib/chai/utils/addProperty.js"
27+
, "lib/chai/utils/flag.js"
28+
, "lib/chai/utils/getActual.js"
29+
, "lib/chai/utils/getEnumerableProperties.js"
30+
, "lib/chai/utils/getMessage.js"
31+
, "lib/chai/utils/getName.js"
32+
, "lib/chai/utils/getPathValue.js"
33+
, "lib/chai/utils/getPathInfo.js"
34+
, "lib/chai/utils/hasProperty.js"
35+
, "lib/chai/utils/getProperties.js"
36+
, "lib/chai/utils/index.js"
37+
, "lib/chai/utils/inspect.js"
38+
, "lib/chai/utils/objDisplay.js"
39+
, "lib/chai/utils/overwriteMethod.js"
40+
, "lib/chai/utils/overwriteProperty.js"
41+
, "lib/chai/utils/overwriteChainableMethod.js"
42+
, "lib/chai/utils/test.js"
43+
, "lib/chai/utils/transferFlags.js"
44+
, "lib/chai/utils/type.js"
45+
]
46+
, "dependencies": {
47+
"chaijs/assertion-error": "1.0.0"
48+
, "chaijs/deep-eql": "0.1.3"
49+
}
50+
, "development": {}
51+
}

0 commit comments

Comments
 (0)