scanoss 1.44.0__tar.gz → 1.45.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 (127) hide show
  1. {scanoss-1.44.0/src/scanoss.egg-info → scanoss-1.45.0}/PKG-INFO +1 -1
  2. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/__init__.py +1 -1
  3. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/cli.py +49 -10
  4. scanoss-1.45.0/src/scanoss/data/build_date.txt +1 -0
  5. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/data/scanoss-settings-schema.json +94 -0
  6. scanoss-1.45.0/src/scanoss/scan_settings_builder.py +311 -0
  7. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanner.py +64 -13
  8. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanoss_settings.py +96 -0
  9. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanossapi.py +58 -0
  10. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanpostprocessor.py +12 -12
  11. {scanoss-1.44.0 → scanoss-1.45.0/src/scanoss.egg-info}/PKG-INFO +1 -1
  12. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss.egg-info/SOURCES.txt +2 -0
  13. scanoss-1.45.0/tests/test_scan_settings_builder.py +362 -0
  14. scanoss-1.44.0/src/scanoss/data/build_date.txt +0 -1
  15. {scanoss-1.44.0 → scanoss-1.45.0}/LICENSE +0 -0
  16. {scanoss-1.44.0 → scanoss-1.45.0}/PACKAGE.md +0 -0
  17. {scanoss-1.44.0 → scanoss-1.45.0}/README.md +0 -0
  18. {scanoss-1.44.0 → scanoss-1.45.0}/pyproject.toml +0 -0
  19. {scanoss-1.44.0 → scanoss-1.45.0}/setup.cfg +0 -0
  20. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/__init__.py +0 -0
  21. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/__init__.py +0 -0
  22. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
  23. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/annotations_pb2.pyi +0 -0
  24. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
  25. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
  26. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/openapiv2_pb2.pyi +0 -0
  27. {scanoss-1.44.0 → scanoss-1.45.0}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
  28. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/__init__.py +0 -0
  29. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/common/__init__.py +0 -0
  30. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/common/v2/__init__.py +0 -0
  31. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
  32. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
  33. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/components/__init__.py +0 -0
  34. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/components/v2/__init__.py +0 -0
  35. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
  36. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
  37. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
  38. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
  39. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/dependencies/__init__.py +0 -0
  40. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
  41. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
  42. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
  43. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/geoprovenance/__init__.py +0 -0
  44. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/geoprovenance/v2/__init__.py +0 -0
  45. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +0 -0
  46. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +0 -0
  47. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/licenses/__init__.py +0 -0
  48. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/licenses/v2/__init__.py +0 -0
  49. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/licenses/v2/scanoss_licenses_pb2.py +0 -0
  50. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +0 -0
  51. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/scanning/__init__.py +0 -0
  52. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/scanning/v2/__init__.py +0 -0
  53. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
  54. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
  55. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/semgrep/__init__.py +0 -0
  56. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
  57. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
  58. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
  59. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
  60. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
  61. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
  62. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
  63. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/components.py +0 -0
  64. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/constants.py +0 -0
  65. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/cryptography.py +0 -0
  66. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/csvoutput.py +0 -0
  67. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/cyclonedx.py +0 -0
  68. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/data/osadl-copyleft.json +0 -0
  69. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/data/spdx-exceptions.json +0 -0
  70. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/data/spdx-licenses.json +0 -0
  71. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/delta.py +0 -0
  72. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/export/__init__.py +0 -0
  73. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/export/dependency_track.py +0 -0
  74. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/file_filters.py +0 -0
  75. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/filecount.py +0 -0
  76. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/gitlabqualityreport.py +0 -0
  77. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/header_filter.py +0 -0
  78. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/__init__.py +0 -0
  79. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/__init__.py +0 -0
  80. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  81. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/dependency_track/project_violation.py +0 -0
  82. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/policy_check.py +0 -0
  83. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  84. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/scanoss/copyleft.py +0 -0
  85. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/policy_check/scanoss/undeclared_component.py +0 -0
  86. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/summary/__init__.py +0 -0
  87. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/summary/component_summary.py +0 -0
  88. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/summary/license_summary.py +0 -0
  89. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/summary/match_summary.py +0 -0
  90. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/utils/file_utils.py +0 -0
  91. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/utils/license_utils.py +0 -0
  92. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/utils/markdown_utils.py +0 -0
  93. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/inspection/utils/scan_result_processor.py +0 -0
  94. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/osadl.py +0 -0
  95. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/results.py +0 -0
  96. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scancodedeps.py +0 -0
  97. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanners/__init__.py +0 -0
  98. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanners/container_scanner.py +0 -0
  99. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanners/folder_hasher.py +0 -0
  100. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanners/scanner_config.py +0 -0
  101. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanners/scanner_hfh.py +0 -0
  102. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanossbase.py +0 -0
  103. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scanossgrpc.py +0 -0
  104. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/scantype.py +0 -0
  105. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/services/dependency_track_service.py +0 -0
  106. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/spdxlite.py +0 -0
  107. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/threadeddependencies.py +0 -0
  108. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/threadedscanning.py +0 -0
  109. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/utils/__init__.py +0 -0
  110. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/utils/abstract_presenter.py +0 -0
  111. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/utils/crc64.py +0 -0
  112. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/utils/file.py +0 -0
  113. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/utils/scanoss_scan_results_utils.py +0 -0
  114. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/utils/simhash.py +0 -0
  115. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss/winnowing.py +0 -0
  116. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss.egg-info/dependency_links.txt +0 -0
  117. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss.egg-info/entry_points.txt +0 -0
  118. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss.egg-info/requires.txt +0 -0
  119. {scanoss-1.44.0 → scanoss-1.45.0}/src/scanoss.egg-info/top_level.txt +0 -0
  120. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_csv_output.py +0 -0
  121. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_file_filters.py +0 -0
  122. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_headers_filter.py +0 -0
  123. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_osadl.py +0 -0
  124. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_policy_inspect.py +0 -0
  125. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_scan_post_processor.py +0 -0
  126. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_spdxlite.py +0 -0
  127. {scanoss-1.44.0 → scanoss-1.45.0}/tests/test_winnowing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.44.0
