scanoss 1.37.1__tar.gz → 1.39.0__tar.gz

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 (117) hide show
  1. {scanoss-1.37.1/src/scanoss.egg-info → scanoss-1.39.0}/PKG-INFO +1 -1
  2. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/__init__.py +1 -1
  3. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/cli.py +174 -12
  4. scanoss-1.39.0/src/scanoss/data/build_date.txt +1 -0
  5. scanoss-1.39.0/src/scanoss/gitlabqualityreport.py +185 -0
  6. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/dependency_track/project_violation.py +3 -2
  7. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/policy_check.py +0 -42
  8. scanoss-1.39.0/src/scanoss/inspection/raw/__init__.py +0 -0
  9. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/raw/component_summary.py +53 -2
  10. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/raw/copyleft.py +3 -2
  11. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/raw/license_summary.py +49 -1
  12. scanoss-1.39.0/src/scanoss/inspection/raw/match_summary.py +290 -0
  13. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/raw/raw_base.py +5 -10
  14. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/raw/undeclared_component.py +3 -2
  15. scanoss-1.39.0/src/scanoss/inspection/utils/file_utils.py +44 -0
  16. scanoss-1.39.0/src/scanoss/inspection/utils/markdown_utils.py +63 -0
  17. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanners/folder_hasher.py +1 -0
  18. scanoss-1.39.0/src/scanoss/utils/scanoss_scan_results_utils.py +41 -0
  19. {scanoss-1.37.1 → scanoss-1.39.0/src/scanoss.egg-info}/PKG-INFO +1 -1
  20. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss.egg-info/SOURCES.txt +6 -0
  21. scanoss-1.37.1/src/scanoss/data/build_date.txt +0 -1
  22. {scanoss-1.37.1 → scanoss-1.39.0}/LICENSE +0 -0
  23. {scanoss-1.37.1 → scanoss-1.39.0}/PACKAGE.md +0 -0
  24. {scanoss-1.37.1 → scanoss-1.39.0}/README.md +0 -0
  25. {scanoss-1.37.1 → scanoss-1.39.0}/pyproject.toml +0 -0
  26. {scanoss-1.37.1 → scanoss-1.39.0}/setup.cfg +0 -0
  27. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/__init__.py +0 -0
  28. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/__init__.py +0 -0
  29. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
  30. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/annotations_pb2.pyi +0 -0
  31. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
  32. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
  33. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/openapiv2_pb2.pyi +0 -0
  34. {scanoss-1.37.1 → scanoss-1.39.0}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
  35. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/__init__.py +0 -0
  36. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/common/__init__.py +0 -0
  37. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/common/v2/__init__.py +0 -0
  38. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
  39. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
  40. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/components/__init__.py +0 -0
  41. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/components/v2/__init__.py +0 -0
  42. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
  43. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
  44. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
  45. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
  46. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/dependencies/__init__.py +0 -0
  47. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
  48. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
  49. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
  50. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/geoprovenance/__init__.py +0 -0
  51. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/geoprovenance/v2/__init__.py +0 -0
  52. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +0 -0
  53. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +0 -0
  54. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/licenses/__init__.py +0 -0
  55. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/licenses/v2/__init__.py +0 -0
  56. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/licenses/v2/scanoss_licenses_pb2.py +0 -0
  57. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +0 -0
  58. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/scanning/__init__.py +0 -0
  59. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/scanning/v2/__init__.py +0 -0
  60. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
  61. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
  62. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/semgrep/__init__.py +0 -0
  63. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
  64. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
  65. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
  66. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
  67. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
  68. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
  69. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
  70. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/components.py +0 -0
  71. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/constants.py +0 -0
  72. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/cryptography.py +0 -0
  73. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/csvoutput.py +0 -0
  74. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/cyclonedx.py +0 -0
  75. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/data/scanoss-settings-schema.json +0 -0
  76. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/data/spdx-exceptions.json +0 -0
  77. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/data/spdx-licenses.json +0 -0
  78. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/delta.py +0 -0
  79. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/export/__init__.py +0 -0
  80. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/export/dependency_track.py +0 -0
  81. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/file_filters.py +0 -0
  82. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/filecount.py +0 -0
  83. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/__init__.py +0 -0
  84. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/inspection/utils/license_utils.py +0 -0
  85. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/results.py +0 -0
  86. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scancodedeps.py +0 -0
  87. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanner.py +0 -0
  88. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanners/__init__.py +0 -0
  89. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanners/container_scanner.py +0 -0
  90. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanners/scanner_config.py +0 -0
  91. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanners/scanner_hfh.py +0 -0
  92. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanoss_settings.py +0 -0
  93. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanossapi.py +0 -0
  94. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanossbase.py +0 -0
  95. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanossgrpc.py +0 -0
  96. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scanpostprocessor.py +0 -0
  97. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/scantype.py +0 -0
  98. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/services/dependency_track_service.py +0 -0
  99. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/spdxlite.py +0 -0
  100. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/threadeddependencies.py +0 -0
  101. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/threadedscanning.py +0 -0
  102. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/utils/__init__.py +0 -0
  103. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/utils/abstract_presenter.py +0 -0
  104. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/utils/crc64.py +0 -0
  105. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/utils/file.py +0 -0
  106. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/utils/simhash.py +0 -0
  107. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss/winnowing.py +0 -0
  108. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss.egg-info/dependency_links.txt +0 -0
  109. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss.egg-info/entry_points.txt +0 -0
  110. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss.egg-info/requires.txt +0 -0
  111. {scanoss-1.37.1 → scanoss-1.39.0}/src/scanoss.egg-info/top_level.txt +0 -0
  112. {scanoss-1.37.1 → scanoss-1.39.0}/tests/test_csv_output.py +0 -0
  113. {scanoss-1.37.1 → scanoss-1.39.0}/tests/test_file_filters.py +0 -0
  114. {scanoss-1.37.1 → scanoss-1.39.0}/tests/test_policy_inspect.py +0 -0
  115. {scanoss-1.37.1 → scanoss-1.39.0}/tests/test_scan_post_processor.py +0 -0
  116. {scanoss-1.37.1 → scanoss-1.39.0}/tests/test_spdxlite.py +0 -0
  117. {scanoss-1.37.1 → scanoss-1.39.0}/tests/test_winnowing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.37.1
