scanoss 1.20.0__py3-none-any.whl → 1.20.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. protoc_gen_swagger/__init__.py +13 -13
  2. protoc_gen_swagger/options/__init__.py +13 -13
  3. protoc_gen_swagger/options/annotations_pb2.py +12 -9
  4. protoc_gen_swagger/options/annotations_pb2_grpc.py +1 -1
  5. protoc_gen_swagger/options/openapiv2_pb2.py +98 -96
  6. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +1 -1
  7. scanoss/__init__.py +18 -18
  8. scanoss/api/__init__.py +17 -17
  9. scanoss/api/common/__init__.py +17 -17
  10. scanoss/api/common/v2/__init__.py +17 -17
  11. scanoss/api/common/v2/scanoss_common_pb2.py +18 -18
  12. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +1 -1
  13. scanoss/api/components/__init__.py +17 -17
  14. scanoss/api/components/v2/__init__.py +17 -17
  15. scanoss/api/components/v2/scanoss_components_pb2.py +48 -38
  16. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +142 -96
  17. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +22 -16
  18. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +75 -49
  19. scanoss/api/dependencies/__init__.py +17 -17
  20. scanoss/api/dependencies/v2/__init__.py +17 -17
  21. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +30 -24
  22. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +75 -49
  23. scanoss/api/scanning/__init__.py +17 -17
  24. scanoss/api/scanning/v2/__init__.py +17 -17
  25. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +10 -8
  26. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +40 -32
  27. scanoss/api/semgrep/__init__.py +17 -17
  28. scanoss/api/semgrep/v2/__init__.py +17 -17
  29. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +22 -18
  30. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +71 -49
  31. scanoss/api/vulnerabilities/__init__.py +17 -17
  32. scanoss/api/vulnerabilities/v2/__init__.py +17 -17
  33. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +37 -27
  34. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +109 -72
  35. scanoss/cli.py +664 -356
  36. scanoss/components.py +67 -45
  37. scanoss/csvoutput.py +83 -56
  38. scanoss/cyclonedx.py +48 -46
  39. scanoss/data/build_date.txt +1 -1
  40. scanoss/file_filters.py +13 -15
  41. scanoss/filecount.py +43 -36
  42. scanoss/inspection/__init__.py +17 -17
  43. scanoss/inspection/copyleft.py +71 -58
  44. scanoss/inspection/policy_check.py +76 -53
  45. scanoss/inspection/undeclared_component.py +98 -75
  46. scanoss/inspection/utils/license_utils.py +66 -44
  47. scanoss/results.py +51 -60
  48. scanoss/scancodedeps.py +61 -38
  49. scanoss/scanner.py +203 -135
  50. scanoss/scanoss_settings.py +5 -3
  51. scanoss/scanossapi.py +98 -69
  52. scanoss/scanossbase.py +19 -19
  53. scanoss/scanossgrpc.py +73 -51
  54. scanoss/scanpostprocessor.py +9 -6
  55. scanoss/scantype.py +22 -21
  56. scanoss/spdxlite.py +265 -171
  57. scanoss/threadeddependencies.py +91 -61
  58. scanoss/threadedscanning.py +37 -31
  59. scanoss/utils/file.py +4 -4
  60. scanoss/winnowing.py +111 -47
  61. {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/METADATA +1 -1
  62. scanoss-1.20.2.dist-info/RECORD +74 -0
  63. {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/WHEEL +1 -1
  64. scanoss-1.20.0.dist-info/RECORD +0 -74
  65. {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/LICENSE +0 -0
  66. {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/entry_points.txt +0 -0
  67. {scanoss-1.20.0.dist-info → scanoss-1.20.2.dist-info}/top_level.txt +0 -0
scanoss/scantype.py CHANGED
@@ -1,25 +1,25 @@
1
1
  """
2
- SPDX-License-Identifier: MIT
3
-
4
- Copyright (c) 2021, SCANOSS
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in
14
- all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- THE SOFTWARE.
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2021, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
23
  """
24
24
 
25
25
  from enum import Enum
@@ -29,6 +29,7 @@ class ScanType(Enum):
29
29
  """
30
30
  Octal Enum class describing all the scanning options
31
31
  """
32
+
32
33
  SCAN_FILES = 1
33
34
  SCAN_SNIPPETS = 2
34
35
  SCAN_DEPENDENCIES = 4
scanoss/spdxlite.py CHANGED
@@ -1,33 +1,35 @@
1
1
  """
2
- SPDX-License-Identifier: MIT
3
-
4
- Copyright (c) 2021, SCANOSS
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in
14
- all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- THE SOFTWARE.
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2021, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
23
  """
24
- import json
25
- import os.path
26
- import sys
27
- import hashlib
24
+
28
25
  import datetime
29
26
  import getpass
27
+ import hashlib
28
+ import json
29
+ import os.path
30
30
  import re
31
+ import sys
32
+
31
33
  import importlib_resources
32
34
 
33
35
  from . import __version__
@@ -71,75 +73,103 @@ class SpdxLite:
71
73
  if not data:
72
74
  self.print_stderr('ERROR: No JSON data provided to parse.')
73
75
  return None
74
- self.print_debug(f'Processing raw results into summary format...')
76
+
77
+ self.print_debug('Processing raw results into summary format...')
78
+ return self._process_files(data)
79
+
80
+ def _process_files(self, data: json) -> dict:
81
+ """Process each file in the data and build summary."""
82
+ summary = {}
83
+ for file_path in data:
84
+ file_details = data.get(file_path)
85
+ self._process_file_entries(file_path, file_details, summary)
86
+ return summary
87
+
88
+ def _process_file_entries(self, file_path: str, file_details: list, summary: dict):
89
+ """Process entries for a single file."""
90
+ for entry in file_details:
91
+ id_details = entry.get('id')
92
+ if not id_details or id_details == 'none':
93
+ continue
94
+
95
+ if id_details == 'dependency':
96
+ self._process_dependency_entry(file_path, entry, summary)
97
+ else:
98
+ self._process_normal_entry(file_path, entry, summary)
99
+
100
+ def _process_dependency_entry(self, file_path: str, entry: dict, summary: dict):
101
+ """Process a dependency type entry."""
102
+ dependencies = entry.get('dependencies')
103
+ if not dependencies:
104
+ self.print_stderr(f'Warning: No Dependencies found for {file_path}')
105
+ return
106
+
107
+ for dep in dependencies:
108
+ purl = dep.get('purl')
109
+ if not self._is_valid_purl(file_path, dep, purl, summary):
110
+ continue
111
+
112
+ summary[purl] = self._create_dependency_summary(dep)
113
+
114
+ def _process_normal_entry(self, file_path: str, entry: dict, summary: dict):
115
+ """Process a normal file type entry."""
116
+ purls = entry.get('purl')
117
+ if not purls:
118
+ self.print_stderr(f'Purl block missing for {file_path}')
119
+ return
120
+
121
+ purl = purls[0] if purls else None
122
+ if not self._is_valid_purl(file_path, entry, purl, summary):
123
+ return
124
+
125
+ summary[purl] = self._create_normal_summary(entry)
126
+
127
+ def _is_valid_purl(self, file_path: str, entry: dict, purl: str, summary: dict) -> bool:
128
+ """Check if PURL is valid and not already processed."""
129
+ if not purl:
130
+ self.print_stderr(f'Warning: No PURL found for {file_path}: {entry}')
131
+ return False
132
+
133
+ if summary.get(purl):
134
+ self.print_debug(f'Component {purl} already stored: {summary.get(purl)}')
135
+ return False
136
+
137
+ return True
138
+
139
+ def _create_dependency_summary(self, dep: dict) -> dict:
140
+ """Create summary for dependency entry."""
75
141
  summary = {}
76
- for f in data:
77
- file_details = data.get(f)
78
- # print(f'File: {f}: {file_details}\n')
79
- for d in file_details:
80
- id_details = d.get("id")
81
- if not id_details or id_details == 'none': # Ignore files with no ids
82
- continue
83
- purl = None
84
- if id_details == 'dependency': # Process dependency data
85
- dependencies = d.get("dependencies")
86
- if not dependencies:
87
- self.print_stderr(f'Warning: No Dependencies found for {f}: {file_details}')
88
- continue
89
- for deps in dependencies:
90
- # print(f'File: {f} Deps: {deps}')
91
- purl = deps.get("purl")
92
- if not purl:
93
- self.print_stderr(f'Warning: No PURL found for {f}: {deps}')
94
- continue
95
- if summary.get(purl):
96
- self.print_debug(f'Component {purl} already stored: {summary.get(purl)}')
97
- continue
98
- fd = {}
99
- for field in ['component', 'version', 'url']:
100
- fd[field] = deps.get(field, '')
101
- licenses = deps.get('licenses')
102
- fdl = []
103
- if licenses:
104
- dc = []
105
- for lic in licenses:
106
- name = lic.get("name")
107
- if name not in dc: # Only save the license name once
108
- fdl.append({'id': name})
109
- dc.append(name)
110
- fd['licenses'] = fdl
111
- summary[purl] = fd
112
- else: # Normal file id type
113
- purls = d.get('purl')
114
- if not purls:
115
- self.print_stderr(f'Purl block missing for {f}: {file_details}')
116
- continue
117
- for p in purls:
118
- self.print_debug(f'Purl: {p}')
119
- purl = p
120
- break
121
- if not purl:
122
- self.print_stderr(f'Warning: No PURL found for {f}: {file_details}')
123
- continue
124
- if summary.get(purl):
125
- self.print_debug(f'Component {purl} already stored: {summary.get(purl)}')
126
- continue
127
- fd = {}
128
- for field in ['id', 'vendor', 'component', 'version', 'latest', 'url']:
129
- fd[field] = d.get(field)
130
- licenses = d.get('licenses')
131
- fdl = []
132
- if licenses:
133
- dc = []
134
- for lic in licenses:
135
- name = lic.get("name")
136
- if name not in dc: # Only save the license name once
137
- fdl.append({'id': name})
138
- dc.append(name)
139
- fd['licenses'] = fdl
140
- summary[purl] = fd
142
+ for field in ['component', 'version', 'url']:
143
+ summary[field] = dep.get(field, '')
144
+ summary['licenses'] = self._process_licenses(dep.get('licenses'))
141
145
  return summary
142
146
 
147
+ def _create_normal_summary(self, entry: dict) -> dict:
148
+ """Create summary for normal file entry."""
149
+ summary = {}
150
+ fields = ['id', 'vendor', 'component', 'version', 'latest',
151
+ 'url', 'url_hash', 'download_url']
152
+ for field in fields:
153
+ summary[field] = entry.get(field)
154
+ summary['licenses'] = self._process_licenses(entry.get('licenses'))
155
+ return summary
156
+
157
+ def _process_licenses(self, licenses: list) -> list:
158
+ """Process license information and remove duplicates."""
159
+ if not licenses:
160
+ return []
161
+
162
+ processed_licenses = []
163
+ seen_names = set()
164
+
165
+ for license_info in licenses:
166
+ name = license_info.get('name')
167
+ if name and name not in seen_names:
168
+ processed_licenses.append({'id': name})
169
+ seen_names.add(name)
170
+
171
+ return processed_licenses
172
+
143
173
  def produce_from_file(self, json_file: str, output_file: str = None) -> bool:
144
174
  """
145
175
  Parse plain/raw input JSON file and produce SPDX Lite output
@@ -168,101 +198,163 @@ class SpdxLite:
168
198
  if not raw_data:
169
199
  self.print_stderr('ERROR: No SPDX data returned for the JSON string provided.')
170
200
  return False
201
+
171
202
  self.load_license_data()
172
- # Using this SPDX version as the spec
173
- # https://github.com/spdx/spdx-spec/blob/development/v2.2.2/examples/SPDXJSONExample-v2.2.spdx.json
174
- # Validate using:
175
- # pip3 install jsonschema
176
- # jsonschema -i spdxlite.json <(curl https://raw.githubusercontent.com/spdx/spdx-spec/v2.2/schemas/spdx-schema.json)
177
- # Validation can also be done online here: https://tools.spdx.org/app/validate/
178
- now = datetime.datetime.utcnow() # TODO replace with recommended format
203
+ spdx_document = self._create_base_document(raw_data)
204
+ self._process_packages(raw_data, spdx_document)
205
+ return self._write_output(spdx_document, output_file)
206
+
207
+ def _create_base_document(self, raw_data: dict) -> dict:
208
+ """Create the base SPDX document structure."""
209
+ now = datetime.datetime.utcnow()
179
210
  md5hex = hashlib.md5(f'{raw_data}-{now}'.encode('utf-8')).hexdigest()
180
- data = {
211
+
212
+ return {
181
213
  'spdxVersion': 'SPDX-2.2',
182
214
  'dataLicense': 'CC0-1.0',
183
- 'SPDXID': f'SPDXRef-DOCUMENT',
215
+ 'SPDXID': 'SPDXRef-DOCUMENT',
184
216
  'name': 'SCANOSS-SBOM',
185
- 'creationInfo': {
186
- 'created': now.strftime('%Y-%m-%dT%H:%M:%SZ'),
187
- 'creators': [f'Tool: SCANOSS-PY: {__version__}', f'Person: {getpass.getuser()}']
188
- },
217
+ 'creationInfo': self._create_creation_info(now),
189
218
  'documentNamespace': f'https://spdx.org/spdxdocs/scanoss-py-{__version__}-{md5hex}',
190
219
  'documentDescribes': [],
191
220
  'hasExtractedLicensingInfos': [],
192
- 'packages': []
221
+ 'packages': [],
193
222
  }
194
- lic_refs = set() # Hash Set of non-SPDX license references
195
- for purl in raw_data:
196
- comp = raw_data.get(purl)
197
- licenses = comp.get('licenses')
198
- lic_text = 'NOASSERTION'
199
- if licenses:
200
- lic_set = set()
201
- for lic in licenses:
202
- lc_id = lic.get('id')
203
- if lc_id:
204
- spdx_id = self.get_spdx_license_id(lc_id)
205
- if not spdx_id:
206
- if not lc_id.startswith('LicenseRef'):
207
- lc_id = f'LicenseRef-{lc_id}' # Make sure it has a license ref in its name
208
- lic_refs.add(lc_id) # save non-SPDX license for later reference
209
- lic_set.add(spdx_id if spdx_id else lc_id)
210
- if len(lic_set) > 0:
211
- lic_text = ' AND '.join(lic_set)
212
- if len(lic_set) > 1:
213
- lic_text = f'({lic_text})' # wrap the names in () if there is more than one
214
- comp_name = comp.get('component')
215
- comp_ver = comp.get('version')
216
- purl_ver = f'{purl}@{comp_ver}'
217
- vendor = comp.get('vendor', 'NOASSERTION')
218
- supplier = f"Organization: {vendor}" if vendor != 'NOASSERTION' else vendor
219
- purl_hash = hashlib.md5(f'{purl_ver}'.encode('utf-8')).hexdigest()
220
- purl_spdx = f'SPDXRef-{purl_hash}'
221
- data['documentDescribes'].append(purl_spdx)
222
- data['packages'].append({
223
- 'name': comp_name,
224
- 'SPDXID': purl_spdx,
225
- 'versionInfo': comp_ver,
226
- 'downloadLocation': 'NOASSERTION', # TODO Add actual download location
227
- 'homepage': comp.get('url', ''),
228
- 'licenseDeclared': lic_text,
229
- 'licenseConcluded': 'NOASSERTION',
230
- 'filesAnalyzed': False,
231
- 'copyrightText': 'NOASSERTION',
232
- 'supplier': supplier,
233
- 'externalRefs': [{
223
+
224
+ def _create_creation_info(self, timestamp: datetime.datetime) -> dict:
225
+ """Create the creation info section."""
226
+ return {
227
+ 'created': timestamp.strftime('%Y-%m-%dT%H:%M:%SZ'),
228
+ 'creators': [
229
+ f'Tool: SCANOSS-PY: {__version__}',
230
+ f'Person: {getpass.getuser()}',
231
+ 'Organization: SCANOSS'
232
+ ],
233
+ 'comment': 'SBOM Build information - SBOM Type: Build',
234
+ }
235
+
236
+ def _process_packages(self, raw_data: dict, spdx_document: dict):
237
+ """Process packages and add them to the SPDX document."""
238
+ lic_refs = set()
239
+
240
+ for purl, comp in raw_data.items():
241
+ package_info = self._create_package_info(purl, comp, lic_refs)
242
+ spdx_document['packages'].append(package_info)
243
+ spdx_document['documentDescribes'].append(package_info['SPDXID'])
244
+
245
+ self._process_license_refs(lic_refs, spdx_document)
246
+
247
+ def _create_package_info(self, purl: str, comp: dict, lic_refs: set) -> dict:
248
+ """Create package information for SPDX document."""
249
+ lic_text = self._process_package_licenses(comp.get('licenses', []), lic_refs)
250
+ comp_ver = comp.get('version')
251
+ purl_ver = f'{purl}@{comp_ver}'
252
+ purl_hash = hashlib.md5(purl_ver.encode('utf-8')).hexdigest()
253
+
254
+ return {
255
+ 'name': comp.get('component'),
256
+ 'SPDXID': f'SPDXRef-{purl_hash}',
257
+ 'versionInfo': comp_ver,
258
+ 'downloadLocation': comp.get('download_url') or comp.get('url'),
259
+ 'homepage': comp.get('url', ''),
260
+ 'licenseDeclared': lic_text,
261
+ 'licenseConcluded': 'NOASSERTION',
262
+ 'filesAnalyzed': False,
263
+ 'copyrightText': 'NOASSERTION',
264
+ 'supplier': f'Organization: {comp.get("vendor", "NOASSERTION")}',
265
+ 'externalRefs': [
266
+ {
234
267
  'referenceCategory': 'PACKAGE-MANAGER',
235
268
  'referenceLocator': purl_ver,
236
269
  'referenceType': 'purl'
237
- }]
238
- })
239
- # End purls for loop
240
- for lic_ref in lic_refs: # Insert all the non-SPDX license references
270
+ }
271
+ ],
272
+ 'checksums': [
273
+ {
274
+ 'algorithm': 'MD5',
275
+ 'checksumValue': comp.get('url_hash') or '0' * 32
276
+ }
277
+ ],
278
+ }
279
+
280
+ def _process_package_licenses(self, licenses: list, lic_refs: set) -> str:
281
+ """Process licenses and return license text."""
282
+ if not licenses:
283
+ return 'NOASSERTION'
284
+
285
+ lic_set = set()
286
+ for lic in licenses:
287
+ lc_id = lic.get('id')
288
+ if lc_id:
289
+ self._process_license_id(lc_id, lic_refs, lic_set)
290
+
291
+ return self._format_license_text(lic_set)
292
+
293
+ def _process_license_id(self, lc_id: str, lic_refs: set, lic_set: set):
294
+ """Process individual license ID."""
295
+ spdx_id = self.get_spdx_license_id(lc_id)
296
+ if not spdx_id:
297
+ if not lc_id.startswith('LicenseRef'):
298
+ lc_id = f'LicenseRef-{lc_id}'
299
+ lic_refs.add(lc_id)
300
+ lic_set.add(spdx_id if spdx_id else lc_id)
301
+
302
+ def _format_license_text(self, lic_set: set) -> str:
303
+ """Format the license text with proper syntax."""
304
+ if not lic_set:
305
+ return 'NOASSERTION'
306
+
307
+ lic_text = ' AND '.join(lic_set)
308
+ if len(lic_set) > 1:
309
+ lic_text = f'({lic_text})'
310
+ return lic_text
311
+
312
+ def _process_license_refs(self, lic_refs: set, spdx_document: dict):
313
+ """Process and add license references to the document."""
314
+ for lic_ref in lic_refs:
315
+ license_info = self._parse_license_ref(lic_ref)
316
+ spdx_document['hasExtractedLicensingInfos'].append(license_info)
317
+
318
+ def _parse_license_ref(self, lic_ref: str) -> dict:
319
+ """Parse license reference and create info dictionary."""
320
+ source, name = self._extract_license_info(lic_ref)
321
+ source_text = f' by {source}.' if source else '.'
322
+
323
+ return {
324
+ 'licenseId': lic_ref,
325
+ 'name': name.replace('-', ' '),
326
+ 'extractedText': 'Detected license, please review component source code.',
327
+ 'comment': f'Detected license{source_text}',
328
+ }
329
+
330
+ def _extract_license_info(self, lic_ref: str):
331
+ """Extract source and name from license reference."""
332
+ match = re.search(r'^LicenseRef-(scancode-|scanoss-|)(\S+)$', lic_ref, re.IGNORECASE)
333
+ if match:
334
+ source = match.group(1).replace('-', '')
335
+ name = match.group(2)
336
+ else:
241
337
  source = ''
242
- match = re.search(r'^LicenseRef-(scancode-|scanoss-|)(\S+)$', lic_ref, re.IGNORECASE)
243
- if match:
244
- source = match.group(1).replace('-', '') # source for the custom license
245
- name = match.group(2) # license name (without references, etc.)
246
- else:
247
- name = lic_ref
248
- name = name.replace('-', ' ')
249
- source = f' by {source}.' if source else '.'
250
- data['hasExtractedLicensingInfos'].append({
251
- 'licenseId': lic_ref,
252
- 'name': name,
253
- 'extractedText': 'Detected license, please review component source code.',
254
- 'comment': f'Detected license{source}'
255
- })
256
- # End license refs for loop
257
- file = sys.stdout
338
+ name = lic_ref
339
+ return source, name
340
+
341
+ def _write_output(self, data: dict, output_file: str = None) -> bool:
342
+ """Write the SPDX document to output."""
343
+ try:
344
+ file = self._get_output_file(output_file)
345
+ print(json.dumps(data, indent=2), file=file)
346
+ if output_file:
347
+ file.close()
348
+ return True
349
+ except Exception as e:
350
+ self.print_stderr(f'Error writing output: {str(e)}')
351
+ return False
352
+
353
+ def _get_output_file(self, output_file: str = None):
354
+ """Get the appropriate output file handle."""
258
355
  if not output_file and self.output_file:
259
356
  output_file = self.output_file
260
- if output_file:
261
- file = open(output_file, 'w')
262
- print(json.dumps(data, indent=2), file=file)
263
- if output_file:
264
- file.close()
265
- return True
357
+ return open(output_file, 'w') if output_file else sys.stdout
266
358
 
267
359
  def produce_from_str(self, json_str: str, output_file: str = None) -> bool:
268
360
  """
@@ -352,6 +444,8 @@ class SpdxLite:
352
444
  return lic_id
353
445
  self.print_debug(f'Warning: Failed to find valid SPDX license identifier for: {lic_name}')
354
446
  return None
447
+
448
+
355
449
  #
356
450
  # End of SpdxLite Class
357
451
  #