scanoss 1.38.0__py3-none-any.whl → 1.39.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 +139 -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/utils/scanoss_scan_results_utils.py +41 -0
- {scanoss-1.38.0.dist-info → scanoss-1.39.0.dist-info}/METADATA +1 -1
- {scanoss-1.38.0.dist-info → scanoss-1.39.0.dist-info}/RECORD +23 -17
- {scanoss-1.38.0.dist-info → scanoss-1.39.0.dist-info}/WHEEL +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.39.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.39.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.38.0.dist-info → scanoss-1.39.0.dist-info}/top_level.txt +0 -0
scanoss/__init__.py
CHANGED
scanoss/cli.py
CHANGED
|
@@ -40,6 +40,7 @@ from scanoss.inspection.dependency_track.project_violation import (
|
|
|
40
40
|
)
|
|
41
41
|
from scanoss.inspection.raw.component_summary import ComponentSummary
|
|
42
42
|
from scanoss.inspection.raw.license_summary import LicenseSummary
|
|
43
|
+
from scanoss.inspection.raw.match_summary import MatchSummary
|
|
43
44
|
from scanoss.scanners.container_scanner import (
|
|
44
45
|
DEFAULT_SYFT_COMMAND,
|
|
45
46
|
DEFAULT_SYFT_TIMEOUT,
|
|
@@ -73,6 +74,7 @@ from .constants import (
|
|
|
73
74
|
from .csvoutput import CsvOutput
|
|
74
75
|
from .cyclonedx import CycloneDx
|
|
75
76
|
from .filecount import FileCount
|
|
77
|
+
from .gitlabqualityreport import GitLabQualityReport
|
|
76
78
|
from .inspection.raw.copyleft import Copyleft
|
|
77
79
|
from .inspection.raw.undeclared_component import UndeclaredComponent
|
|
78
80
|
from .results import Results
|
|
@@ -283,7 +285,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
283
285
|
'--format',
|
|
284
286
|
'-f',
|
|
285
287
|
type=str,
|
|
286
|
-
choices=['cyclonedx', 'spdxlite', 'csv'],
|
|
288
|
+
choices=['cyclonedx', 'spdxlite', 'csv', 'glc-codequality'],
|
|
287
289
|
default='spdxlite',
|
|
288
290
|
help='Output format (optional - default: spdxlite)',
|
|
289
291
|
)
|
|
@@ -794,6 +796,66 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
794
796
|
help='Timeout (in seconds) for API communication (optional - default 300 sec)',
|
|
795
797
|
)
|
|
796
798
|
|
|
799
|
+
|
|
800
|
+
# ==============================================================================
|
|
801
|
+
# GitLab Integration Parser
|
|
802
|
+
# ==============================================================================
|
|
803
|
+
# Main parser for GitLab-specific inspection commands and report generation
|
|
804
|
+
p_gitlab_sub = p_inspect_sub.add_parser(
|
|
805
|
+
'gitlab',
|
|
806
|
+
aliases=['glc'],
|
|
807
|
+
description='Generate GitLab-compatible reports from SCANOSS scan results (Markdown summaries)',
|
|
808
|
+
help='Generate GitLab integration reports',
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# GitLab sub-commands parser
|
|
812
|
+
# Provides access to different GitLab report formats and inspection tools
|
|
813
|
+
p_gitlab_sub_parser = p_gitlab_sub.add_subparsers(
|
|
814
|
+
title='GitLab Report Types',
|
|
815
|
+
dest='subparser_subcmd',
|
|
816
|
+
description='Available GitLab report formats for scan result analysis',
|
|
817
|
+
help='Select the type of GitLab report to generate',
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
# ==============================================================================
|
|
821
|
+
# GitLab Matches Summary Command
|
|
822
|
+
# ==============================================================================
|
|
823
|
+
# Analyzes scan results and generates a GitLab-compatible Markdown summary
|
|
824
|
+
p_gl_inspect_matches = p_gitlab_sub_parser.add_parser(
|
|
825
|
+
'matches',
|
|
826
|
+
aliases=['ms'],
|
|
827
|
+
description='Generate a Markdown summary report of scan matches for GitLab integration',
|
|
828
|
+
help='Generate Markdown summary report of scan matches',
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
# Input file argument - SCANOSS scan results in JSON format
|
|
832
|
+
p_gl_inspect_matches.add_argument(
|
|
833
|
+
'-i',
|
|
834
|
+
'--input',
|
|
835
|
+
required=True,
|
|
836
|
+
type=str,
|
|
837
|
+
help='Path to SCANOSS scan results file (JSON format) to analyze'
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Line range prefix for GitLab file navigation
|
|
841
|
+
# Enables clickable file references in the generated report that link to specific lines in GitLab
|
|
842
|
+
p_gl_inspect_matches.add_argument(
|
|
843
|
+
'-lpr',
|
|
844
|
+
'--line-range-prefix',
|
|
845
|
+
required=True,
|
|
846
|
+
type=str,
|
|
847
|
+
help='Base URL prefix for GitLab file links with line ranges (e.g., https://gitlab.com/org/project/-/blob/main)'
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
# Output file argument - where to save the generated Markdown report
|
|
851
|
+
p_gl_inspect_matches.add_argument(
|
|
852
|
+
'--output',
|
|
853
|
+
'-o',
|
|
854
|
+
required=False,
|
|
855
|
+
type=str,
|
|
856
|
+
help='Output file path for the generated Markdown report (default: stdout)'
|
|
857
|
+
)
|
|
858
|
+
|
|
797
859
|
# TODO Move to the command call def location
|
|
798
860
|
# RAW results
|
|
799
861
|
p_inspect_raw_undeclared.set_defaults(func=inspect_undeclared)
|
|
@@ -807,6 +869,8 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
807
869
|
p_inspect_legacy_component_summary.set_defaults(func=inspect_component_summary)
|
|
808
870
|
# Dependency Track
|
|
809
871
|
p_inspect_dt_project_violation.set_defaults(func=inspect_dep_track_project_violations)
|
|
872
|
+
# GitLab
|
|
873
|
+
p_gl_inspect_matches.set_defaults(func=inspect_gitlab_matches)
|
|
810
874
|
|
|
811
875
|
# =========================================================================
|
|
812
876
|
# END INSPECT SUBCOMMAND CONFIGURATION
|
|
@@ -1153,6 +1217,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
1153
1217
|
p_inspect_legacy_license_summary,
|
|
1154
1218
|
p_inspect_legacy_component_summary,
|
|
1155
1219
|
p_inspect_dt_project_violation,
|
|
1220
|
+
p_gl_inspect_matches,
|
|
1156
1221
|
c_provenance,
|
|
1157
1222
|
p_folder_scan,
|
|
1158
1223
|
p_folder_hash,
|
|
@@ -1207,7 +1272,11 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
1207
1272
|
) and not args.subparsercmd:
|
|
1208
1273
|
parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
|
|
1209
1274
|
sys.exit(1)
|
|
1210
|
-
elif (
|
|
1275
|
+
elif (
|
|
1276
|
+
(args.subparser in 'inspect')
|
|
1277
|
+
and (args.subparsercmd in ('raw', 'dt', 'glc', 'gitlab'))
|
|
1278
|
+
and (args.subparser_subcmd is None)
|
|
1279
|
+
):
|
|
1211
1280
|
parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
|
|
1212
1281
|
sys.exit(1)
|
|
1213
1282
|
args.func(parser, args) # Execute the function associated with the sub-command
|
|
@@ -1628,6 +1697,11 @@ def convert(parser, args):
|
|
|
1628
1697
|
print_stderr('Producing CSV report...')
|
|
1629
1698
|
csvo = CsvOutput(debug=args.debug, output_file=args.output)
|
|
1630
1699
|
success = csvo.produce_from_file(args.input)
|
|
1700
|
+
elif args.format == 'glc-codequality':
|
|
1701
|
+
if not args.quiet:
|
|
1702
|
+
print_stderr('Producing GitLab code quality report...')
|
|
1703
|
+
glc_code_quality = GitLabQualityReport(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1704
|
+
success = glc_code_quality.produce_from_file(args.input, output_file=args.output)
|
|
1631
1705
|
else:
|
|
1632
1706
|
print_stderr(f'ERROR: Unknown output format (--format): {args.format}')
|
|
1633
1707
|
if not success:
|
|
@@ -1901,6 +1975,69 @@ def inspect_dep_track_project_violations(parser, args):
|
|
|
1901
1975
|
sys.exit(1)
|
|
1902
1976
|
|
|
1903
1977
|
|
|
1978
|
+
def inspect_gitlab_matches(parser,args):
|
|
1979
|
+
"""
|
|
1980
|
+
Handle GitLab matches the summary inspection command.
|
|
1981
|
+
|
|
1982
|
+
Analyzes SCANOSS scan results and generates a GitLab-compatible Markdown summary
|
|
1983
|
+
report of component matches. The report includes match details, file locations,
|
|
1984
|
+
and optionally clickable links to source files in GitLab repositories.
|
|
1985
|
+
|
|
1986
|
+
This command processes SCANOSS scan output and creates human-readable Markdown.
|
|
1987
|
+
|
|
1988
|
+
Parameters
|
|
1989
|
+
----------
|
|
1990
|
+
parser : ArgumentParser
|
|
1991
|
+
Command line parser object for help display
|
|
1992
|
+
args : Namespace
|
|
1993
|
+
Parsed command line arguments containing:
|
|
1994
|
+
- input: Path to SCANOSS scan results file (JSON format) to analyze
|
|
1995
|
+
- line_range_prefix: Base URL prefix for generating GitLab file links with line ranges
|
|
1996
|
+
(e.g., 'https://gitlab.com/org/project/-/blob/main')
|
|
1997
|
+
- output: Optional output file path for the generated Markdown report (default: stdout)
|
|
1998
|
+
- debug: Enable debug output for troubleshooting
|
|
1999
|
+
- trace: Enable trace-level logging
|
|
2000
|
+
- quiet: Suppress informational messages
|
|
2001
|
+
|
|
2002
|
+
Notes
|
|
2003
|
+
-----
|
|
2004
|
+
- The output is formatted in Markdown for optimal display in GitLab
|
|
2005
|
+
- Line range prefix enables clickable file references in the report
|
|
2006
|
+
- If output is not specified, the report is written to stdout
|
|
2007
|
+
"""
|
|
2008
|
+
|
|
2009
|
+
if args.input is None:
|
|
2010
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2011
|
+
sys.exit(1)
|
|
2012
|
+
|
|
2013
|
+
if args.line_range_prefix is None:
|
|
2014
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2015
|
+
sys.exit(1)
|
|
2016
|
+
|
|
2017
|
+
# Initialize output file if specified (create/truncate)
|
|
2018
|
+
if args.output:
|
|
2019
|
+
initialise_empty_file(args.output)
|
|
2020
|
+
|
|
2021
|
+
try:
|
|
2022
|
+
# Create GitLab matches summary generator with configuration
|
|
2023
|
+
match_summary = MatchSummary(
|
|
2024
|
+
debug=args.debug,
|
|
2025
|
+
trace=args.trace,
|
|
2026
|
+
quiet=args.quiet,
|
|
2027
|
+
scanoss_results_path=args.input, # Path to SCANOSS JSON results
|
|
2028
|
+
output=args.output, # Output file path or None for stdout
|
|
2029
|
+
line_range_prefix=args.line_range_prefix, # GitLab URL prefix for file links
|
|
2030
|
+
)
|
|
2031
|
+
|
|
2032
|
+
# Execute the summary generation
|
|
2033
|
+
match_summary.run()
|
|
2034
|
+
except Exception as e:
|
|
2035
|
+
# Handle any errors during report generation
|
|
2036
|
+
print_stderr(e)
|
|
2037
|
+
if args.debug:
|
|
2038
|
+
traceback.print_exc()
|
|
2039
|
+
sys.exit(1)
|
|
2040
|
+
|
|
1904
2041
|
# =============================================================================
|
|
1905
2042
|
# END INSPECT COMMAND HANDLERS
|
|
1906
2043
|
# =============================================================================
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20251027150435, utime: 1761577475
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
import sys
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
|
|
30
|
+
from .scanossbase import ScanossBase
|
|
31
|
+
from .utils import scanoss_scan_results_utils
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Lines:
|
|
36
|
+
begin: int
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Location:
|
|
40
|
+
path: str
|
|
41
|
+
lines: Lines
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class CodeQuality:
|
|
45
|
+
description: str
|
|
46
|
+
check_name: str
|
|
47
|
+
fingerprint: str
|
|
48
|
+
severity: str
|
|
49
|
+
location: Location
|
|
50
|
+
|
|
51
|
+
def to_dict(self):
|
|
52
|
+
"""Convert to dictionary for JSON serialization."""
|
|
53
|
+
return {
|
|
54
|
+
"description": self.description,
|
|
55
|
+
"check_name": self.check_name,
|
|
56
|
+
"fingerprint": self.fingerprint,
|
|
57
|
+
"severity": self.severity,
|
|
58
|
+
"location": {
|
|
59
|
+
"path": self.location.path,
|
|
60
|
+
"lines": {
|
|
61
|
+
"begin": self.location.lines.begin
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class GitLabQualityReport(ScanossBase):
|
|
67
|
+
"""
|
|
68
|
+
GitLabCodeQuality management class
|
|
69
|
+
Handle all interaction with GitLab Code Quality Report formatting
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, debug: bool = False, trace: bool = False, quiet: bool = False):
|
|
73
|
+
"""
|
|
74
|
+
Initialise the GitLabCodeQuality class
|
|
75
|
+
"""
|
|
76
|
+
super().__init__(debug, trace, quiet)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_code_quality(self, file_name: str, result: dict) -> CodeQuality or None:
|
|
80
|
+
if not result.get('file_hash'):
|
|
81
|
+
self.print_debug(f"Warning: no hash found for result: {result}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
if result.get('id') == 'file':
|
|
85
|
+
description = f"File match found in: {file_name}"
|
|
86
|
+
return CodeQuality(
|
|
87
|
+
description=description,
|
|
88
|
+
check_name=file_name,
|
|
89
|
+
fingerprint=result.get('file_hash'),
|
|
90
|
+
severity="info",
|
|
91
|
+
location=Location(
|
|
92
|
+
path=file_name,
|
|
93
|
+
lines = Lines(
|
|
94
|
+
begin= 1
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if not result.get('lines'):
|
|
100
|
+
self.print_debug(f"Warning: No lines found for result: {result}")
|
|
101
|
+
return None
|
|
102
|
+
lines = scanoss_scan_results_utils.get_lines(result.get('lines'))
|
|
103
|
+
if len(lines) == 0:
|
|
104
|
+
self.print_debug(f"Warning: empty lines for result: {result}")
|
|
105
|
+
return None
|
|
106
|
+
end_line = lines[len(lines) - 1] if len(lines) > 1 else lines[0]
|
|
107
|
+
description = f"Snippet found in: {file_name} - lines {lines[0]}-{end_line}"
|
|
108
|
+
return CodeQuality(
|
|
109
|
+
description=description,
|
|
110
|
+
check_name=file_name,
|
|
111
|
+
fingerprint=result.get('file_hash'),
|
|
112
|
+
severity="info",
|
|
113
|
+
location=Location(
|
|
114
|
+
path=file_name,
|
|
115
|
+
lines=Lines(
|
|
116
|
+
begin=lines[0]
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def _write_output(self, data: list[CodeQuality], output_file: str = None) -> bool:
|
|
122
|
+
"""Write the Gitlab Code Quality Report to output."""
|
|
123
|
+
try:
|
|
124
|
+
json_data = [item.to_dict() for item in data]
|
|
125
|
+
file = open(output_file, 'w') if output_file else sys.stdout
|
|
126
|
+
print(json.dumps(json_data, indent=2), file=file)
|
|
127
|
+
if output_file:
|
|
128
|
+
file.close()
|
|
129
|
+
return True
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self.print_stderr(f'Error writing output: {str(e)}')
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
def _produce_from_json(self, data: dict, output_file: str = None) -> bool:
|
|
135
|
+
code_quality = []
|
|
136
|
+
for file_name, results in data.items():
|
|
137
|
+
for result in results:
|
|
138
|
+
if not result.get('id'):
|
|
139
|
+
self.print_debug(f"Warning: No ID found for result: {result}")
|
|
140
|
+
continue
|
|
141
|
+
if result.get('id') != 'snippet' and result.get('id') != 'file':
|
|
142
|
+
self.print_debug(f"Skipping non-snippet/file match: {result}")
|
|
143
|
+
continue
|
|
144
|
+
code_quality_item = self._get_code_quality(file_name, result)
|
|
145
|
+
if code_quality_item:
|
|
146
|
+
code_quality.append(code_quality_item)
|
|
147
|
+
else:
|
|
148
|
+
self.print_debug(f"Warning: No Code Quality found for result: {result}")
|
|
149
|
+
self._write_output(data=code_quality,output_file=output_file)
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def _produce_from_str(self, json_str: str, output_file: str = None) -> bool:
|
|
153
|
+
"""
|
|
154
|
+
Produce Gitlab Code Quality Report output from input JSON string
|
|
155
|
+
:param json_str: input JSON string
|
|
156
|
+
:param output_file: Output file (optional)
|
|
157
|
+
:return: True if successful, False otherwise
|
|
158
|
+
"""
|
|
159
|
+
if not json_str:
|
|
160
|
+
self.print_stderr('ERROR: No JSON string provided to parse.')
|
|
161
|
+
return False
|
|
162
|
+
try:
|
|
163
|
+
data = json.loads(json_str)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
166
|
+
return False
|
|
167
|
+
return self._produce_from_json(data, output_file)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def produce_from_file(self, json_file: str, output_file: str = None) -> bool:
|
|
171
|
+
"""
|
|
172
|
+
Parse plain/raw input JSON file and produce GitLab Code Quality JSON output
|
|
173
|
+
:param json_file:
|
|
174
|
+
:param output_file:
|
|
175
|
+
:return: True if successful, False otherwise
|
|
176
|
+
"""
|
|
177
|
+
if not json_file:
|
|
178
|
+
self.print_stderr('ERROR: No JSON file provided to parse.')
|
|
179
|
+
return False
|
|
180
|
+
if not os.path.isfile(json_file):
|
|
181
|
+
self.print_stderr(f'ERROR: JSON file does not exist or is not a file: {json_file}')
|
|
182
|
+
return False
|
|
183
|
+
with open(json_file, 'r') as f:
|
|
184
|
+
success = self._produce_from_str(f.read(), output_file)
|
|
185
|
+
return success
|
|
@@ -28,6 +28,7 @@ from typing import Any, Dict, List, Optional, TypedDict
|
|
|
28
28
|
|
|
29
29
|
from ...services.dependency_track_service import DependencyTrackService
|
|
30
30
|
from ..policy_check import PolicyCheck, PolicyStatus
|
|
31
|
+
from ..utils.markdown_utils import generate_jira_table, generate_table
|
|
31
32
|
|
|
32
33
|
# Constants
|
|
33
34
|
PROCESSING_RETRY_DELAY = 5 # seconds
|
|
@@ -195,7 +196,7 @@ class DependencyTrackProjectViolationPolicyCheck(PolicyCheck[PolicyViolationDict
|
|
|
195
196
|
Returns:
|
|
196
197
|
Dictionary with formatted Markdown details and summary
|
|
197
198
|
"""
|
|
198
|
-
return self._md_summary_generator(project_violations,
|
|
199
|
+
return self._md_summary_generator(project_violations, generate_table)
|
|
199
200
|
|
|
200
201
|
def _jira_markdown(self, data: list[PolicyViolationDict]) -> Dict[str, Any]:
|
|
201
202
|
"""
|
|
@@ -207,7 +208,7 @@ class DependencyTrackProjectViolationPolicyCheck(PolicyCheck[PolicyViolationDict
|
|
|
207
208
|
Returns:
|
|
208
209
|
Dictionary containing Jira markdown formatted results and summary
|
|
209
210
|
"""
|
|
210
|
-
return self._md_summary_generator(data,
|
|
211
|
+
return self._md_summary_generator(data, generate_jira_table)
|
|
211
212
|
|
|
212
213
|
def is_project_updated(self, dt_project: Dict[str, Any]) -> bool:
|
|
213
214
|
"""
|
|
@@ -137,48 +137,6 @@ class PolicyCheck(ScanossBase, Generic[T]):
|
|
|
137
137
|
"""
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
|
-
def generate_table(self, headers, rows, centered_columns=None):
|
|
141
|
-
"""
|
|
142
|
-
Generate a Markdown table.
|
|
143
|
-
|
|
144
|
-
:param headers: List of headers for the table.
|
|
145
|
-
:param rows: List of rows for the table.
|
|
146
|
-
:param centered_columns: List of column indices to be centered.
|
|
147
|
-
:return: A string representing the Markdown table.
|
|
148
|
-
"""
|
|
149
|
-
col_sep = ' | '
|
|
150
|
-
centered_column_set = set(centered_columns or [])
|
|
151
|
-
if headers is None:
|
|
152
|
-
self.print_stderr('ERROR: Header are no set')
|
|
153
|
-
return None
|
|
154
|
-
|
|
155
|
-
# Decide which separator to use
|
|
156
|
-
def create_separator(index):
|
|
157
|
-
if centered_columns is None:
|
|
158
|
-
return '-'
|
|
159
|
-
return ':-:' if index in centered_column_set else '-'
|
|
160
|
-
|
|
161
|
-
# Build the row separator
|
|
162
|
-
row_separator = col_sep + col_sep.join(create_separator(index) for index, _ in enumerate(headers)) + col_sep
|
|
163
|
-
# build table rows
|
|
164
|
-
table_rows = [col_sep + col_sep.join(headers) + col_sep, row_separator]
|
|
165
|
-
table_rows.extend(col_sep + col_sep.join(row) + col_sep for row in rows)
|
|
166
|
-
return '\n'.join(table_rows)
|
|
167
|
-
|
|
168
|
-
def generate_jira_table(self, headers, rows, centered_columns=None):
|
|
169
|
-
col_sep = '*|*'
|
|
170
|
-
if headers is None:
|
|
171
|
-
self.print_stderr('ERROR: Header are no set')
|
|
172
|
-
return None
|
|
173
|
-
|
|
174
|
-
table_header = '|*' + col_sep.join(headers) + '*|\n'
|
|
175
|
-
table = table_header
|
|
176
|
-
for row in rows:
|
|
177
|
-
if len(headers) == len(row):
|
|
178
|
-
table += '|' + '|'.join(row) + '|\n'
|
|
179
|
-
|
|
180
|
-
return table
|
|
181
|
-
|
|
182
140
|
def _get_formatter(self) -> Callable[[List[dict]], Dict[str, Any]] or None:
|
|
183
141
|
"""
|
|
184
142
|
Get the appropriate formatter function based on the specified format.
|
|
File without changes
|
|
@@ -21,13 +21,58 @@ SPDX-License-Identifier: MIT
|
|
|
21
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
-
|
|
25
24
|
import json
|
|
25
|
+
from typing import Any
|
|
26
26
|
|
|
27
|
+
from ..policy_check import T
|
|
27
28
|
from .raw_base import RawBase
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class ComponentSummary(RawBase):
|
|
32
|
+
|
|
33
|
+
def _json(self, data: dict[str,Any]) -> dict[str,Any]:
|
|
34
|
+
"""
|
|
35
|
+
Format component summary data as JSON.
|
|
36
|
+
|
|
37
|
+
This method returns the component summary data in its original JSON structure
|
|
38
|
+
without any transformation. The data can be directly serialized to JSON format.
|
|
39
|
+
|
|
40
|
+
:param data: Dictionary containing component summary information including:
|
|
41
|
+
- components: List of component-license pairs with status and metadata
|
|
42
|
+
- totalComponents: Total number of unique components
|
|
43
|
+
- undeclaredComponents: Number of components with 'pending' status
|
|
44
|
+
- declaredComponents: Number of components with 'identified' status
|
|
45
|
+
- totalFilesDetected: Total count of files where components were detected
|
|
46
|
+
- totalFilesUndeclared: Count of files with undeclared components
|
|
47
|
+
- totalFilesDeclared: Count of files with declared components
|
|
48
|
+
:return: The same data dictionary, ready for JSON serialization
|
|
49
|
+
"""
|
|
50
|
+
return data
|
|
51
|
+
|
|
52
|
+
def _markdown(self, data: list[T]) -> dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Format component summary data as Markdown (not yet implemented).
|
|
55
|
+
|
|
56
|
+
This method is intended to convert component summary data into a human-readable
|
|
57
|
+
Markdown format with tables and formatted sections.
|
|
58
|
+
|
|
59
|
+
:param data: List of component summary items to format
|
|
60
|
+
:return: Dictionary containing formatted Markdown output
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def _jira_markdown(self, data: list[T]) -> dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Format component summary data as Jira-flavored Markdown (not yet implemented).
|
|
67
|
+
|
|
68
|
+
This method is intended to convert component summary data into Jira-compatible
|
|
69
|
+
Markdown format, which may include Jira-specific syntax for tables and formatting.
|
|
70
|
+
|
|
71
|
+
:param data: List of component summary items to format
|
|
72
|
+
:return: Dictionary containing Jira-formatted Markdown output
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
31
76
|
def _get_component_summary_from_components(self, scan_components: list)-> dict:
|
|
32
77
|
"""
|
|
33
78
|
Get a component summary from detected components.
|
|
@@ -84,10 +129,16 @@ class ComponentSummary(RawBase):
|
|
|
84
129
|
self._get_components_data(self.results, components)
|
|
85
130
|
return self._convert_components_to_list(components)
|
|
86
131
|
|
|
132
|
+
def _format(self, component_summary) -> str:
|
|
133
|
+
# TODO: Implement formatter to support dynamic outputs
|
|
134
|
+
json_data = self._json(component_summary)
|
|
135
|
+
return json.dumps(json_data, indent=2)
|
|
136
|
+
|
|
87
137
|
def run(self):
|
|
88
138
|
components = self._get_components()
|
|
89
139
|
component_summary = self._get_component_summary_from_components(components)
|
|
90
|
-
|
|
140
|
+
output = self._format(component_summary)
|
|
141
|
+
self.print_to_file_or_stdout(output, self.output)
|
|
91
142
|
return component_summary
|
|
92
143
|
#
|
|
93
144
|
# End of ComponentSummary Class
|
|
@@ -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
|
|
|
@@ -111,7 +112,7 @@ class Copyleft(RawBase[Component]):
|
|
|
111
112
|
:param components: List of components with copyleft licenses
|
|
112
113
|
:return: Dictionary with formatted Markdown details and summary
|
|
113
114
|
"""
|
|
114
|
-
return self._md_summary_generator(components,
|
|
115
|
+
return self._md_summary_generator(components, generate_table)
|
|
115
116
|
|
|
116
117
|
def _jira_markdown(self, components: list[Component]) -> Dict[str, Any]:
|
|
117
118
|
"""
|
|
@@ -120,7 +121,7 @@ class Copyleft(RawBase[Component]):
|
|
|
120
121
|
:param components: List of components with copyleft licenses
|
|
121
122
|
:return: Dictionary with formatted Markdown details and summary
|
|
122
123
|
"""
|
|
123
|
-
return self._md_summary_generator(components,
|
|
124
|
+
return self._md_summary_generator(components, generate_jira_table)
|
|
124
125
|
|
|
125
126
|
def _md_summary_generator(self, components: list[Component], table_generator):
|
|
126
127
|
"""
|
|
@@ -23,7 +23,9 @@ SPDX-License-Identifier: MIT
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
|
+
from typing import Any
|
|
26
27
|
|
|
28
|
+
from ..policy_check import T
|
|
27
29
|
from .raw_base import RawBase
|
|
28
30
|
|
|
29
31
|
|
|
@@ -36,6 +38,46 @@ class LicenseSummary(RawBase):
|
|
|
36
38
|
information, providing detailed summaries including copyleft analysis and license statistics.
|
|
37
39
|
"""
|
|
38
40
|
|
|
41
|
+
def _json(self, data: dict[str,Any]) -> dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Format license summary data as JSON.
|
|
44
|
+
|
|
45
|
+
This method is intended to return the license summary data in JSON structure
|
|
46
|
+
for serialization. The data should include license information with copyleft
|
|
47
|
+
analysis and license statistics.
|
|
48
|
+
|
|
49
|
+
:param data: List of license summary items to format
|
|
50
|
+
:return: Dictionary containing license summary information including:
|
|
51
|
+
- licenses: List of detected licenses with SPDX IDs, URLs, and copyleft status
|
|
52
|
+
- detectedLicenses: Total number of unique licenses
|
|
53
|
+
- detectedLicensesWithCopyleft: Count of licenses marked as copyleft
|
|
54
|
+
"""
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
def _markdown(self, data: list[T]) -> dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Format license summary data as Markdown (not yet implemented).
|
|
60
|
+
|
|
61
|
+
This method is intended to convert license summary data into a human-readable
|
|
62
|
+
Markdown format with tables and formatted sections.
|
|
63
|
+
|
|
64
|
+
:param data: List of license summary items to format
|
|
65
|
+
:return: Dictionary containing formatted Markdown output
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def _jira_markdown(self, data: list[T]) -> dict[str, Any]:
|
|
70
|
+
"""
|
|
71
|
+
Format license summary data as Jira-flavored Markdown (not yet implemented).
|
|
72
|
+
|
|
73
|
+
This method is intended to convert license summary data into Jira-compatible
|
|
74
|
+
Markdown format, which may include Jira-specific syntax for tables and formatting.
|
|
75
|
+
|
|
76
|
+
:param data: List of license summary items to format
|
|
77
|
+
:return: Dictionary containing Jira-formatted Markdown output
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
39
81
|
# Define required license fields as class constants
|
|
40
82
|
REQUIRED_LICENSE_FIELDS = ['spdxid', 'url', 'copyleft', 'source']
|
|
41
83
|
|
|
@@ -131,10 +173,16 @@ class LicenseSummary(RawBase):
|
|
|
131
173
|
self._get_dependencies_data(self.results, components)
|
|
132
174
|
return self._convert_components_to_list(components)
|
|
133
175
|
|
|
176
|
+
def _format(self, license_summary) -> str:
|
|
177
|
+
# TODO: Implement formatter to support dynamic outputs
|
|
178
|
+
json_data = self._json(license_summary)
|
|
179
|
+
return json.dumps(json_data, indent=2)
|
|
180
|
+
|
|
134
181
|
def run(self):
|
|
135
182
|
components = self._get_components()
|
|
136
183
|
license_summary = self._get_licenses_summary_from_components(components)
|
|
137
|
-
|
|
184
|
+
output = self._format(license_summary)
|
|
185
|
+
self.print_to_file_or_stdout(output, self.output)
|
|
138
186
|
return license_summary
|
|
139
187
|
#
|
|
140
188
|
# End of LicenseSummary Class
|
|
@@ -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
|
|
@@ -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=q6qrK1ovnkPF_j1UjeGsMzCShu2ZJ4lwVjxJWcnkEro,1146
|
|
10
|
+
scanoss/cli.py,sha256=u8t-F5TubEY7XnMtOdpQyf8hHQTWgJV6NLSAbO70KZY,103272
|
|
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,6 +16,7 @@ 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
|
|
@@ -64,24 +65,28 @@ 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=E98VubnEloGNCDN8aqw3fIG_fDuiNX2rH1wNr9RiqVk,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
91
|
scanoss/scanners/scanner_hfh.py,sha256=M2PB4wDTi4LD1DwuAVfWiqQkjOImSpNok7vgo5H_Spg,9190
|
|
87
92
|
scanoss/services/dependency_track_service.py,sha256=JIpqev4I-x_ZajMxD5W2Y3OAUvEJ_4nstzAPV90vfP8,5070
|
|
@@ -89,10 +94,11 @@ scanoss/utils/__init__.py,sha256=0hjb5ktavp7utJzFhGMPImPaZiHWgilM2HwvTp5lXJE,112
|
|
|
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.39.0.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
|
|
100
|
+
scanoss-1.39.0.dist-info/METADATA,sha256=SfWyYWos1ELzQhh1fOPEPCrv2ZzlyEIRUrQ-k26Qg1k,6181
|
|
101
|
+
scanoss-1.39.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
102
|
+
scanoss-1.39.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
|
|
103
|
+
scanoss-1.39.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
|
|
104
|
+
scanoss-1.39.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|