3
+ Version: 1.45.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.44.0'
25
+ __version__ = '1.45.0'
@@ -195,6 +195,40 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
195
195
  help='Save fingerprints to specified file during scan'
196
196
  )
197
197
 
198
+ # Snippet tuning options
199
+ p_scan.add_argument(
200
+ '--min-snippet-hits',
201
+ type=int,
202
+ default=None,
203
+ help='Minimum snippet hits required. A value of 0 defers to server configuration (optional)',
204
+ )
205
+ p_scan.add_argument(
206
+ '--min-snippet-lines',
207
+ type=int,
208
+ default=None,
209
+ help='Minimum snippet lines required. A value of 0 defers to server configuration (optional)',
210
+ )
211
+ p_scan.add_argument(
212
+ '--ranking',
213
+ type=str,
214
+ choices=['unset' ,'true', 'false'],
215
+ default='unset',
216
+ help='Enable or disable ranking (optional - default: server configuration)',
217
+ )
218
+ p_scan.add_argument(
219
+ '--ranking-threshold',
220
+ type=int,
221
+ default=-1,
222
+ help='Ranking threshold value. Valid range: -1 to 10. A value of -1 defers to server configuration (optional)',
223
+ )
224
+ p_scan.add_argument(
225
+ '--honour-file-exts',
226
+ type=str,
227
+ choices=['unset','true', 'false'],
228
+ default='unset',
229
+ help='Honour file extensions during scanning. When not set, defers to server configuration (optional)',
230
+ )
231
+
198
232
  # Sub-command: fingerprint
199
233
  p_wfp = subparsers.add_parser(
200
234
  'fingerprint',
@@ -1381,11 +1415,11 @@ def wfp(parser, args):
1381
1415
  initialise_empty_file(args.output)
1382
1416
 
1383
1417
  # Load scan settings
1384
- scan_settings = None
1418
+ scanoss_settings = None
1385
1419
  if not args.skip_settings_file:
1386
- scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
1420
+ scanoss_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
1387
1421
  try:
1388
- scan_settings.load_json_file(args.settings, args.scan_dir)
1422
+ scanoss_settings.load_json_file(args.settings, args.scan_dir)
1389
1423
  except ScanossSettingsError as e:
1390
1424
  print_stderr(f'Error: {e}')
1391
1425
  sys.exit(1)
@@ -1407,7 +1441,7 @@ def wfp(parser, args):
1407
1441
  skip_md5_ids=args.skip_md5,
1408
1442
  strip_hpsm_ids=args.strip_hpsm,
1409
1443
  strip_snippet_ids=args.strip_snippet,
1410
- scan_settings=scan_settings,
1444
+ scanoss_settings=scanoss_settings,
1411
1445
  skip_headers=args.skip_headers,
1412
1446
  skip_headers_limit=args.skip_headers_limit,
1413
1447
  )