3
+ Version: 1.39.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -22,4 +22,4 @@ SPDX-License-Identifier: MIT
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = '1.37.1'
25
+ __version__ = '1.39.0'
@@ -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
@@ -929,10 +993,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
929
993
  )
930
994
 
931
995
  delta_sub = p_delta.add_subparsers(
932
- title='Delta Commands',
933
- dest='subparsercmd',
934
- description='Delta sub-commands',
935
- help='Delta sub-commands'
996
+ title='Delta Commands', dest='subparsercmd', description='Delta sub-commands', help='Delta sub-commands'
936
997
  )
937
998
 
938
999
  # Delta Sub-command: copy
@@ -1156,6 +1217,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
1156
1217
  p_inspect_legacy_license_summary,
1157
1218
  p_inspect_legacy_component_summary,
1158
1219
  p_inspect_dt_project_violation,
1220
+ p_gl_inspect_matches,
1159
1221
  c_provenance,
1160
1222
  p_folder_scan,
1161
1223
  p_folder_hash,
@@ -1165,9 +1227,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
1165
1227
  p_crypto_versions_in_range,
1166
1228
  c_licenses,
1167
1229
  e_dt,
1168
- p_copy
1230
+ p_copy,
1169
1231
  ]:
1170
- p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
1232
+ p.add_argument(
1233
+ '--debug',
1234
+ '-d',
1235
+ action='store_true',
1236
+ default=os.environ.get('SCANOSS_DEBUG', '').lower() == 'true',
1237
+ help='Enable debug messages (can also be set via environment variable SCANOSS_DEBUG)',
1238
+ )
1171
1239
  p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
