scanoss 1.15.0__tar.gz → 1.17.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 (73) hide show
  1. {scanoss-1.15.0/src/scanoss.egg-info → scanoss-1.17.0}/PKG-INFO +1 -1
  2. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/__init__.py +1 -1
  3. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/cli.py +95 -14
  4. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/cyclonedx.py +13 -0
  5. scanoss-1.17.0/src/scanoss/data/build_date.txt +1 -0
  6. scanoss-1.17.0/src/scanoss/inspection/__init__.py +23 -0
  7. scanoss-1.17.0/src/scanoss/inspection/copyleft.py +156 -0
  8. scanoss-1.17.0/src/scanoss/inspection/policy_check.py +341 -0
  9. scanoss-1.17.0/src/scanoss/inspection/undeclared_component.py +167 -0
  10. scanoss-1.17.0/src/scanoss/inspection/utils/license_utils.py +115 -0
  11. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/results.py +2 -2
  12. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scanossbase.py +10 -0
  13. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/spdxlite.py +2 -2
  14. {scanoss-1.15.0 → scanoss-1.17.0/src/scanoss.egg-info}/PKG-INFO +1 -1
  15. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss.egg-info/SOURCES.txt +6 -1
  16. scanoss-1.15.0/src/scanoss/data/build_date.txt +0 -1
  17. {scanoss-1.15.0 → scanoss-1.17.0}/LICENSE +0 -0
  18. {scanoss-1.15.0 → scanoss-1.17.0}/PACKAGE.md +0 -0
  19. {scanoss-1.15.0 → scanoss-1.17.0}/README.md +0 -0
  20. {scanoss-1.15.0 → scanoss-1.17.0}/pyproject.toml +0 -0
  21. {scanoss-1.15.0 → scanoss-1.17.0}/setup.cfg +0 -0
  22. {scanoss-1.15.0 → scanoss-1.17.0}/src/protoc_gen_swagger/__init__.py +0 -0
  23. {scanoss-1.15.0 → scanoss-1.17.0}/src/protoc_gen_swagger/options/__init__.py +0 -0
  24. {scanoss-1.15.0 → scanoss-1.17.0}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
  25. {scanoss-1.15.0 → scanoss-1.17.0}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
  26. {scanoss-1.15.0 → scanoss-1.17.0}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
  27. {scanoss-1.15.0 → scanoss-1.17.0}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
  28. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/__init__.py +0 -0
  29. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/common/__init__.py +0 -0
  30. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/common/v2/__init__.py +0 -0
  31. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
  32. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
  33. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/components/__init__.py +0 -0
  34. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/components/v2/__init__.py +0 -0
  35. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
  36. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
  37. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
  38. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
  39. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/dependencies/__init__.py +0 -0
  40. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
  41. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
  42. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
  43. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/scanning/__init__.py +0 -0
  44. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/scanning/v2/__init__.py +0 -0
  45. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
  46. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
  47. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/semgrep/__init__.py +0 -0
  48. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
  49. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
  50. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
  51. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
  52. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
  53. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
  54. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
  55. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/components.py +0 -0
  56. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/csvoutput.py +0 -0
  57. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/data/spdx-exceptions.json +0 -0
  58. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/data/spdx-licenses.json +0 -0
  59. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/filecount.py +0 -0
  60. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scancodedeps.py +0 -0
  61. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scanner.py +0 -0
  62. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scanoss_settings.py +0 -0
  63. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scanossapi.py +0 -0
  64. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scanossgrpc.py +0 -0
  65. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scanpostprocessor.py +0 -0
  66. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/scantype.py +0 -0
  67. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/threadeddependencies.py +0 -0
  68. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/threadedscanning.py +0 -0
  69. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss/winnowing.py +0 -0
  70. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss.egg-info/dependency_links.txt +0 -0
  71. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss.egg-info/entry_points.txt +0 -0
  72. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss.egg-info/requires.txt +0 -0
  73. {scanoss-1.15.0 → scanoss-1.17.0}/src/scanoss.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scanoss
3
- Version: 1.15.0
3
+ Version: 1.17.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 @@
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = "1.15.0"
25
+ __version__ = "1.17.0"
@@ -25,15 +25,13 @@ import argparse
25
25
  import os
26
26
  from pathlib import Path
27
27
  import sys
28
- from array import array
29
-
30
28
  import pypac
31
-
29
+ from scanoss.inspection.copyleft import Copyleft
30
+ from scanoss.inspection.undeclared_component import UndeclaredComponent
32
31
  from .threadeddependencies import SCOPE
33
- from .scanner import Scanner
34
32
  from .scanoss_settings import ScanossSettings
35
33
  from .scancodedeps import ScancodeDeps