@@ -1491,20 +1525,20 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1491
1525
  print_stderr('ERROR: Cannot specify both --settings and --skip-file-settings options.')
1492
1526
  sys.exit(1)
1493
1527
  # Figure out which settings (if any) to load before processing
1494
- scan_settings = None
1528
+ scanoss_settings = None
1495
1529
  if not args.skip_settings_file:
1496
- scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
1530
+ scanoss_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
1497
1531
  try:
1498
1532
  if args.identify:
1499
- scan_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type(
1533
+ scanoss_settings.load_json_file(args.identify, args.scan_dir).set_file_type('legacy').set_scan_type(
1500
1534
  'identify'
1501
1535
  )
1502
1536
  elif args.ignore:
1503
- scan_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type(
1537
+ scanoss_settings.load_json_file(args.ignore, args.scan_dir).set_file_type('legacy').set_scan_type(
1504
1538
  'blacklist'
1505
1539
  )
1506
1540
  else:
1507
- scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
1541
+ scanoss_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
1508
1542
 
1509
1543
  except ScanossSettingsError as e:
1510
1544
  print_stderr(f'Error: {e}')
@@ -1600,9 +1634,14 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1600
1634
  skip_md5_ids=args.skip_md5,
1601
1635
  strip_hpsm_ids=args.strip_hpsm,
1602
1636
  strip_snippet_ids=args.strip_snippet,
1603
- scan_settings=scan_settings,
1637
+ scanoss_settings=scanoss_settings,
1604
1638
  req_headers=process_req_headers(args.header),
1605
1639
  use_grpc=args.grpc,
1640
+ min_snippet_hits=args.min_snippet_hits,
1641
+ min_snippet_lines=args.min_snippet_lines,
1642
+ ranking=args.ranking,
1643
+ ranking_threshold=args.ranking_threshold,
1644
+ honour_file_exts=args.honour_file_exts,
1606
1645
  skip_headers=args.skip_headers,
1607
1646
  skip_headers_limit=args.skip_headers_limit,
1608
1647
  wfp_output=args.wfp_output,
@@ -0,0 +1 @@
1
+ date: 20260202142827, utime: 1770042507
@@ -139,6 +139,100 @@
139
139
  }
140
140
  }
141
141
  }
142
+ },
143
+ "file_snippet": {
144
+ "type": "object",
145
+ "description": "File snippet scanning configuration",
146
+ "properties": {
147
+ "proxy": {
148
+ "type": "object",
149
+ "description": "Proxy configuration for file snippet requests",
150
+ "properties": {
151
+ "host": {
152
+ "type": "string",
153
+ "description": "Proxy host URL"
154
+ }
155
+ }
156
+ },
157
+ "http_config": {
158
+ "type": "object",
159
+ "description": "HTTP configuration for file snippet requests",
160
+ "properties": {
161
+ "base_uri": {
162
+ "type": "string",
163
+ "description": "Base URI for file snippet API requests"
164
+ },
165
+ "ignore_cert_errors": {
166
+ "type": "boolean",
167
+ "description": "Whether to ignore certificate errors"
168
+ }
169
+ }
170
+ },
171
+ "ranking_enabled": {
172
+ "type": ["boolean", "null"],
173
+ "description": "Enable/disable ranking",
174
+ "default": null
175
+ },
176
+ "ranking_threshold": {
177
+ "type": ["integer", "null"],
178
+ "description": "Ranking threshold value. A value of -1 defers to server configuration",
179
+ "minimum": -1,
180
+ "maximum": 99,
181
+ "default": 0
182
+ },
183
+ "min_snippet_hits": {
184
+ "type": "integer",
185
+ "description": "Minimum snippet hits required",
186
+ "minimum": 0,
187
+ "default": 0
188
+ },
189
+ "min_snippet_lines": {
190
+ "type": "integer",
191
+ "description": "Minimum snippet lines required",
192
+ "minimum": 0,
193
+ "default": 0
194
+ },
195
+ "honour_file_exts": {
196
+ "type": ["boolean", "null"],
197
+ "description": "Ignores file extensions. When not set, defers to server configuration.",
198
+ "default": true
199
+ },
200
+ "dependency_analysis": {
201
+ "type": "boolean",
202
+ "description": "Enable dependency analysis"
203
+ },
204
+ "skip_headers": {
205
+ "type": "boolean",
206
+ "description": "Skip license headers, comments and imports at the beginning of files",
207
+ "default": false
208
+ },
209
+ "skip_headers_limit": {
210
+ "type": "integer",
211
+ "description": "Maximum number of lines to skip when filtering headers",
212
+ "default": 0
213
+ }
214
+ }
215
+ },
216
+ "hpfm": {
217
+ "type": "object",
218
+ "description": "HPFM (High Precision Folder Matching) configuration",
219
+ "properties": {
220
+ "ranking_enabled": {
221
+ "type": "boolean",
222
+ "description": "Enable ranking for HPFM"
223
+ },
224
+ "ranking_threshold": {
225
+ "type": ["integer", "null"],
226
+ "description": "Ranking threshold value. A value of -1 defers to server configuration",
227
+ "minimum": -1,
228
+ "maximum": 99,
229
+ "default": 0
230
+ }
231
+ }
232
+ },
233
+ "container": {
234
+ "type": "object",
235
+ "description": "Container scanning configuration"
142
236
  }