1172
1240
  p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
1173
1241
 
@@ -1186,12 +1254,29 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
1186
1254
  sys.exit(1)
1187
1255
  elif (
1188
1256
  args.subparser
1189
- in ('utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins',
1190
- 'crypto', 'cr', 'export', 'exp', 'delta', 'dl')
1257
+ in (
1258
+ 'utils',
1259
+ 'ut',
1260
+ 'component',
1261
+ 'comp',
1262
+ 'inspect',
1263
+ 'insp',
1264
+ 'ins',
1265
+ 'crypto',
1266
+ 'cr',
1267
+ 'export',
1268
+ 'exp',
1269
+ 'delta',
1270
+ 'dl',
1271
+ )
1191
1272
  ) and not args.subparsercmd:
1192
1273
  parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
1193
1274
  sys.exit(1)
1194
- elif (args.subparser in 'inspect') and (args.subparsercmd in ('raw', 'dt')) and (args.subparser_subcmd is None):
1275
+ elif (
1276
+ (args.subparser in 'inspect')
1277
+ and (args.subparsercmd in ('raw', 'dt', 'glc', 'gitlab'))
1278
+ and (args.subparser_subcmd is None)
1279
+ ):
1195
1280
  parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
1196
1281
  sys.exit(1)
1197
1282
  args.func(parser, args) # Execute the function associated with the sub-command
@@ -1612,6 +1697,11 @@ def convert(parser, args):
1612
1697
  print_stderr('Producing CSV report...')
1613
1698
  csvo = CsvOutput(debug=args.debug, output_file=args.output)
1614
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)
1615
1705
  else:
1616
1706
  print_stderr(f'ERROR: Unknown output format (--format): {args.format}')
1617
1707
  if not success:
@@ -1885,6 +1975,69 @@ def inspect_dep_track_project_violations(parser, args):
1885
1975
  sys.exit(1)
1886
1976
 
1887
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
+
1888
2041
  # =============================================================================
1889
2042
  # END INSPECT COMMAND HANDLERS
1890
2043
  # =============================================================================
@@ -2634,6 +2787,7 @@ def initialise_empty_file(filename: str):
2634
2787
  print_stderr(f'Error: Unable to create output file {filename}: {e}')
2635
2788
  sys.exit(1)
2636
2789
 
2790
+
2637
2791
  def delta_copy(parser, args):
2638
2792
  """
2639
2793
  Handle delta copy command.
@@ -2661,8 +2815,15 @@ def delta_copy(parser, args):
2661
2815
  initialise_empty_file(args.output)
2662
2816
  try:
2663
2817
  # Create and configure delta copy command
2664
- delta = Delta(debug=args.debug, trace=args.trace, quiet=args.quiet, filepath=args.input, folder=args.folder,
2665
- output=args.output, root_dir=args.root)
2818
+ delta = Delta(
2819
+ debug=args.debug,
2820
+ trace=args.trace,
2821
+ quiet=args.quiet,
2822
+ filepath=args.input,
2823
+ folder=args.folder,
2824
+ output=args.output,
2825
+ root_dir=args.root,
2826
+ )
2666
2827
  # Execute copy and exit with appropriate status code
2667
2828
  status, _ = delta.copy()
2668
2829
  sys.exit(status)
@@ -2672,6 +2833,7 @@ def delta_copy(parser, args):
2672
2833
  traceback.print_exc()
2673
2834
  sys.exit(1)
2674
2835
 
2836
+
2675
2837
  def main():
2676
2838
  """
2677
2839
  Run the ScanOSS CLI
@@ -0,0 +1 @@
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, self.generate_table)
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, self.generate_jira_table)
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
- self.print_to_file_or_stdout(json.dumps(component_summary, indent=2), self.output)
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, self.generate_table)
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, self.generate_jira_table)
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
  """