36
- from .scanner import FAST_WINNOWING, Scanner
34
+ from .scanner import Scanner
37
35
  from .scantype import ScanType
38
36
  from .filecount import FileCount
39
37
  from .cyclonedx import CycloneDx
@@ -171,19 +169,19 @@ def setup_args() -> None:
171
169
  # Component Sub-command: component crypto
172
170
  c_crypto = comp_sub.add_parser('crypto', aliases=['cr'],
173
171
  description=f'Show Cryptographic algorithms: {__version__}',
174
- help='Retreive cryptographic algorithms for the given components')
172
+ help='Retrieve cryptographic algorithms for the given components')
175
173
  c_crypto.set_defaults(func=comp_crypto)
176
174
 
177
175
  # Component Sub-command: component vulns
178
176
  c_vulns = comp_sub.add_parser('vulns', aliases=['vulnerabilities', 'vu'],
179
177
  description=f'Show Vulnerability details: {__version__}',
180
- help='Retreive vulnerabilities for the given components')
178
+ help='Retrieve vulnerabilities for the given components')
181
179
  c_vulns.set_defaults(func=comp_vulns)
182
180
 
183
181
  # Component Sub-command: component semgrep
184
182
  c_semgrep = comp_sub.add_parser('semgrep', aliases=['sp'],
185
183
  description=f'Show Semgrep findings: {__version__}',
186
- help='Retreive semgrep issues/findings for the given components')
184
+ help='Retrieve semgrep issues/findings for the given components')
187
185
  c_semgrep.set_defaults(func=comp_semgrep)
188
186
 
189
187
  # Component Sub-command: component search
@@ -299,6 +297,31 @@ def setup_args() -> None:
299
297
  )
300
298
  p_results.set_defaults(func=results)
301
299
 
300
+
301
+ # Sub-command: inspect
302
+ p_inspect = subparsers.add_parser('inspect', aliases=['insp', 'ins'],
303
+ description=f'Inspect results: {__version__}',
304
+ help='Inspect results')
305
+ # Sub-parser: inspect
306
+ p_inspect_sub = p_inspect.add_subparsers(title='Inspect Commands', dest='subparsercmd',
307
+ description='Inspect sub-commands', help='Inspect sub-commands')
308
+ # Inspect Sub-command: inspect copyleft
309
+ p_copyleft = p_inspect_sub.add_parser('copyleft', aliases=['cp'],description="Inspect for copyleft licenses", help='Inspect for copyleft licenses')
310
+ p_copyleft.add_argument('--include', help='List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list.')
311
+ p_copyleft.add_argument('--exclude', help='List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list.')
312
+ p_copyleft.add_argument('--explicit', help='Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list.s')
313
+ p_copyleft.set_defaults(func=inspect_copyleft)
314
+
315
+ # Inspect Sub-command: inspect undeclared
316
+ p_undeclared = p_inspect_sub.add_parser('undeclared', aliases=['un'],description="Inspect for undeclared components", help='Inspect for undeclared components')
317
+ p_undeclared.set_defaults(func=inspect_undeclared)
318
+
319
+ for p in [p_copyleft, p_undeclared]:
320
+ p.add_argument('-i', '--input', nargs='?', help='Path to results file')
321
+ p.add_argument('-f', '--format',required=False ,choices=['json', 'md'], default='json', help='Output format (default: json)')
322
+ p.add_argument('-o', '--output', type=str, help='Save details into a file')
323
+ p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
324
+
302
325
  # Global Scan command options
303
326
  for p in [p_scan]:
304
327
  p.add_argument('--apiurl', type=str,
@@ -344,7 +367,7 @@ def setup_args() -> None:
344
367
 
345
368
  # Help/Trace command options
346
369
  for p in [p_scan, p_wfp, p_dep, p_fc, p_cnv, p_c_loc, p_c_dwnld, p_p_proxy, c_crypto, c_vulns, c_search,
347
- c_versions, c_semgrep, p_results]:
370
+ c_versions, c_semgrep, p_results, p_undeclared, p_copyleft]:
348
371
  p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
349
372
  p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
350
373
  p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
@@ -357,9 +380,10 @@ def setup_args() -> None:
357
380
  parser.print_help() # No sub command subcommand, print general help
358
381
  exit(1)
359
382
  else:
360
- if (args.subparser == 'utils' or args.subparser == 'ut' or
361
- args.subparser == 'component' or args.subparser == 'comp') \
362
- and not args.subparsercmd:
383
+ if ((args.subparser == 'utils' or args.subparser == 'ut' or
384
+ args.subparser == 'component' or args.subparser == 'comp' or
385
+ args.subparser == 'inspect' or args.subparser == 'insp' or args.subparser == 'ins')
386
+ and not args.subparsercmd):
363
387
  parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
364
388
  exit(1)
