scanoss 1.12.2__py3-none-any.whl → 1.43.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. protoc_gen_swagger/__init__.py +13 -13
  2. protoc_gen_swagger/options/__init__.py +13 -13
  3. protoc_gen_swagger/options/annotations_pb2.py +18 -12
  4. protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
  5. protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
  6. protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
  7. protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
  8. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
  9. scanoss/__init__.py +18 -18
  10. scanoss/api/__init__.py +17 -17
  11. scanoss/api/common/__init__.py +17 -17
  12. scanoss/api/common/v2/__init__.py +17 -17
  13. scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
  14. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
  15. scanoss/api/components/__init__.py +17 -17
  16. scanoss/api/components/v2/__init__.py +17 -17
  17. scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
  18. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
  19. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -21
  20. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
  21. scanoss/api/dependencies/__init__.py +17 -17
  22. scanoss/api/dependencies/v2/__init__.py +17 -17
  23. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
  24. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
  25. scanoss/api/geoprovenance/__init__.py +23 -0
  26. scanoss/api/geoprovenance/v2/__init__.py +23 -0
  27. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
  28. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
  29. scanoss/api/licenses/__init__.py +23 -0
  30. scanoss/api/licenses/v2/__init__.py +23 -0
  31. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
  32. scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
  33. scanoss/api/scanning/__init__.py +17 -17
  34. scanoss/api/scanning/v2/__init__.py +17 -17
  35. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
  36. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
  37. scanoss/api/semgrep/__init__.py +17 -17
  38. scanoss/api/semgrep/v2/__init__.py +17 -17
  39. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
  40. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
  41. scanoss/api/vulnerabilities/__init__.py +17 -17
  42. scanoss/api/vulnerabilities/v2/__init__.py +17 -17
  43. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
  44. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
  45. scanoss/cli.py +2359 -370
  46. scanoss/components.py +187 -94
  47. scanoss/constants.py +22 -0
  48. scanoss/cryptography.py +308 -0
  49. scanoss/csvoutput.py +91 -58
  50. scanoss/cyclonedx.py +221 -63
  51. scanoss/data/build_date.txt +1 -1
  52. scanoss/data/osadl-copyleft.json +133 -0
  53. scanoss/data/scanoss-settings-schema.json +254 -0
  54. scanoss/delta.py +197 -0
  55. scanoss/export/__init__.py +23 -0
  56. scanoss/export/dependency_track.py +227 -0
  57. scanoss/file_filters.py +582 -0
  58. scanoss/filecount.py +75 -69
  59. scanoss/gitlabqualityreport.py +214 -0
  60. scanoss/header_filter.py +563 -0
  61. scanoss/inspection/__init__.py +23 -0
  62. scanoss/inspection/policy_check/__init__.py +0 -0
  63. scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  64. scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
  65. scanoss/inspection/policy_check/policy_check.py +222 -0
  66. scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  67. scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
  68. scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
  69. scanoss/inspection/summary/__init__.py +0 -0
  70. scanoss/inspection/summary/component_summary.py +170 -0
  71. scanoss/inspection/summary/license_summary.py +191 -0
  72. scanoss/inspection/summary/match_summary.py +341 -0
  73. scanoss/inspection/utils/file_utils.py +44 -0
  74. scanoss/inspection/utils/license_utils.py +123 -0
  75. scanoss/inspection/utils/markdown_utils.py +63 -0
  76. scanoss/inspection/utils/scan_result_processor.py +417 -0
  77. scanoss/osadl.py +125 -0
  78. scanoss/results.py +275 -0
  79. scanoss/scancodedeps.py +87 -38
  80. scanoss/scanner.py +431 -539
  81. scanoss/scanners/__init__.py +23 -0
  82. scanoss/scanners/container_scanner.py +476 -0
  83. scanoss/scanners/folder_hasher.py +358 -0
  84. scanoss/scanners/scanner_config.py +73 -0
  85. scanoss/scanners/scanner_hfh.py +252 -0
  86. scanoss/scanoss_settings.py +337 -0
  87. scanoss/scanossapi.py +140 -101
  88. scanoss/scanossbase.py +59 -22
  89. scanoss/scanossgrpc.py +799 -251
  90. scanoss/scanpostprocessor.py +294 -0
  91. scanoss/scantype.py +22 -21
  92. scanoss/services/dependency_track_service.py +132 -0
  93. scanoss/spdxlite.py +532 -174
  94. scanoss/threadeddependencies.py +148 -47
  95. scanoss/threadedscanning.py +53 -37
  96. scanoss/utils/__init__.py +23 -0
  97. scanoss/utils/abstract_presenter.py +103 -0
  98. scanoss/utils/crc64.py +96 -0
  99. scanoss/utils/file.py +84 -0
  100. scanoss/utils/scanoss_scan_results_utils.py +41 -0
  101. scanoss/utils/simhash.py +198 -0
  102. scanoss/winnowing.py +241 -63
  103. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
  104. scanoss-1.43.1.dist-info/RECORD +110 -0
  105. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
  106. scanoss-1.12.2.dist-info/RECORD +0 -58
  107. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
  108. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
  109. {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
scanoss/filecount.py CHANGED
@@ -1,30 +1,32 @@
1
1
  """
2
- SPDX-License-Identifier: MIT
3
-
4
- Copyright (c) 2022, SCANOSS
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in
14
- all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- THE SOFTWARE.
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2022, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
23
  """
24
+
24
25
  import csv
25
26
  import os
26
27
  import pathlib
27
28
  import sys
29
+ from contextlib import nullcontext
28
30
 
29
31
  from progress.spinner import Spinner
30
32
 
@@ -36,9 +38,15 @@ class FileCount(ScanossBase):
36
38
  SCANOSS File Type Count class
37
39
  Handle the scanning of files, snippets and dependencies
38
40
  """
39
- def __init__(self, scan_output: str = None, hidden_files_folders: bool = False,
40
- debug: bool = False, trace: bool = False, quiet: bool = False
41
- ):
41
+
42
+ def __init__(
43
+ self,
44
+ scan_output: str = None,
45
+ hidden_files_folders: bool = False,
46
+ debug: bool = False,
47
+ trace: bool = False,
48
+ quiet: bool = False,
49
+ ):
42
50
  """
43
51
  Initialise scanning class
44
52
  """
@@ -56,7 +64,7 @@ class FileCount(ScanossBase):
56
64
  file_list = []
57
65
  for f in files:
58
66
  ignore = False
59
- if f.startswith(".") and not self.hidden_files_folders: # Ignore all . files unless requested
67
+ if f.startswith('.') and not self.hidden_files_folders: # Ignore all . files unless requested
60
68
  ignore = True
61
69
  if not ignore:
62
70
  file_list.append(f)
@@ -71,7 +79,7 @@ class FileCount(ScanossBase):
71
79
  dir_list = []
72
80
  for d in dirs:
73
81
  ignore = False
74
- if d.startswith(".") and not self.hidden_files_folders: # Ignore all . folders unless requested
82
+ if d.startswith('.') and not self.hidden_files_folders: # Ignore all . folders unless requested
75
83
  ignore = True
76
84
  if not ignore:
77
85
  dir_list.append(d)
@@ -84,7 +92,7 @@ class FileCount(ScanossBase):
84
92
  if not outfile and self.scan_output:
85
93
  outfile = self.scan_output
86
94
  if outfile:
87
- with open(outfile, "a") as rf:
95
+ with open(outfile, 'a') as rf:
88
96
  rf.write(string + '\n')
89
97
  else:
90
98
  print(string)
@@ -98,60 +106,58 @@ class FileCount(ScanossBase):
98
106
  """
99
107
  success = True
100
108
  if not scan_dir:
101
- raise Exception(f"ERROR: Please specify a folder to scan")
109
+ raise Exception('ERROR: Please specify a folder to scan')
102
110
  if not os.path.exists(scan_dir) or not os.path.isdir(scan_dir):
103
- raise Exception(f"ERROR: Specified folder does not exist or is not a folder: {scan_dir}")
111
+ raise Exception(f'ERROR: Specified folder does not exist or is not a folder: {scan_dir}')
104
112
 
105
113
  self.print_msg(f'Searching {scan_dir} for files to count...')
106
- spinner = None
107
- if not self.quiet and self.isatty:
108
- spinner = Spinner('Searching ')
109
- file_types = {}
110
- file_count = 0
111
- file_size = 0
112
- for root, dirs, files in os.walk(scan_dir):
113
- self.print_trace(f'U Root: {root}, Dirs: {dirs}, Files {files}')
114
- dirs[:] = self.__filter_dirs(dirs) # Strip out unwanted directories
115
- filtered_files = self.__filter_files(files) # Strip out unwanted files
116
- self.print_trace(f'F Root: {root}, Dirs: {dirs}, Files {filtered_files}')
117
- for file in filtered_files: # Cycle through each filtered file
118
- path = os.path.join(root, file)
119
- f_size = 0
120
- try:
121
- f_size = os.stat(path).st_size
122
- except Exception as e:
123
- self.print_trace(f'Ignoring missing symlink file: {file} ({e})') # broken symlink
124
- if f_size > 0: # Ignore broken links and empty files
125
- file_count = file_count + 1
126
- file_size = file_size + f_size
127
- f_suffix = pathlib.Path(file).suffix
128
- if not f_suffix or f_suffix == '':
129
- f_suffix = 'no_suffix'
130
- self.print_trace(f'Counting {path} ({f_suffix} - {f_size})..')
131
- fc = file_types.get(f_suffix)
132
- if not fc:
133
- fc = [1, f_size]
134
- else:
135
- fc[0] = fc[0] + 1
136
- fc[1] = fc[1] + f_size
137
- file_types[f_suffix] = fc
138
- if spinner:
139
- spinner.next()
140
- # End for loop
141
- if spinner:
142
- spinner.finish()
143
- self.print_stderr(f'Found {file_count:,.0f} files with a total size of {file_size/(1<<20):,.2f} MB.')
114
+ spinner_ctx = Spinner('Searching ') if (not self.quiet and self.isatty) else nullcontext()
115
+
116
+ with spinner_ctx as spinner:
117
+ file_types = {}
118
+ file_count = 0
119
+ file_size = 0
120
+ for root, dirs, files in os.walk(scan_dir):
121
+ self.print_trace(f'U Root: {root}, Dirs: {dirs}, Files {files}')
122
+ dirs[:] = self.__filter_dirs(dirs) # Strip out unwanted directories
123
+ filtered_files = self.__filter_files(files) # Strip out unwanted files
124
+ self.print_trace(f'F Root: {root}, Dirs: {dirs}, Files {filtered_files}')
125
+ for file in filtered_files: # Cycle through each filtered file
126
+ path = os.path.join(root, file)
127
+ f_size = 0
128
+ try:
129
+ f_size = os.stat(path).st_size
130
+ except Exception as e:
131
+ self.print_trace(f'Ignoring missing symlink file: {file} ({e})') # broken symlink
132
+ if f_size > 0: # Ignore broken links and empty files
133
+ file_count = file_count + 1
134
+ file_size = file_size + f_size
135
+ f_suffix = pathlib.Path(file).suffix
136
+ if not f_suffix or f_suffix == '':
137
+ f_suffix = 'no_suffix'
138
+ self.print_trace(f'Counting {path} ({f_suffix} - {f_size})..')
139
+ fc = file_types.get(f_suffix)
140
+ if not fc:
141
+ fc = [1, f_size]
142
+ else:
143
+ fc[0] = fc[0] + 1
144
+ fc[1] = fc[1] + f_size
145
+ file_types[f_suffix] = fc
146
+ if spinner:
147
+ spinner.next()
148
+ # End for loop
149
+ self.print_stderr(f'Found {file_count:,.0f} files with a total size of {file_size / (1 << 20):,.2f} MB.')
144
150
  if file_types:
145
151
  csv_dict = []
146
152
  for k in file_types:
147
153
  d = file_types[k]
148
- csv_dict.append({'extension': k, 'count': d[0], 'size(MB)': f'{d[1]/(1<<20):,.2f}'})
154
+ csv_dict.append({'extension': k, 'count': d[0], 'size(MB)': f'{d[1] / (1 << 20):,.2f}'})
149
155
  fields = ['extension', 'count', 'size(MB)']
150
156
  file = sys.stdout
151
157
  if self.scan_output:
152
158
  file = open(self.scan_output, 'w')
153
159
  writer = csv.DictWriter(file, fieldnames=fields)
154
- writer.writeheader() # writing headers (field names)
160
+ writer.writeheader() # writing headers (field names)
155
161
  writer.writerows(csv_dict) # writing data rows
156
162
  if self.scan_output:
157
163
  file.close()
@@ -0,0 +1,214 @@
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
+ self.print_trace(f"GitLabQualityReport initialized with debug={debug}, trace={trace}, quiet={quiet}")
78
+
79
+
80
+ def _get_code_quality(self, file_name: str, result: dict) -> CodeQuality or None:
81
+ self.print_trace(f"_get_code_quality called for file: {file_name}")
82
+ self.print_trace(f"Processing result: {result}")
83
+
84
+ if not result.get('file_hash'):
85
+ self.print_debug(f"Warning: no hash found for result: {result}")
86
+ return None
87
+
88
+ if result.get('id') == 'file':
89
+ self.print_debug(f"Processing file match for: {file_name}")
90
+ description = f"File match found in: {file_name}"
91
+ code_quality = CodeQuality(
92
+ description=description,
93
+ check_name=file_name,
94
+ fingerprint=result.get('file_hash'),
95
+ severity="info",
96
+ location=Location(
97
+ path=file_name,
98
+ lines = Lines(
99
+ begin= 1
100
+ )
101
+ )
102
+ )
103
+ self.print_trace(f"Created file CodeQuality object: {code_quality}")
104
+ return code_quality
105
+
106
+ if not result.get('lines'):
107
+ self.print_debug(f"Warning: No lines found for result: {result}")
108
+ return None
109
+ lines = scanoss_scan_results_utils.get_lines(result.get('lines'))
110
+ self.print_trace(f"Extracted lines: {lines}")
111
+ if len(lines) == 0:
112
+ self.print_debug(f"Warning: empty lines for result: {result}")
113
+ return None
114
+ end_line = lines[len(lines) - 1] if len(lines) > 1 else lines[0]
115
+ description = f"Snippet found in: {file_name} - lines {lines[0]}-{end_line}"
116
+ self.print_debug(f"Processing snippet match for: {file_name}, lines: {lines[0]}-{end_line}")
117
+ code_quality = CodeQuality(
118
+ description=description,
119
+ check_name=file_name,
120
+ fingerprint=result.get('file_hash'),
121
+ severity="info",
122
+ location=Location(
123
+ path=file_name,
124
+ lines=Lines(
125
+ begin=lines[0]
126
+ )
127
+ )
128
+ )
129
+ self.print_trace(f"Created snippet CodeQuality object: {code_quality}")
130
+ return code_quality
131
+
132
+ def _write_output(self, data: list[CodeQuality], output_file: str = None) -> bool:
133
+ """Write the Gitlab Code Quality Report to output."""
134
+ self.print_trace(f"_write_output called with {len(data)} items, output_file: {output_file}")
135
+ try:
136
+ json_data = [item.to_dict() for item in data]
137
+ self.print_trace(f"JSON data: {json_data}")
138
+ file = open(output_file, 'w') if output_file else sys.stdout
139
+ print(json.dumps(json_data, indent=2), file=file)
140
+ if output_file:
141
+ file.close()
142
+ self.print_debug(f"Wrote output to file: {output_file}")
143
+ else:
144
+ self.print_debug("Wrote output to 'stdout'")
145
+ return True
146
+ except Exception as e:
147
+ self.print_stderr(f'Error writing output: {str(e)}')
148
+ return False
149
+
150
+ def _produce_from_json(self, data: dict, output_file: str = None) -> bool:
151
+ self.print_trace(f"_produce_from_json called with output_file: {output_file}")
152
+ self.print_debug(f"Processing {len(data)} files from JSON data")
153
+ code_quality = []
154
+ for file_name, results in data.items():
155
+ self.print_trace(f"Processing file: {file_name} with {len(results)} results")
156
+ for result in results:
157
+ if not result.get('id'):
158
+ self.print_debug(f"Warning: No ID found for result: {result}")
159
+ continue
160
+ if result.get('id') != 'snippet' and result.get('id') != 'file':
161
+ self.print_debug(f"Skipping non-snippet/file match: {file_name}, id: '{result['id']}'")
162
+ continue
163
+ code_quality_item = self._get_code_quality(file_name, result)
164
+ if code_quality_item:
165
+ code_quality.append(code_quality_item)
166
+ self.print_trace(f"Added code quality item for {file_name}")
167
+ else:
168
+ self.print_debug(f"Warning: No Code Quality found for result: {result}")
169
+ self.print_debug(f"Generated {len(code_quality)} code quality items")
170
+ self._write_output(data=code_quality,output_file=output_file)
171
+ return True
172
+
173
+ def _produce_from_str(self, json_str: str, output_file: str = None) -> bool:
174
+ """
175
+ Produce Gitlab Code Quality Report output from input JSON string
176
+ :param json_str: input JSON string
177
+ :param output_file: Output file (optional)
178
+ :return: True if successful, False otherwise
179
+ """
180
+ self.print_trace(f"_produce_from_str called with output_file: {output_file}")
181
+ if not json_str:
182
+ self.print_stderr('ERROR: No JSON string provided to parse.')
183
+ return False
184
+ self.print_debug(f"Parsing JSON string of length: {len(json_str)}")
185
+ try:
186
+ data = json.loads(json_str)
187
+ self.print_debug("Successfully parsed JSON data")
188
+ self.print_trace(f"Parsed data structure: {type(data)}")
189
+ except Exception as e:
190
+ self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
191
+ return False
192
+ return self._produce_from_json(data, output_file)
193
+
194
+
195
+ def produce_from_file(self, json_file: str, output_file: str = None) -> bool:
196
+ """
197
+ Parse plain/raw input JSON file and produce GitLab Code Quality JSON output
198
+ :param json_file:
199
+ :param output_file:
200
+ :return: True if successful, False otherwise
201
+ """
202
+ self.print_trace(f"produce_from_file called with json_file: {json_file}, output_file: {output_file}")
203
+ self.print_debug(f"Input JSON file: {json_file}, output_file: {output_file}")
204
+ if not json_file:
205
+ self.print_stderr('ERROR: No JSON file provided to parse.')
206
+ return False
207
+ if not os.path.isfile(json_file):
208
+ self.print_stderr(f'ERROR: JSON file does not exist or is not a file: {json_file}')
209
+ return False
210
+ self.print_debug(f"Reading JSON file: {json_file}")
211
+ with open(json_file, 'r') as f:
212
+ json_content = f.read()
213
+ success = self._produce_from_str(json_content, output_file)
214
+ return success