scanoss 1.38.0__py3-none-any.whl → 1.40.0__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.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +137 -2
- scanoss/data/build_date.txt +1 -1
- scanoss/gitlabqualityreport.py +185 -0
- scanoss/inspection/dependency_track/project_violation.py +3 -2
- scanoss/inspection/policy_check.py +0 -42
- scanoss/inspection/raw/__init__.py +0 -0
- scanoss/inspection/raw/component_summary.py +53 -2
- scanoss/inspection/raw/copyleft.py +3 -2
- scanoss/inspection/raw/license_summary.py +49 -1
- scanoss/inspection/raw/match_summary.py +290 -0
- scanoss/inspection/raw/raw_base.py +5 -10
- scanoss/inspection/raw/undeclared_component.py +3 -2
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/scanners/folder_hasher.py +1 -0
- scanoss/scanners/scanner_hfh.py +3 -1
- scanoss/scanossgrpc.py +5 -2
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/METADATA +1 -1
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/RECORD +25 -19
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/WHEEL +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.40.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, 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
|
+
"""
|
|
24
|
+
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
|
|
27
|
+
from ...scanossbase import ScanossBase
|
|
28
|
+
from ...utils import scanoss_scan_results_utils
|
|
29
|
+
from ..utils.file_utils import load_json_file
|
|
30
|
+
from ..utils.markdown_utils import generate_table
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class MatchSummaryItem:
|
|
35
|
+
"""
|
|
36
|
+
Represents a single match entry in the SCANOSS results.
|
|
37
|
+
|
|
38
|
+
This data class encapsulates all the relevant information about a component
|
|
39
|
+
match found during scanning, including file location, license details, and
|
|
40
|
+
match quality metrics.
|
|
41
|
+
"""
|
|
42
|
+
file: str
|
|
43
|
+
file_url: str
|
|
44
|
+
license: str
|
|
45
|
+
similarity: str
|
|
46
|
+
purl: str
|
|
47
|
+
purl_url: str
|
|
48
|
+
version: str
|
|
49
|
+
lines: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ComponentMatchSummary:
|
|
54
|
+
"""
|
|
55
|
+
Container for categorized SCANOSS match results.
|
|
56
|
+
|
|
57
|
+
Organizes matches into two categories: full file matches and snippet matches.
|
|
58
|
+
This separation allows for different presentation and analysis of match types.
|
|
59
|
+
"""
|
|
60
|
+
files: list[MatchSummaryItem]
|
|
61
|
+
snippet: list[MatchSummaryItem]
|
|
62
|
+
|
|
63
|
+
class MatchSummary(ScanossBase):
|
|
64
|
+
"""
|
|
65
|
+
Generates Markdown summaries from SCANOSS scan results.
|
|
66
|
+
|
|
67
|
+
This class processes SCANOSS scan results and creates human-readable Markdown
|
|
68
|
+
reports with collapsible sections for file and snippet matches. The reports
|
|
69
|
+
include clickable links to files when a line range
|
|
70
|
+
prefix is provided.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__( # noqa: PLR0913
|
|
74
|
+
self,
|
|
75
|
+
debug: bool = False,
|
|
76
|
+
trace: bool = False,
|
|
77
|
+
quiet: bool = False,
|
|
78
|
+
line_range_prefix: str = None,
|
|
79
|
+
scanoss_results_path: str = None,
|
|
80
|
+
output: str = None,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
Initialize the Matches Summary generator.
|
|
84
|
+
|
|
85
|
+
:param debug: Enable debug output for troubleshooting
|
|
86
|
+
:param trace: Enable trace-level logging for detailed execution tracking
|
|
87
|
+
:param quiet: Suppress informational messages
|
|
88
|
+
:param line_range_prefix: Base URL prefix for GitLab file links with line ranges
|
|
89
|
+
(e.g., 'https://gitlab.com/org/project/-/blob/main')
|
|
90
|
+
:param scanoss_results_path: Path to SCANOSS scan results file in JSON format
|
|
91
|
+
:param output: Output file path for the generated Markdown report (default: stdout)
|
|
92
|
+
"""
|
|
93
|
+
super().__init__(debug=debug, trace=trace, quiet=quiet)
|
|
94
|
+
self.scanoss_results_path = scanoss_results_path
|
|
95
|
+
self.line_range_prefix = line_range_prefix
|
|
96
|
+
self.output = output
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_match_summary_item(self, file_name: str, result: dict) -> MatchSummaryItem:
|
|
100
|
+
"""
|
|
101
|
+
Create a MatchSummaryItem from a single scan result.
|
|
102
|
+
|
|
103
|
+
Processes a SCANOSS scan result and creates a MatchSummaryItem with appropriate
|
|
104
|
+
file URLs, license information, and line ranges. Handles both snippet matches
|
|
105
|
+
(with specific line ranges) and file matches (entire file).
|
|
106
|
+
|
|
107
|
+
:param file_name: Name of the scanned file (relative path in the repository)
|
|
108
|
+
:param result: SCANOSS scan result dictionary containing match details
|
|
109
|
+
:return: Populated match summary item with all relevant information
|
|
110
|
+
"""
|
|
111
|
+
if result.get('id') == "snippet":
|
|
112
|
+
# Snippet match: create URL with line range anchor
|
|
113
|
+
lines = scanoss_scan_results_utils.get_lines(result.get('lines'))
|
|
114
|
+
end_line = lines[len(lines) - 1] if len(lines) > 1 else lines[0]
|
|
115
|
+
file_url = f"{self.line_range_prefix}/{file_name}#L{lines[0]}-L{end_line}"
|
|
116
|
+
return MatchSummaryItem(
|
|
117
|
+
file_url=file_url,
|
|
118
|
+
file=file_name,
|
|
119
|
+
license=result.get('licenses')[0].get('name'),
|
|
120
|
+
similarity=result.get('matched'),
|
|
121
|
+
purl=result.get('purl')[0],
|
|
122
|
+
purl_url=result.get('url'),
|
|
123
|
+
version=result.get('version'),
|
|
124
|
+
lines=f"{lines[0]}-{lines[len(lines) - 1] if len(lines) > 1 else lines[0]}"
|
|
125
|
+
)
|
|
126
|
+
# File match: create URL without line range
|
|
127
|
+
return MatchSummaryItem(
|
|
128
|
+
file=file_name,
|
|
129
|
+
file_url=f"{self.line_range_prefix}/{file_name}",
|
|
130
|
+
license=result.get('licenses')[0].get('name'),
|
|
131
|
+
similarity=result.get('matched'),
|
|
132
|
+
purl=result.get('purl')[0],
|
|
133
|
+
purl_url=result.get('url'),
|
|
134
|
+
version=result.get('version'),
|
|
135
|
+
lines="all"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _validate_result(self, file_name: str, result: dict) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Validate that a scan result has all required fields.
|
|
141
|
+
|
|
142
|
+
:param file_name: Name of the file being validated
|
|
143
|
+
:param result: The scan result to validate
|
|
144
|
+
:return: True if valid, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
validations = [
|
|
147
|
+
('id', 'No id found'),
|
|
148
|
+
('lines', 'No lines found'),
|
|
149
|
+
('purl', 'No purl found'),
|
|
150
|
+
('licenses', 'No licenses found'),
|
|
151
|
+
('version', 'No version found'),
|
|
152
|
+
('matched', 'No matched found'),
|
|
153
|
+
('url', 'No url found'),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
for field, error_msg in validations:
|
|
157
|
+
if not result.get(field):
|
|
158
|
+
self.print_debug(f'ERROR: {error_msg} for file {file_name}')
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
# Additional validation for non-empty lists
|
|
162
|
+
if len(result.get('purl')) == 0:
|
|
163
|
+
self.print_debug(f'ERROR: No purl found for file {file_name}')
|
|
164
|
+
return False
|
|
165
|
+
if len(result.get('licenses')) == 0:
|
|
166
|
+
self.print_debug(f'ERROR: Empty licenses list for file {file_name}')
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
def _get_matches_summary(self) -> ComponentMatchSummary:
|
|
172
|
+
"""
|
|
173
|
+
Parse SCANOSS scan results and create categorized match summaries.
|
|
174
|
+
|
|
175
|
+
Loads the SCANOSS scan results file and processes each match, validating
|
|
176
|
+
required fields and categorizing matches into file matches and snippet matches.
|
|
177
|
+
Skips invalid or incomplete results with debug messages.
|
|
178
|
+
"""
|
|
179
|
+
# Load scan results from JSON file
|
|
180
|
+
scan_results = load_json_file(self.scanoss_results_path)
|
|
181
|
+
gitlab_matches_summary = ComponentMatchSummary(files=[], snippet=[])
|
|
182
|
+
|
|
183
|
+
# Process each file and its results
|
|
184
|
+
for file_name, results in scan_results.items():
|
|
185
|
+
for result in results:
|
|
186
|
+
# Skip non-matches
|
|
187
|
+
if result.get('id') == "none":
|
|
188
|
+
self.print_debug(f'Skipping non-match for file {file_name}')
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
# Validate required fields
|
|
192
|
+
if not self._validate_result(file_name, result):
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
# Create summary item and categorize by match type
|
|
196
|
+
summary_item = self._get_match_summary_item(file_name, result)
|
|
197
|
+
if result.get('id') == "snippet":
|
|
198
|
+
gitlab_matches_summary.snippet.append(summary_item)
|
|
199
|
+
else:
|
|
200
|
+
gitlab_matches_summary.files.append(summary_item)
|
|
201
|
+
|
|
202
|
+
return gitlab_matches_summary
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _markdown(self, gitlab_matches_summary: ComponentMatchSummary) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Generate Markdown from match summaries.
|
|
208
|
+
|
|
209
|
+
Creates a formatted Markdown document with collapsible sections for file
|
|
210
|
+
and snippet matches.
|
|
211
|
+
|
|
212
|
+
:param gitlab_matches_summary: Container with categorized file and snippet matches to format
|
|
213
|
+
:return: Complete Markdown document with formatted match tables
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if len(gitlab_matches_summary.files) == 0 and len(gitlab_matches_summary.snippet) == 0:
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
# Define table headers
|
|
220
|
+
file_match_headers = ['File', 'License', 'Similarity', 'PURL', 'Version']
|
|
221
|
+
snippet_match_headers = ['File', 'License', 'Similarity', 'PURL', 'Version', 'Lines']
|
|
222
|
+
# Build file matches table
|
|
223
|
+
file_match_rows = []
|
|
224
|
+
for file_match in gitlab_matches_summary.files:
|
|
225
|
+
row = [
|
|
226
|
+
f"[{file_match.file}]({file_match.file_url})",
|
|
227
|
+
file_match.license,
|
|
228
|
+
file_match.similarity,
|
|
229
|
+
f"[{file_match.purl}]({file_match.purl_url})",
|
|
230
|
+
file_match.version,
|
|
231
|
+
]
|
|
232
|
+
file_match_rows.append(row)
|
|
233
|
+
file_match_table = generate_table(file_match_headers, file_match_rows)
|
|
234
|
+
|
|
235
|
+
# Build snippet matches table
|
|
236
|
+
snippet_match_rows = []
|
|
237
|
+
for snippet_match in gitlab_matches_summary.snippet:
|
|
238
|
+
row = [
|
|
239
|
+
f"[{snippet_match.file}]({snippet_match.file_url})",
|
|
240
|
+
snippet_match.license,
|
|
241
|
+
snippet_match.similarity,
|
|
242
|
+
f"[{snippet_match.purl}]({snippet_match.purl_url})",
|
|
243
|
+
snippet_match.version,
|
|
244
|
+
snippet_match.lines
|
|
245
|
+
]
|
|
246
|
+
snippet_match_rows.append(row)
|
|
247
|
+
snippet_match_table = generate_table(snippet_match_headers, snippet_match_rows)
|
|
248
|
+
|
|
249
|
+
# Assemble complete Markdown document
|
|
250
|
+
markdown = ""
|
|
251
|
+
markdown += "### SCANOSS Match Summary\n\n"
|
|
252
|
+
|
|
253
|
+
# File matches section (collapsible)
|
|
254
|
+
markdown += "<details>\n"
|
|
255
|
+
markdown += "<summary>File Match Summary</summary>\n\n"
|
|
256
|
+
markdown += file_match_table
|
|
257
|
+
markdown += "\n</details>\n"
|
|
258
|
+
|
|
259
|
+
# Snippet matches section (collapsible)
|
|
260
|
+
markdown += "<details>\n"
|
|
261
|
+
markdown += "<summary>Snippet Match Summary</summary>\n\n"
|
|
262
|
+
markdown += snippet_match_table
|
|
263
|
+
markdown += "\n</details>\n"
|
|
264
|
+
|
|
265
|
+
return markdown
|
|
266
|
+
|
|
267
|
+
def run(self):
|
|
268
|
+
"""
|
|
269
|
+
Execute the matches summary generation process.
|
|
270
|
+
|
|
271
|
+
This is the main entry point for generating the matches summary report.
|
|
272
|
+
It orchestrates the entire workflow:
|
|
273
|
+
1. Loads and parses SCANOSS scan results
|
|
274
|
+
2. Validates and categorizes matches
|
|
275
|
+
3. Generates Markdown report
|
|
276
|
+
4. Outputs to file or stdout
|
|
277
|
+
"""
|
|
278
|
+
# Load and process scan results into categorized matches
|
|
279
|
+
matches = self._get_matches_summary()
|
|
280
|
+
|
|
281
|
+
# Format matches as GitLab-compatible Markdown
|
|
282
|
+
matches_md = self._markdown(matches)
|
|
283
|
+
if matches_md == "":
|
|
284
|
+
self.print_stdout("No matches found.")
|
|
285
|
+
return
|
|
286
|
+
# Output to file or stdout
|
|
287
|
+
self.print_to_file_or_stdout(matches_md, self.output)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
@@ -22,13 +22,12 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
import json
|
|
26
|
-
import os.path
|
|
27
25
|
from abc import abstractmethod
|
|
28
26
|
from enum import Enum
|
|
29
27
|
from typing import Any, Dict, TypeVar
|
|
30
28
|
|
|
31
29
|
from ..policy_check import PolicyCheck
|
|
30
|
+
from ..utils.file_utils import load_json_file
|
|
32
31
|
from ..utils.license_utils import LicenseUtil
|
|
33
32
|
|
|
34
33
|
|
|
@@ -313,15 +312,11 @@ class RawBase(PolicyCheck[T]):
|
|
|
313
312
|
Returns:
|
|
314
313
|
Dict[str, Any]: The parsed JSON data
|
|
315
314
|
"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
with open(self.filepath, 'r') as jsonfile:
|
|
320
|
-
try:
|
|
321
|
-
return json.load(jsonfile)
|
|
322
|
-
except Exception as e:
|
|
315
|
+
try:
|
|
316
|
+
return load_json_file(self.filepath)
|
|
317
|
+
except Exception as e:
|
|
323
318
|
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
324
|
-
|
|
319
|
+
return None
|
|
325
320
|
|
|
326
321
|
def _convert_components_to_list(self, components: dict):
|
|
327
322
|
if components is None:
|
|
@@ -27,6 +27,7 @@ from dataclasses import dataclass
|
|
|
27
27
|
from typing import Any, Dict, List
|
|
28
28
|
|
|
29
29
|
from ..policy_check import PolicyStatus
|
|
30
|
+
from ..utils.markdown_utils import generate_jira_table, generate_table
|
|
30
31
|
from .raw_base import RawBase
|
|
31
32
|
|
|
32
33
|
|
|
@@ -193,7 +194,7 @@ class UndeclaredComponent(RawBase[Component]):
|
|
|
193
194
|
for component in component_licenses:
|
|
194
195
|
rows.append([component.get('purl'), component.get('spdxid')])
|
|
195
196
|
return {
|
|
196
|
-
'details': f'### Undeclared components\n{
|
|
197
|
+
'details': f'### Undeclared components\n{generate_table(headers, rows)}\n',
|
|
197
198
|
'summary': self._get_summary(component_licenses),
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -211,7 +212,7 @@ class UndeclaredComponent(RawBase[Component]):
|
|
|
211
212
|
for component in component_licenses:
|
|
212
213
|
rows.append([component.get('purl'), component.get('spdxid')])
|
|
213
214
|
return {
|
|
214
|
-
'details': f'{
|
|
215
|
+
'details': f'{generate_jira_table(headers, rows)}',
|
|
215
216
|
'summary': self._get_jira_summary(component_licenses),
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, 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
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_json_file(file_path: str) -> dict:
|
|
30
|
+
"""
|
|
31
|
+
Load the file
|
|
32
|
+
|
|
33
|
+
:param file_path: file path to the JSON file
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dict[str, Any]: The parsed JSON data
|
|
37
|
+
"""
|
|
38
|
+
if not os.path.exists(file_path):
|
|
39
|
+
raise ValueError(f'The file "{file_path}" does not exist.')
|
|
40
|
+
with open(file_path, 'r') as jsonfile:
|
|
41
|
+
try:
|
|
42
|
+
return json.load(jsonfile)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
raise ValueError(f'ERROR: Problem parsing input JSON: {e}')
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, 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
|
+
"""
|
|
24
|
+
|
|
25
|
+
def generate_table(headers, rows, centered_columns=None):
|
|
26
|
+
"""
|
|
27
|
+
Generate a Markdown table.
|
|
28
|
+
|
|
29
|
+
:param headers: List of headers for the table.
|
|
30
|
+
:param rows: List of rows for the table.
|
|
31
|
+
:param centered_columns: List of column indices to be centered.
|
|
32
|
+
:return: A string representing the Markdown table.
|
|
33
|
+
"""
|
|
34
|
+
col_sep = ' | '
|
|
35
|
+
centered_column_set = set(centered_columns or [])
|
|
36
|
+
if headers is None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Decide which separator to use
|
|
40
|
+
def create_separator(index):
|
|
41
|
+
if centered_columns is None:
|
|
42
|
+
return '-'
|
|
43
|
+
return ':-:' if index in centered_column_set else '-'
|
|
44
|
+
|
|
45
|
+
# Build the row separator
|
|
46
|
+
row_separator = col_sep + col_sep.join(create_separator(index) for index, _ in enumerate(headers)) + col_sep
|
|
47
|
+
# build table rows
|
|
48
|
+
table_rows = [col_sep + col_sep.join(headers) + col_sep, row_separator]
|
|
49
|
+
table_rows.extend(col_sep + col_sep.join(row) + col_sep for row in rows)
|
|
50
|
+
return '\n'.join(table_rows)
|
|
51
|
+
|
|
52
|
+
def generate_jira_table(headers, rows, centered_columns=None):
|
|
53
|
+
col_sep = '*|*'
|
|
54
|
+
if headers is None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
table_header = '|*' + col_sep.join(headers) + '*|\n'
|
|
58
|
+
table = table_header
|
|
59
|
+
for row in rows:
|
|
60
|
+
if len(headers) == len(row):
|
|
61
|
+
table += '|' + '|'.join(row) + '|\n'
|
|
62
|
+
|
|
63
|
+
return table
|
|
@@ -158,6 +158,7 @@ class FolderHasher:
|
|
|
158
158
|
filtered_files.sort()
|
|
159
159
|
|
|
160
160
|
bar = Bar('Hashing files...', max=len(filtered_files))
|
|
161
|
+
full_file_path = ''
|
|
161
162
|
for file_path in filtered_files:
|
|
162
163
|
try:
|
|
163
164
|
file_path_obj = Path(file_path) if isinstance(file_path, str) else file_path
|
scanoss/scanners/scanner_hfh.py
CHANGED
|
@@ -63,6 +63,7 @@ class ScannerHFH:
|
|
|
63
63
|
depth: int = DEFAULT_HFH_DEPTH,
|
|
64
64
|
recursive_threshold: float = DEFAULT_HFH_RECURSIVE_THRESHOLD,
|
|
65
65
|
min_accepted_score: float = DEFAULT_HFH_MIN_ACCEPTED_SCORE,
|
|
66
|
+
use_grpc: bool = False,
|
|
66
67
|
):
|
|
67
68
|
"""
|
|
68
69
|
Initialize the ScannerHFH.
|
|
@@ -107,6 +108,7 @@ class ScannerHFH:
|
|
|
107
108
|
self.rank_threshold = rank_threshold
|
|
108
109
|
self.recursive_threshold = recursive_threshold
|
|
109
110
|
self.min_accepted_score = min_accepted_score
|
|
111
|
+
self.use_grpc = use_grpc
|
|
110
112
|
|
|
111
113
|
def scan(self) -> Optional[Dict]:
|
|
112
114
|
"""
|
|
@@ -134,7 +136,7 @@ class ScannerHFH:
|
|
|
134
136
|
spinner_thread.start()
|
|
135
137
|
|
|
136
138
|
try:
|
|
137
|
-
response = self.client.folder_hash_scan(hfh_request)
|
|
139
|
+
response = self.client.folder_hash_scan(hfh_request, self.use_grpc)
|
|
138
140
|
if response:
|
|
139
141
|
self.scan_results = response
|
|
140
142
|
finally:
|
scanoss/scanossgrpc.py
CHANGED
|
@@ -426,21 +426,24 @@ class ScanossGrpc(ScanossBase):
|
|
|
426
426
|
use_grpc=use_grpc,
|
|
427
427
|
)
|
|
428
428
|
|
|
429
|
-
def folder_hash_scan(self, request: Dict) -> Optional[Dict]:
|
|
429
|
+
def folder_hash_scan(self, request: Dict, use_grpc: Optional[bool] = None) -> Optional[Dict]:
|
|
430
430
|
"""
|
|
431
431
|
Client function to call the rpc for Folder Hashing Scan
|
|
432
432
|
|
|
433
433
|
Args:
|
|
434
434
|
request (Dict): Folder Hash Request
|
|
435
|
+
use_grpc (Optional[bool]): Whether to use gRPC or REST API
|
|
435
436
|
|
|
436
437
|
Returns:
|
|
437
438
|
Optional[Dict]: Folder Hash Response, or None if the request was not succesfull
|
|
438
439
|
"""
|
|
439
|
-
return self.
|
|
440
|
+
return self._call_api(
|
|
441
|
+
'scanning.FolderHashScan',
|
|
440
442
|
self.scanning_stub.FolderHashScan,
|
|
441
443
|
request,
|
|
442
444
|
HFHRequest,
|
|
443
445
|
'Sending folder hash scan data (rqId: {rqId})...',
|
|
446
|
+
use_grpc=use_grpc,
|
|
444
447
|
)
|
|
445
448
|
|
|
446
449
|
def _call_api(
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, 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
|
+
"""
|
|
24
|
+
|
|
25
|
+
def get_lines(lines: str) -> list:
|
|
26
|
+
"""
|
|
27
|
+
Parse line range string into a list of line numbers.
|
|
28
|
+
|
|
29
|
+
Converts SCANOSS line notation (e.g., '10-20,25-30') into a flat list
|
|
30
|
+
of individual line numbers for processing.
|
|
31
|
+
|
|
32
|
+
:param lines: Comma-separated line ranges in SCANOSS format (e.g., '10-20,25-30')
|
|
33
|
+
:return: Flat list of all line numbers extracted from the ranges
|
|
34
|
+
"""
|
|
35
|
+
lines_list = []
|
|
36
|
+
lines = lines.split(',')
|
|
37
|
+
for line in lines:
|
|
38
|
+
line_parts = line.split('-')
|
|
39
|
+
for part in line_parts:
|
|
40
|
+
lines_list.append(int(part))
|
|
41
|
+
return lines_list
|
|
@@ -6,8 +6,8 @@ protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=KZOW9Ciio-f9iL42FuLFnS
|
|
|
6
6
|
protoc_gen_swagger/options/openapiv2_pb2.py,sha256=w0xDs63uyrWGgzRaQZXfJpfI7Jpyvh-i9ay_uzOR-aM,16475
|
|
7
7
|
protoc_gen_swagger/options/openapiv2_pb2.pyi,sha256=hYOV6uQ2yqhP89042_V3GuAsvoBBiXf5CGuYmnFnfv4,54665
|
|
8
8
|
protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=sje9Nh3yE7CHCUWZwtjTgwsKB4GvyGz5vOrGTnRXJfc,917
|
|
9
|
-
scanoss/__init__.py,sha256=
|
|
10
|
-
scanoss/cli.py,sha256=
|
|
9
|
+
scanoss/__init__.py,sha256=6IaHKZDhDx8hxXCPX8B5ueipZk0jEM6bGl8Vn_6-az8,1146
|
|
10
|
+
scanoss/cli.py,sha256=fw0iCF4CuIEwy8BIyBlgxFBlVESV30kYGy1uS74sP0E,103298
|
|
11
11
|
scanoss/components.py,sha256=NFyt_w3aoMotr_ZaFU-ng00_89sruc0kgY7ERnJXkmM,15891
|
|
12
12
|
scanoss/constants.py,sha256=GHLTaLNVxXdTXRj7ngRK4u4S653pHzM8qFy4JFLa0wQ,450
|
|
13
13
|
scanoss/cryptography.py,sha256=lOoD_dW16ARQxYiYyb5R8S7gx0FqWIsnGkKfsB0nGaU,10627
|
|
@@ -16,13 +16,14 @@ scanoss/cyclonedx.py,sha256=mHeX66yQCk41N3YCIzKy_fI7fLqQnetYPFRIzUKy_M4,18416
|
|
|
16
16
|
scanoss/delta.py,sha256=slmgnD7SsUOmfSE2zb0zdRAGo-JcjPJAtxyzuCSzO3I,9455
|
|
17
17
|
scanoss/file_filters.py,sha256=QcLqunaBKQIafjNZ9_Snh9quBX5_-fsTusVmxwjC1q8,18511
|
|
18
18
|
scanoss/filecount.py,sha256=RZjKQ6M5P_RQg0_PMD2tsRe5Z8f98ke0sxYVjPDN8iQ,6538
|
|
19
|
+
scanoss/gitlabqualityreport.py,sha256=JNDT63gxTOxLONe4dxUzn1o7eVniK0B3j6SiFTRLinw,6790
|
|
19
20
|
scanoss/results.py,sha256=47ZXXuU2sDjYa5vhtbWTmikit9jHhA0rsYKwkvZFI5w,9252
|
|
20
21
|
scanoss/scancodedeps.py,sha256=JbpoGW1POtPMmowzfwa4oh8sSBeeQCqaW9onvc4UFYM,11517
|
|
21
22
|
scanoss/scanner.py,sha256=-RCxLX0EepUebK8jQKvlMxFEQrCc8SwEjxznoWjadkg,45510
|
|
22
23
|
scanoss/scanoss_settings.py,sha256=W8uFQ6uRIqtE-DXXA56bO8I4GsbJ-aA1c84hQ_qBel4,12161
|
|
23
24
|
scanoss/scanossapi.py,sha256=U3IytJXA0mQHYsylzjwCuFakuhD_dJhstY37NmyvtB8,13243
|
|
24
25
|
scanoss/scanossbase.py,sha256=Dkpwxa8NH8XN1iRl03NM_Mkvby0JQ4qfvCiiUrJ5ul0,3163
|
|
25
|
-
scanoss/scanossgrpc.py,sha256=
|
|
26
|
+
scanoss/scanossgrpc.py,sha256=9UuVPUjBLUhqim_tSntyoRZW-OAtiz5iP_VjjNr5RPY,41715
|
|
26
27
|
scanoss/scanpostprocessor.py,sha256=-JsThlxrU70r92GHykTMERnicdd-6jmwNsE4PH0MN2o,11063
|
|
27
28
|
scanoss/scantype.py,sha256=gFmyVmKQpHWogN2iCmMj032e_sZo4T92xS3_EH5B3Tc,1310
|
|
28
29
|
scanoss/spdxlite.py,sha256=4JMxmyNmvcL6fjScihk8toWfSuQ-Pj1gzaT3SIn1fXA,29425
|
|
@@ -64,35 +65,40 @@ scanoss/api/vulnerabilities/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSC
|
|
|
64
65
|
scanoss/api/vulnerabilities/v2/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSCHhIDMJT4r0,1122
|
|
65
66
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=pmm0MSiXkdf8e4rCIIDRcsNRixR2vGvD1Xak4l-wdwI,16550
|
|
66
67
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=BNxT5kUKQ-mgtOt5QYBM1Qrg5LNDqSpWKpfEZquIlsM,19127
|
|
67
|
-
scanoss/data/build_date.txt,sha256=
|
|
68
|
+
scanoss/data/build_date.txt,sha256=EsAP44xbRYCL6Rg23-wKXjV_HwKJKnffQXg6uFnmiCY,40
|
|
68
69
|
scanoss/data/scanoss-settings-schema.json,sha256=ClkRYAkjAN0Sk704G8BE_Ok006oQ6YnIGmX84CF8h9w,8798
|
|
69
70
|
scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
|
|
70
71
|
scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
|
|
71
72
|
scanoss/export/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
|
|
72
73
|
scanoss/export/dependency_track.py,sha256=A_xQH6_r9xL_fth1Wr770GCTRFVyn7XcUPfVUsXp4-w,9271
|
|
73
74
|
scanoss/inspection/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
|
|
74
|
-
scanoss/inspection/policy_check.py,sha256=
|
|
75
|
-
scanoss/inspection/dependency_track/project_violation.py,sha256=
|
|
76
|
-
scanoss/inspection/raw/
|
|
77
|
-
scanoss/inspection/raw/
|
|
78
|
-
scanoss/inspection/raw/
|
|
79
|
-
scanoss/inspection/raw/
|
|
80
|
-
scanoss/inspection/raw/
|
|
75
|
+
scanoss/inspection/policy_check.py,sha256=DtDZKeNe_WltF42BoiMbns4v8m7XhHVziqIc3RWzDEA,8171
|
|
76
|
+
scanoss/inspection/dependency_track/project_violation.py,sha256=x9PVk9mh5BU9Qr9WiDiRvwTTZxPzYFjzd11miZP-lpM,20654
|
|
77
|
+
scanoss/inspection/raw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
|
+
scanoss/inspection/raw/component_summary.py,sha256=axuzDxMdWva4HAM_i9D-NpqzPZKRd2Jp3oKq9R1oE88,6407
|
|
79
|
+
scanoss/inspection/raw/copyleft.py,sha256=ZOJ6FOvg2kx2pYOOCVza301h9c3ZB_jGNp-Lp4TTFcY,9385
|
|
80
|
+
scanoss/inspection/raw/license_summary.py,sha256=kW0mHztuAbOXI7G1-7ajzq7A8qhgSqANi0W6n-jOF5Y,7786
|
|
81
|
+
scanoss/inspection/raw/match_summary.py,sha256=3Mhp-H7OmMxhvSxR_opfl-xHB4UfDfTM-io5HsEH4co,11386
|
|
82
|
+
scanoss/inspection/raw/raw_base.py,sha256=mzdBs2dAaZlmYW9U8PnkvnjWnWyXWMkHzyhZ9FCM5Hc,18166
|
|
83
|
+
scanoss/inspection/raw/undeclared_component.py,sha256=mnSJr7RjUs2YT46xiBNkwx3Jrulo_8QtuagWAFL5KLo,11422
|
|
84
|
+
scanoss/inspection/utils/file_utils.py,sha256=b-xTH6FSyPpl3EPZ9WzK0c4734yE9mAexT1_YLLqymE,1641
|
|
81
85
|
scanoss/inspection/utils/license_utils.py,sha256=Zb6QLmVJb86lKCwZyBsmwakyAtY1SXa54kUyyKmWMqA,5093
|
|
86
|
+
scanoss/inspection/utils/markdown_utils.py,sha256=zkFs48TM-NR6nUHYOyQmHCwV82_fUsks5UB4BmyGifU,2446
|
|
82
87
|
scanoss/scanners/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
|
|
83
88
|
scanoss/scanners/container_scanner.py,sha256=fOrb64owrstX7LnTuxiIan059YgLeKXeBS6g2QaCyq0,16346
|
|
84
|
-
scanoss/scanners/folder_hasher.py,sha256=
|
|
89
|
+
scanoss/scanners/folder_hasher.py,sha256=PD1tghOrra3KtfsZJUbqKOmIBF-0Tg14FcBCKkqGUis,12873
|
|
85
90
|
scanoss/scanners/scanner_config.py,sha256=egG7cw3S2akU-D9M1aLE5jLrfz_c8e7_DIotMnnpM84,2601
|
|
86
|
-
scanoss/scanners/scanner_hfh.py,sha256=
|
|
91
|
+
scanoss/scanners/scanner_hfh.py,sha256=xrWxRB0SOq5FJbil3lXnBk6z_on7jxr2itPBj2kb0RA,9270
|
|
87
92
|
scanoss/services/dependency_track_service.py,sha256=JIpqev4I-x_ZajMxD5W2Y3OAUvEJ_4nstzAPV90vfP8,5070
|
|
88
93
|
scanoss/utils/__init__.py,sha256=0hjb5ktavp7utJzFhGMPImPaZiHWgilM2HwvTp5lXJE,1122
|
|
89
94
|
scanoss/utils/abstract_presenter.py,sha256=teiDTxBj5jBMCk2T8i4l1BJPf_u4zBLWrtCTFHSSECM,3148
|
|
90
95
|
scanoss/utils/crc64.py,sha256=TMrwQimSdE6imhFOUL7oAG6Kxu-8qMpGWMuMg8QpSVs,3169
|
|
91
96
|
scanoss/utils/file.py,sha256=62cA9a17TU9ZvfA3FY5HY4-QOajJeSrc8S6xLA_f-3M,2980
|
|
97
|
+
scanoss/utils/scanoss_scan_results_utils.py,sha256=ho9-DKefHFJlVZkw4gXOmMI-mgPIbV9Y2ftkI83fC1k,1727
|
|
92
98
|
scanoss/utils/simhash.py,sha256=6iu8DOcecPAY36SZjCOzrrLMT9oIE7-gI6QuYwUQ7B0,5793
|
|
93
|
-
scanoss-1.
|
|
94
|
-
scanoss-1.
|
|
95
|
-
scanoss-1.
|
|
96
|
-
scanoss-1.
|
|
97
|
-
scanoss-1.
|
|
98
|
-
scanoss-1.
|
|
99
|
+
scanoss-1.40.0.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
|
|
100
|
+
scanoss-1.40.0.dist-info/METADATA,sha256=ZX_JjQXpdybrkeUWvaqxN8UhZyVs3-zlByU3UWkcRAU,6181
|
|
101
|
+
scanoss-1.40.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
102
|
+
scanoss-1.40.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
|
|
103
|
+
scanoss-1.40.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
|
|
104
|
+
scanoss-1.40.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|