365
389
  args.func(parser, args) # Execute the function associated with the sub-command
@@ -778,6 +802,64 @@ def convert(parser, args):
778
802
  if not success:
779
803
  exit(1)
780
804
 
805
+ def inspect_copyleft(parser, args):
806
+ """
807
+ Run the "inspect" sub-command
808
+ Parameters
809
+ ----------
810
+ parser: ArgumentParser
811
+ command line parser object
812
+ args: Namespace
813
+ Parsed arguments
814
+ """
815
+ if args.input is None:
816
+ print_stderr('Please specify an input file to inspect')
817
+ parser.parse_args([args.subparser, args.subparsercmd, '-h'])
818
+ exit(1)
819
+ output: str = None
820
+ if args.output:
821
+ output = args.output
822
+ open(output, 'w').close()
823
+
824
+ status_output: str = None
825
+ if args.status:
826
+ status_output = args.status
827
+ open(status_output, 'w').close()
828
+
829
+ i_copyleft = Copyleft(debug=args.debug, trace=args.trace, quiet=args.quiet, filepath=args.input,
830
+ format_type=args.format, status=status_output, output=output, include=args.include,
831
+ exclude=args.exclude, explicit=args.explicit)
832
+ status, _ = i_copyleft.run()
833
+ sys.exit(status)
834
+
835
+ def inspect_undeclared(parser, args):
836
+ """
837
+ Run the "inspect" sub-command
838
+ Parameters
839
+ ----------
840
+ parser: ArgumentParser
841
+ command line parser object
842
+ args: Namespace
843
+ Parsed arguments
844
+ """
845
+ if args.input is None:
846
+ print_stderr('Please specify an input file to inspect')
847
+ parser.parse_args([args.subparser, args.subparsercmd, '-h'])
848
+ exit(1)
849
+ output: str = None
850
+ if args.output:
851
+ output = args.output
852
+ open(output, 'w').close()
853
+
854
+ status_output: str = None
855
+ if args.status:
856
+ status_output = args.status
857
+ open(status_output, 'w').close()
858
+ i_undeclared = UndeclaredComponent(debug=args.debug, trace=args.trace, quiet=args.quiet,
859
+ filepath=args.input, format_type=args.format,
860
+ status=status_output, output=output)
861
+ status, _ = i_undeclared.run()
862
+ sys.exit(status)
781
863
 
782
864
  def utils_certloc(*_):
783
865
  """
@@ -787,7 +869,6 @@ def utils_certloc(*_):
787
869
  import certifi
788
870
  print(f'CA Cert File: {certifi.where()}')
789
871
 
790
-
791
872
  def utils_cert_download(_, args):
792
873
  """
793
874
  Run the "utils cert-download" sub-command
@@ -820,7 +901,7 @@ def utils_cert_download(_, args):
820
901
  else:
821
902
  cn = cert_components.get('CN')
822
903
  if not args.quiet:
823
- print_stderr(f'Centificate {index} - CN: {cn}')
904
+ print_stderr(f'Certificate {index} - CN: {cn}')
824
905
  if sys.version_info[0] >= 3:
825
906
  print((crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')).strip(), file=file) # Print the downloaded PEM certificate
826
907
  else:
@@ -25,6 +25,9 @@ import json
25
25
  import os.path
26
26
  import sys
27
27
  import uuid
28
+ import datetime
29
+
30
+ from . import __version__
28
31
 
29
32
  from .scanossbase import ScanossBase
30
33
  from .spdxlite import SpdxLite
@@ -186,6 +189,16 @@ class CycloneDx(ScanossBase):
186
189
  'specVersion': '1.4',
187
190
  'serialNumber': f'urn:uuid:{uuid.uuid4()}',
188
191
  'version': 1,
192
+ 'metadata': {
193
+ 'timestamp': datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
194
+ 'tools': [
195
+ {
196
+ 'vendor': 'SCANOSS',
197
+ 'name': 'scanoss-py',
198
+ 'version': __version__,
199
+ }
200
+ ]
201
+ },
189
202
  'components': [],
190
203
  'vulnerabilities': []
191
204
  }
@@ -0,0 +1 @@
1
+ date: 20241023122954, utime: 1729686594
@@ -0,0 +1,23 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2024, 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
+ """
@@ -0,0 +1,156 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2024, 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
+ import json
25
+ from typing import Dict, Any
26
+ from scanoss.inspection.policy_check import PolicyCheck, PolicyStatus
27
+
28
+ class Copyleft(PolicyCheck):
29
+ """
30
+ SCANOSS Copyleft class
31
+ Inspects components for copyleft licenses
32
+ """
33
+
34
+ def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False, filepath: str = None,
35
+ format_type: str = 'json', status: str = None, output: str = None, include: str = None,
36
+ exclude: str = None, explicit: str = None):
37
+ """
38
+ Initialize the Copyleft class.
39
+
40
+ :param debug: Enable debug mode
41
+ :param trace: Enable trace mode (default True)
42
+ :param quiet: Enable quiet mode
43
+ :param filepath: Path to the file containing component data
44
+ :param format_type: Output format ('json' or 'md')
45
+ :param status: Path to save the status output
46
+ :param output: Path to save detailed output
47
+ :param include: Licenses to include in the analysis
48
+ :param exclude: Licenses to exclude from the analysis
49
+ :param explicit: Explicitly defined licenses
50
+ """
51
+ super().__init__(debug, trace, quiet, filepath, format_type, status, output, name='Copyleft Policy')
52
+ self.license_util.init(include, exclude, explicit)
53
+ self.filepath = filepath
54
+ self.format = format
55
+ self.output = output
56
+ self.status = status
57
+ self.include = include
58
+ self.exclude = exclude
59
+ self.explicit = explicit
60
+
61
+ def _json(self, components: list) -> Dict[str, Any]:
62
+ """
63
+ Format the components with copyleft licenses as JSON.
64
+
65
+ :param components: List of components with copyleft licenses
66
+ :return: Dictionary with formatted JSON details and summary
67
+ """
68
+ details = {}
69
+ if len(components) > 0:
70
+ details = { 'components': components }
71
+ return {
72
+ 'details': json.dumps(details, indent=2),
73
+ 'summary': f'{len(components)} component(s) with copyleft licenses were found.'
74
+ }
75
+
76
+ def _markdown(self, components: list) -> Dict[str,Any]:
77
+ """
78
+ Format the components with copyleft licenses as Markdown.
79
+
80
+ :param components: List of components with copyleft licenses
81
+ :return: Dictionary with formatted Markdown details and summary
82
+ """
83
+ headers = ['Component', 'Version', 'License', 'URL', 'Copyleft']
84
+ centered_columns = [1, 4]
85
+ rows: [[]]= []
86
+ for component in components:
87
+ for lic in component['licenses']:
88
+ row = [
89
+ component['purl'],
90
+ component['version'],
91
+ lic['spdxid'],
92
+ lic['url'],
93
+ 'YES' if lic['copyleft'] else 'NO'
94
+ ]
95
+ rows.append(row)
96
+ # End license loop
97
+ # End component loop
98
+ return {
99
+ 'details': f'### Copyleft licenses\n{self.generate_table(headers,rows,centered_columns)}',
100
+ 'summary' : f'{len(components)} component(s) with copyleft licenses were found.'
101
+ }
102
+
103
+ def _filter_components_with_copyleft_licenses(self, components: list) -> list:
104
+ """
105
+ Filter the components list to include only those with copyleft licenses.
106
+
107
+ :param components: List of all components
108
+ :return: List of components with copyleft licenses
109
+ """
110
+ filtered_components = []
111
+ for component in components:
112
+ copyleft_licenses = [lic for lic in component['licenses'] if lic['copyleft']]
113
+ if copyleft_licenses:
114
+ filtered_component = component
115
+ filtered_component['licenses'] = copyleft_licenses
116
+ del filtered_component['status']
117
+ filtered_components.append(filtered_component)
118
+ # End component loop
119
+ self.print_debug(f'Copyleft components: {filtered_components}')
120
+ return filtered_components
121
+
122
+ def run(self):
123
+ """
124
+ Run the copyleft license inspection process.
125
+
126
+ This method performs the following steps:
127
+ 1. Get all components
128
+ 2. Filter components with copyleft licenses
129
+ 3. Format the results
130
+ 4. Save the output to files if required
131
+
132
+ :return: Dictionary containing the inspection results
133
+ """
134
+ self._debug()
135
+ # Get the components from the results
136
+ components = self._get_components()
137
+ if components is None:
138
+ return PolicyStatus.ERROR.value, {}
139
+ # Get a list of copyleft components if they exist
140
+ copyleft_components = self._filter_components_with_copyleft_licenses(components)
141
+ # Get a formatter for the output results
142
+ formatter = self._get_formatter()
143
+ if formatter is None:
144
+ return PolicyStatus.ERROR.value, {}
145
+ # Format the results
146
+ results = formatter(copyleft_components)
147
+ ## Save outputs if required
148
+ self.print_to_file_or_stdout(results['details'], self.output)
149
+ self.print_to_file_or_stderr(results['summary'], self.status)
150
+ # Check to see if we have policy violations
151
+ if len(copyleft_components) <= 0:
152
+ return PolicyStatus.FAIL.value, results
153
+ return PolicyStatus.SUCCESS.value, results
154
+ #
155
+ # End of Copyleft Class
156
+ #