143
237
  }
144
238
  },
@@ -0,0 +1,311 @@
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 typing import TYPE_CHECKING, Optional
26
+
27
+ from .scanossbase import ScanossBase
28
+
29
+ if TYPE_CHECKING:
30
+ from .scanoss_settings import ScanossSettings
31
+
32
+ MAX_RANKING_THRESHOLD = 10
33
+
34
+
35
+ class ScanSettingsBuilder(ScanossBase):
36
+ """Builder class for merging CLI arguments with scanoss.json settings file values.
37
+
38
+ This class implements an API for merging scan configuration
39
+ from multiple sources with the following priority order:
40
+ 1. settings.file_snippet section in scanoss.json (highest priority)
41
+ 2. settings section in scanoss.json (middle priority)
42
+ 3. CLI arguments (lowest priority - used as fallback)
43
+
44
+ Attributes:
45
+ proxy: Merged proxy host URL
46
+ url: Merged API base URL
47
+ ignore_cert_errors: Whether to ignore SSL certificate errors
48
+ min_snippet_hits: Minimum snippet hits required for matching
49
+ min_snippet_lines: Minimum snippet lines required for matching
50
+ honour_file_exts: Whether to honour file extensions during scanning
51
+ ranking: Whether ranking is enabled
52
+ ranking_threshold: Ranking threshold value
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ scanoss_settings: 'ScanossSettings | None',
58
+ debug: bool = False,
59
+ trace: bool = False,
60
+ quiet: bool = False,
61
+ ):
62
+ """Initialize the builder with optional scanoss settings.
63
+
64
+ Args:
65
+ scanoss_settings: ScanossSettings instance loaded from scanoss.json,
66
+ or None if no settings file is available.
67
+ debug: Enable debug output
68
+ trace: Enable trace output
69
+ quiet: Enable quiet mode
70
+ """
71
+ super().__init__(debug=debug, trace=trace, quiet=quiet)
72
+ self.scanoss_settings = scanoss_settings
73
+ # Merged values
74
+ self.proxy: Optional[str] = None
75
+ self.url: Optional[str] = None
76
+ self.ignore_cert_errors: bool = False
77
+ self.min_snippet_hits: Optional[int] = None
78
+ self.min_snippet_lines: Optional[int] = None
79
+ self.honour_file_exts: Optional[any] = None
80
+ self.ranking: Optional[any] = None
81
+ self.ranking_threshold: Optional[int] = None
82
+
83
+ def with_proxy(self, cli_value: str = None) -> 'ScanSettingsBuilder':
84
+ """Set proxy host with priority: file_snippet.proxy.host > settings.proxy.host > CLI.
85
+
86
+ Args:
87
+ cli_value: Proxy host from CLI argument (e.g., 'http://proxy:8080')
88
+
89
+ Returns:
90
+ Self for method chaining
91
+ """
92
+ self.proxy = self._merge_with_priority(
93
+ cli_value,
94
+ self._get_proxy_host(self._get_file_snippet_proxy()),
95
+ self._get_proxy_host(self._get_root_proxy())
96
+ )
97
+ return self
98
+
99
+ def with_url(self, cli_value: str = None) -> 'ScanSettingsBuilder':
100
+ """Set API base URL with priority: file_snippet.http_config.base_uri > settings.http_config.base_uri > CLI.
101
+
102
+ Args:
103
+ cli_value: API base URL from CLI argument (e.g., 'https://api.scanoss.com')
104
+
105
+ Returns:
106
+ Self for method chaining
107
+ """
108
+ self.url = self._merge_with_priority(
109
+ cli_value,
110
+ self._get_file_snippet_http_config_value('base_uri'),
111
+ self._get_http_config_value('base_uri')
112
+ )
113
+ return self
114
+
115
+ def with_ignore_cert_errors(self, cli_value: bool = False) -> 'ScanSettingsBuilder':
116
+ """Set ignore_cert_errors with priority: CLI True > file_snippet > settings > False.
117
+
118
+ Note: CLI value only takes effect if True (flag present). False means
119
+ the flag was not provided, so settings file values are checked.
120
+
121
+ Args:
122
+ cli_value: Whether to ignore SSL certificate errors from CLI flag
123
+
124
+ Returns:
125
+ Self for method chaining
126
+ """
127
+ result = self._merge_with_priority(
128
+ cli_value if cli_value else None,
129
+ self._get_file_snippet_http_config_value('ignore_cert_errors'),
130
+ self._get_http_config_value('ignore_cert_errors')
131
+ )
132
+ self.ignore_cert_errors = result if result is not None else False
133
+ return self
134
+
135
+ def with_min_snippet_hits(self, cli_value: int = None) -> 'ScanSettingsBuilder':
136
+ """Set minimum snippet hits with priority: settings.file_snippet.min_snippet_hits > CLI.
137
+
138
+ Minimum allowed value is 0. Values below 0 will be clamped and logged.
139
+
140
+ Args:
141
+ cli_value: Minimum snippet hits from CLI argument
142
+
143
+ Returns:
144
+ Self for method chaining
145
+ """
146
+ self.min_snippet_hits = self._merge_cli_with_settings(
147
+ cli_value,
148
+ self._get_file_snippet_setting('min_snippet_hits')
149
+ )
150
+ if self.min_snippet_hits is not None and self.min_snippet_hits < 0:
151
+ self.print_msg(
152
+ f'WARNING: min-snippet-hits value {self.min_snippet_hits} is below minimum allowed (0). '
153
+ f'Setting to 0.'
154
+ )
155
+ self.min_snippet_hits = 0
156
+ return self
157
+
158
+ def with_min_snippet_lines(self, cli_value: int = None) -> 'ScanSettingsBuilder':
159
+ """Set minimum snippet lines with priority: settings.file_snippet.min_snippet_lines > CLI.
160
+
161
+ Minimum allowed value is 0. Values below 0 will be clamped and logged.
162
+
163
+ Args:
164
+ cli_value: Minimum snippet lines from CLI argument
165
+
166
+ Returns:
167
+ Self for method chaining
168
+ """
169
+ self.min_snippet_lines = self._merge_cli_with_settings(
170
+ cli_value,
171
+ self._get_file_snippet_setting('min_snippet_lines')
172
+ )
173
+ if self.min_snippet_lines is not None and self.min_snippet_lines < 0:
174
+ self.print_msg(
175
+ f'WARNING: min-snippet-lines value {self.min_snippet_lines} is below minimum allowed (0). '
176
+ f'Setting to 0.'
177
+ )
178
+ self.min_snippet_lines = 0
179
+ return self
180
+
181
+ def with_honour_file_exts(self, cli_value: str = None) -> 'ScanSettingsBuilder':
182
+ """Set honour_file_exts with priority: settings.file_snippet.honour_file_exts > CLI.
183
+
184
+ Args:
185
+ cli_value: String 'true', 'false', or 'unset' from CLI argument
186
+
187
+ Returns:
188
+ Self for method chaining
189
+ """
190
+ self.honour_file_exts = self._merge_cli_with_settings(
191
+ cli_value,
192
+ self._get_file_snippet_setting('honour_file_exts')
193
+ )
194
+ ## Convert to boolean
195
+ if self.honour_file_exts is not None and self.honour_file_exts!= 'unset':
196
+ self.honour_file_exts = self._str_to_bool(self.honour_file_exts)
197
+ return self
198
+
199
+ def with_ranking(self, cli_value: str = None) -> 'ScanSettingsBuilder':
200
+ """Set ranking enabled with priority: settings.file_snippet.ranking_enabled > CLI.
201
+
202
+ Args:
203
+ cli_value: String 'true', 'false', or 'unset' from CLI argument
204
+
205
+ Returns:
206
+ Self for method chaining
207
+ """
208
+ self.ranking = self._merge_cli_with_settings(
209
+ cli_value,
210
+ self._get_file_snippet_setting('ranking_enabled')
211
+ )
212
+ if self.ranking is not None and self.ranking != 'unset':
213
+ self.ranking = self._str_to_bool(self.ranking)
214
+ return self
215
+
216
+ def with_ranking_threshold(self, cli_value: int = None) -> 'ScanSettingsBuilder':
217
+ """Set ranking threshold with priority: settings.file_snippet.ranking_threshold > CLI.
218
+
219
+ Valid range is -1 to 10. Values outside this range will be clamped and logged.
220
+
221
+ Args:
222
+ cli_value: Ranking threshold from CLI argument
223
+
224
+ Returns:
225
+ Self for method chaining
226
+ """
227
+ self.ranking_threshold = self._merge_cli_with_settings(
228
+ cli_value,
229
+ self._get_file_snippet_setting('ranking_threshold')
230
+ )
231
+ if self.ranking_threshold is not None:
232
+ if self.ranking_threshold > MAX_RANKING_THRESHOLD:
233
+ self.print_msg(
234
+ f'WARNING: ranking-threshold value {self.ranking_threshold} exceeds maximum allowed '
235
+ f'({MAX_RANKING_THRESHOLD}). Setting to {MAX_RANKING_THRESHOLD}.'
236
+ )
237
+ self.ranking_threshold = MAX_RANKING_THRESHOLD
238
+ elif self.ranking_threshold < -1:
239
+ self.print_msg(
240
+ f'WARNING: ranking-threshold value {self.ranking_threshold} is below minimum allowed (-1). '
241
+ f'Setting to -1.'
242
+ )
243
+ self.ranking_threshold = -1
244
+ return self
245
+
246
+ # Private helper methods
247
+ @staticmethod
248
+ def _merge_with_priority(cli_value, file_snippet_value, root_value):
249
+ """Merge with priority: file_snippet > root settings > CLI"""
250
+ if file_snippet_value is not None:
251
+ return file_snippet_value
252
+ if root_value is not None:
253
+ return root_value
254
+ return cli_value
255
+
256
+ @staticmethod
257
+ def _merge_cli_with_settings(cli_value, settings_value):
258
+ """Merge CLI value with settings, with settings taking priority over CLI.
259
+
260
+ Returns settings_value if not None, otherwise falls back to cli_value.
261
+ """
262
+ if settings_value is not None:
263
+ return settings_value
264
+ return cli_value
265
+
266
+
267
+ @staticmethod
268
+ def _str_to_bool(value: str) -> Optional[bool]:
269
+ """Convert string 'true'/'false' to boolean."""
270
+ if value is None:
271
+ return None
272
+ if isinstance(value, bool):
273
+ return value
274
+ return value.lower() == 'true'
275
+
276
+ # Methods to extract values from scanoss_settings
277
+ def _get_file_snippet_setting(self, key: str):
278
+ """Get a setting from the file_snippet section."""
279
+ if not self.scanoss_settings:
280
+ return None
281
+ return self.scanoss_settings.get_file_snippet_settings().get(key)
282
+
283
+ def _get_file_snippet_proxy(self):
284
+ """Get proxy config from file_snippet section."""
285
+ return self.scanoss_settings.get_file_snippet_proxy() if self.scanoss_settings else None
286
+
287
+ def _get_root_proxy(self):
288
+ """Get proxy config from root settings section."""
289
+ return self.scanoss_settings.get_proxy() if self.scanoss_settings else None
290
+
291
+ @staticmethod
292
+ def _get_proxy_host(proxy_config) -> Optional[str]:
293
+ """Extract host from proxy configuration dict."""
294
+ if proxy_config is None:
295
+ return None
296
+ host = proxy_config.get('host')
297
+ return host if host else None
298
+
299
+ def _get_http_config_value(self, key: str):
300
+ """Extract a value from http_config dict."""
301
+ if not self.scanoss_settings:
302
+ return None
303
+ config = self.scanoss_settings.get_http_config()
304
+ return config.get(key) if config else None
305
+
306
+ def _get_file_snippet_http_config_value(self, key: str):
307
+ """Extract a value from file_snippet http_config dict."""
308
+ if not self.scanoss_settings:
309
+ return None
310
+ config = self.scanoss_settings.get_file_snippet_http_config()
311
+ return config.get(key) if config else None