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+ }
0 commit comments