scanoss 1.27.1__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 (79) hide show
  1. protoc_gen_swagger/options/annotations_pb2.py +18 -12
  2. protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
  3. protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
  4. protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
  5. protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
  6. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
  7. scanoss/__init__.py +1 -1
  8. scanoss/api/common/v2/scanoss_common_pb2.py +49 -22
  9. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
  10. scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
  11. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
  12. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -47
  13. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +650 -33
  14. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -37
  15. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +64 -12
  16. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +74 -31
  17. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +252 -13
  18. scanoss/api/licenses/__init__.py +23 -0
  19. scanoss/api/licenses/v2/__init__.py +23 -0
  20. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
  21. scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
  22. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +32 -21
  23. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
  24. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
  25. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
  26. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
  27. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
  28. scanoss/cli.py +1000 -186
  29. scanoss/components.py +80 -50
  30. scanoss/constants.py +7 -1
  31. scanoss/cryptography.py +89 -55
  32. scanoss/csvoutput.py +13 -7
  33. scanoss/cyclonedx.py +141 -9
  34. scanoss/data/build_date.txt +1 -1
  35. scanoss/data/osadl-copyleft.json +133 -0
  36. scanoss/delta.py +197 -0
  37. scanoss/export/__init__.py +23 -0
  38. scanoss/export/dependency_track.py +227 -0
  39. scanoss/file_filters.py +2 -163
  40. scanoss/filecount.py +37 -38
  41. scanoss/gitlabqualityreport.py +214 -0
  42. scanoss/header_filter.py +563 -0
  43. scanoss/inspection/policy_check/__init__.py +0 -0
  44. scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  45. scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
  46. scanoss/inspection/{policy_check.py → policy_check/policy_check.py} +65 -72
  47. scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  48. scanoss/inspection/{copyleft.py → policy_check/scanoss/copyleft.py} +89 -73
  49. scanoss/inspection/{undeclared_component.py → policy_check/scanoss/undeclared_component.py} +52 -46
  50. scanoss/inspection/summary/__init__.py +0 -0
  51. scanoss/inspection/summary/component_summary.py +170 -0
  52. scanoss/inspection/{license_summary.py → summary/license_summary.py} +62 -12
  53. scanoss/inspection/summary/match_summary.py +341 -0
  54. scanoss/inspection/utils/file_utils.py +44 -0
  55. scanoss/inspection/utils/license_utils.py +57 -71
  56. scanoss/inspection/utils/markdown_utils.py +63 -0
  57. scanoss/inspection/{inspect_base.py → utils/scan_result_processor.py} +53 -67
  58. scanoss/osadl.py +125 -0
  59. scanoss/scanner.py +135 -253
  60. scanoss/scanners/folder_hasher.py +47 -32
  61. scanoss/scanners/scanner_hfh.py +50 -18
  62. scanoss/scanoss_settings.py +33 -3
  63. scanoss/scanossapi.py +23 -25
  64. scanoss/scanossbase.py +1 -1
  65. scanoss/scanossgrpc.py +543 -289
  66. scanoss/services/dependency_track_service.py +132 -0
  67. scanoss/spdxlite.py +11 -4
  68. scanoss/threadeddependencies.py +19 -18
  69. scanoss/threadedscanning.py +10 -0
  70. scanoss/utils/scanoss_scan_results_utils.py +41 -0
  71. scanoss/winnowing.py +71 -19
  72. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/METADATA +8 -5
  73. scanoss-1.43.1.dist-info/RECORD +110 -0
  74. scanoss/inspection/component_summary.py +0 -94
  75. scanoss-1.27.1.dist-info/RECORD +0 -87
  76. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/WHEEL +0 -0
  77. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
  78. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/licenses/LICENSE +0 -0
  79. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
scanoss/cli.py CHANGED
@@ -25,6 +25,7 @@ SPDX-License-Identifier: MIT
25
25
  import argparse
26
26
  import os
27
27
  import sys
28
+ import traceback
28
29
  from dataclasses import asdict
29
30
  from pathlib import Path
30
31
  from typing import List
@@ -32,8 +33,8 @@ from typing import List
32
33
  import pypac
33
34
 
34
35
  from scanoss.cryptography import Cryptography, create_cryptography_config_from_args
35
- from scanoss.inspection.component_summary import ComponentSummary
36
- from scanoss.inspection.license_summary import LicenseSummary
36
+ from scanoss.delta import Delta
37
+ from scanoss.export.dependency_track import DependencyTrackExporter
37
38
  from scanoss.scanners.container_scanner import (
38
39
  DEFAULT_SYFT_COMMAND,
39
40
  DEFAULT_SYFT_TIMEOUT,
@@ -54,18 +55,30 @@ from . import __version__
54
55
  from .components import Components
55
56
  from .constants import (
56
57
  DEFAULT_API_TIMEOUT,
58
+ DEFAULT_COPYLEFT_LICENSE_SOURCES,
59
+ DEFAULT_HFH_DEPTH,
60
+ DEFAULT_HFH_MIN_ACCEPTED_SCORE,
57
61
  DEFAULT_HFH_RANK_THRESHOLD,
62
+ DEFAULT_HFH_RECURSIVE_THRESHOLD,
58
63
  DEFAULT_POST_SIZE,
59
64
  DEFAULT_RETRY,
60
65
  DEFAULT_TIMEOUT,
61
66
  MIN_TIMEOUT,
62
67
  PYTHON_MAJOR_VERSION,
68
+ VALID_LICENSE_SOURCES,
63
69
  )
64
70
  from .csvoutput import CsvOutput
65
71
  from .cyclonedx import CycloneDx
66
72
  from .filecount import FileCount
67
- from .inspection.copyleft import Copyleft
68
- from .inspection.undeclared_component import UndeclaredComponent
73
+ from .gitlabqualityreport import GitLabQualityReport
74
+ from .inspection.policy_check.dependency_track.project_violation import (
75
+ DependencyTrackProjectViolationPolicyCheck,
76
+ )
77
+ from .inspection.policy_check.scanoss.copyleft import Copyleft
78
+ from .inspection.policy_check.scanoss.undeclared_component import UndeclaredComponent
79
+ from .inspection.summary.component_summary import ComponentSummary
80
+ from .inspection.summary.license_summary import LicenseSummary
81
+ from .inspection.summary.match_summary import MatchSummary
69
82
  from .results import Results
70
83
  from .scancodedeps import ScancodeDeps
71
84
  from .scanner import FAST_WINNOWING, Scanner
@@ -157,7 +170,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
157
170
  default=DEFAULT_RETRY,
158
171
  help='Retry limit for API communication (optional - default 5)',
159
172
  )
160
- p_scan.add_argument('--no-wfp-output', action='store_true', help='Skip WFP file generation')
161
173
  p_scan.add_argument('--dependencies', '-D', action='store_true', help='Add Dependency scanning')
162
174
  p_scan.add_argument('--dependencies-only', action='store_true', help='Run Dependency scanning only')
163
175
  p_scan.add_argument(
@@ -174,6 +186,10 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
174
186
  )
175
187
  p_scan.add_argument('--dep-scope-inc', '-dsi', type=str, help='Include dependencies with declared scopes')
176
188
  p_scan.add_argument('--dep-scope-exc', '-dse', type=str, help='Exclude dependencies with declared scopes')
189
+ p_scan.add_argument(
190
+ '--no-wfp-output', action='store_true',
191
+ help='DEPRECATED: Scans no longer generate scanner_output.wfp. Use "fingerprint -o" to create WFP files.'
192
+ )
177
193
 
178
194
  # Sub-command: fingerprint
179
195
  p_wfp = subparsers.add_parser(
@@ -274,7 +290,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
274
290
  '--format',
275
291
  '-f',
276
292
  type=str,
277
- choices=['cyclonedx', 'spdxlite', 'csv'],
293
+ choices=['cyclonedx', 'spdxlite', 'csv', 'glc-codequality'],
278
294
  default='spdxlite',
279
295
  help='Output format (optional - default: spdxlite)',
280
296
  )
@@ -306,6 +322,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
306
322
  )
307
323
  c_vulns.set_defaults(func=comp_vulns)
308
324
 
325
+ # Component Sub-command: component licenses
326
+ c_licenses = comp_sub.add_parser(
327
+ 'licenses',
328
+ aliases=['lics'],
329
+ description=f'Show License details: {__version__}',
330
+ help='Retrieve licenses for the given components',
331
+ )
332
+ c_licenses.set_defaults(func=comp_licenses)
333
+
309
334
  # Component Sub-command: component semgrep
310
335
  c_semgrep = comp_sub.add_parser(
311
336
  'semgrep',
@@ -407,7 +432,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
407
432
  p_crypto_versions_in_range.set_defaults(func=crypto_versions_in_range)
408
433
 
409
434
  # Common purl Component sub-command options
410
- for p in [c_vulns, c_semgrep, c_provenance, p_crypto_algorithms, p_crypto_hints, p_crypto_versions_in_range]:
435
+ for p in [
436
+ c_vulns,
437
+ c_semgrep,
438
+ c_provenance,
439
+ p_crypto_algorithms,
440
+ p_crypto_hints,
441
+ p_crypto_versions_in_range,
442
+ c_licenses,
443
+ ]:
411
444
  p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
412
445
  p.add_argument('--input', '-i', type=str, help='Input file name')
413
446
 
@@ -421,6 +454,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
421
454
  p_crypto_algorithms,
422
455
  p_crypto_hints,
423
456
  p_crypto_versions_in_range,
457
+ c_licenses,
424
458
  ]:
425
459
  p.add_argument(
426
460
  '--timeout',
@@ -534,76 +568,355 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
534
568
  )
535
569
  p_results.set_defaults(func=results)
536
570
 
537
- ########################################### INSPECT SUBCOMMAND ###########################################
538
- # Sub-command: inspect
571
+ # =========================================================================
572
+ # INSPECT SUBCOMMAND - Analysis and validation of scan results
573
+ # =========================================================================
574
+
575
+ # Main inspect parser - provides tools for analyzing scan results
539
576
  p_inspect = subparsers.add_parser(
540
- 'inspect', aliases=['insp', 'ins'], description=f'Inspect results: {__version__}', help='Inspect results'
577
+ 'inspect',
578
+ aliases=['insp', 'ins'],
579
+ description=f'Inspect and analyse scan results: {__version__}',
580
+ help='Inspect and analyse scan results',
541
581
  )
542
- # Sub-parser: inspect
582
+
583
+ # Inspect sub-commands parser
543
584
  p_inspect_sub = p_inspect.add_subparsers(
544
- title='Inspect Commands', dest='subparsercmd', description='Inspect sub-commands', help='Inspect sub-commands'
585
+ title='Inspect Commands',
586
+ dest='subparsercmd',
587
+ description='Available inspection sub-commands',
588
+ help='Choose an inspection type',
545
589
  )
546
590
 
547
- ####### INSPECT: Copyleft ######
548
- # Inspect Sub-command: inspect copyleft
549
- p_copyleft = p_inspect_sub.add_parser(
550
- 'copyleft', aliases=['cp'], description='Inspect for copyleft licenses', help='Inspect for copyleft licenses'
591
+ # -------------------------------------------------------------------------
592
+ # RAW RESULTS INSPECTION - Analyse raw scan output
593
+ # -------------------------------------------------------------------------
594
+
595
+ # Raw results parser - handles inspection of unprocessed scan results
596
+ p_inspect_raw = p_inspect_sub.add_parser(
597
+ 'raw',
598
+ description='Inspect and analyse SCANOSS raw scan results',
599
+ help='Analyse raw scan results for various compliance issues',
551
600
  )
552
601
 
553
- ####### INSPECT: License Summary ######
554
- # Inspect Sub-command: inspect license summary
555
- p_license_summary = p_inspect_sub.add_parser(
556
- 'license-summary', aliases=['lic-summary', 'licsum'], description='Get license summary',
557
- help='Get detected license summary from scan results'
602
+ # Raw results sub-commands parser
603
+ p_inspect_raw_sub = p_inspect_raw.add_subparsers(
604
+ title='Raw Results Inspection Commands',
605
+ dest='subparser_subcmd',
606
+ description='Tools for analyzing raw scan results',
607
+ help='Choose a raw results analysis type',
558
608
  )
559
609
 
560
- p_component_summary = p_inspect_sub.add_parser(
561
- 'component-summary', aliases=['comp-summary', 'compsum'], description='Get component summary',
562
- help='Get detected component summary from scan results'
610
+ # Copyleft license inspection - identifies copyleft license violations
611
+ p_inspect_raw_copyleft = p_inspect_raw_sub.add_parser(
612
+ 'copyleft',
613
+ aliases=['cp'],
614
+ description='Identify components with copyleft licenses that may require compliance action',
615
+ help='Find copyleft license violations',
563
616
  )
564
617
 
565
- ####### INSPECT: Undeclared components ######
566
- # Inspect Sub-command: inspect undeclared
567
- p_undeclared = p_inspect_sub.add_parser(
618
+ # License summary inspection - provides overview of all detected licenses
619
+ p_inspect_raw_license_summary = p_inspect_raw_sub.add_parser(
620
+ 'license-summary',
621
+ aliases=['lic-summary', 'licsum'],
622
+ description='Generate comprehensive summary of all licenses found in scan results',
623
+ help='Generate license summary report',
624
+ )
625
+
626
+ # Component summary inspection - provides overview of all detected components
627
+ p_inspect_raw_component_summary = p_inspect_raw_sub.add_parser(
628
+ 'component-summary',
629
+ aliases=['comp-summary', 'compsum'],
630
+ description='Generate comprehensive summary of all components found in scan results',
631
+ help='Generate component summary report',
632
+ )
633
+
634
+ # Undeclared components inspection - finds components not declared in SBOM
635
+ p_inspect_raw_undeclared = p_inspect_raw_sub.add_parser(
568
636
  'undeclared',
569
637
  aliases=['un'],
570
- description='Inspect for undeclared components',
571
- help='Inspect for undeclared components',
638
+ description='Identify components present in code but not declared in SBOM files',
639
+ help='Find undeclared components',
572
640
  )
573
- p_undeclared.add_argument(
641
+ # SBOM format option for undeclared components inspection
642
+ p_inspect_raw_undeclared.add_argument(
574
643
  '--sbom-format',
575
644
  required=False,
576
645
  choices=['legacy', 'settings'],
577
646
  default='settings',
578
- help='Sbom format for status output',
647
+ help='SBOM format type for comparison: legacy or settings (default)',
579
648
  )
580
649
 
581
- # Add common commands for inspect copyleft and license summary
582
- for p in [p_copyleft, p_license_summary]:
583
- p.add_argument(
584
- '--include',
585
- help='List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list.',
586
- )
650
+ # -------------------------------------------------------------------------
651
+ # BACKWARD COMPATIBILITY - Support old inspect command format
652
+ # -------------------------------------------------------------------------
653
+
654
+ # Legacy copyleft inspection - backward compatibility for 'scanoss-py inspect copyleft'
655
+ p_inspect_legacy_copyleft = p_inspect_sub.add_parser(
656
+ 'copyleft',
657
+ aliases=['cp'],
658
+ description='Identify components with copyleft licenses that may require compliance action',
659
+ help='Find copyleft license violations (legacy format)',
660
+ )
661
+
662
+ # Legacy undeclared components inspection - backward compatibility for 'scanoss-py inspect undeclared'
663
+ p_inspect_legacy_undeclared = p_inspect_sub.add_parser(
664
+ 'undeclared',
665
+ aliases=['un'],
666
+ description='Identify components present in code but not declared in SBOM files',
667
+ help='Find undeclared components (legacy format)',
668
+ )
669
+
670
+ # SBOM format option for legacy undeclared components inspection
671
+ p_inspect_legacy_undeclared.add_argument(
672
+ '--sbom-format',
673
+ required=False,
674
+ choices=['legacy', 'settings'],
675
+ default='settings',
676
+ help='SBOM format type for comparison: legacy or settings (default)',
677
+ )
678
+
679
+ # Legacy license summary inspection - backward compatibility for 'scanoss-py inspect license-summary'
680
+ p_inspect_legacy_license_summary = p_inspect_sub.add_parser(
681
+ 'license-summary',
682
+ aliases=['lic-summary', 'licsum'],
683
+ description='Generate comprehensive summary of all licenses found in scan results',
684
+ help='Generate license summary report (legacy format)',
685
+ )
686
+
687
+ # Legacy component summary inspection - backward compatibility for 'scanoss-py inspect component-summary'
688
+ p_inspect_legacy_component_summary = p_inspect_sub.add_parser(
689
+ 'component-summary',
690
+ aliases=['comp-summary', 'compsum'],
691
+ description='Generate comprehensive summary of all components found in scan results',
692
+ help='Generate component summary report (legacy format)',
693
+ )
694
+
695
+ # Applies the same configuration to both legacy and raw versions
696
+ # License filtering options - common to (legacy) copyleft and license summary commands
697
+ for p in [
698
+ p_inspect_raw_copyleft,
699
+ p_inspect_raw_license_summary,
700
+ p_inspect_legacy_copyleft,
701
+ p_inspect_legacy_license_summary,
702
+ ]:
703
+ p.add_argument('--include', help='Additional licenses to include in analysis (comma-separated list)')
704
+ p.add_argument('--exclude', help='Licenses to exclude from analysis (comma-separated list)')
705
+ p.add_argument('--explicit', help='Use only these specific licenses for analysis (comma-separated list)')
706
+
707
+ # License source filtering
708
+ for p in [p_inspect_raw_copyleft, p_inspect_legacy_copyleft]:
587
709
  p.add_argument(
588
- '--exclude',
589
- help='List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list.',
710
+ '-ls', '--license-sources',
711
+ action='extend',
712
+ nargs='+',
713
+ choices=VALID_LICENSE_SOURCES,
714
+ help=f'Specify which license sources to check for copyleft violations. Each license object in scan results '
715
+ f'has a source field indicating its origin. Default: {", ".join(DEFAULT_COPYLEFT_LICENSE_SOURCES)}',
590
716
  )
717
+
718
+ # Common options for (legacy) copyleft and undeclared component inspection
719
+ for p in [p_inspect_raw_copyleft, p_inspect_raw_undeclared, p_inspect_legacy_copyleft, p_inspect_legacy_undeclared]:
720
+ p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
591
721
  p.add_argument(
592
- '--explicit',
593
- help='Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list.s',
722
+ '-f',
723
+ '--format',
724
+ required=False,
725
+ choices=['json', 'md', 'jira_md'],
726
+ default='json',
727
+ help='Output format: json (default), md (Markdown), or jira_md (JIRA Markdown)',
594
728
  )
729
+ p.add_argument('-o', '--output', type=str, help='Save detailed results to specified file')
730
+ p.add_argument('-s', '--status', type=str, help='Save summary status report to Markdown file')
595
731
 
596
- # Add common commands for inspect copyleft and license summary
597
- for p in [p_license_summary, p_component_summary]:
598
- p.add_argument('-i', '--input', nargs='?', help='Path to results file')
599
- p.add_argument('-o', '--output', type=str, help='Save summary into a file')
600
-
601
- p_undeclared.set_defaults(func=inspect_undeclared)
602
- p_copyleft.set_defaults(func=inspect_copyleft)
603
- p_license_summary.set_defaults(func=inspect_license_summary)
604
- p_component_summary.set_defaults(func=inspect_component_summary)
732
+ # Common options for (legacy) license and component summary commands
733
+ for p in [
734
+ p_inspect_raw_license_summary,
735
+ p_inspect_raw_component_summary,
736
+ p_inspect_legacy_license_summary,
737
+ p_inspect_legacy_component_summary,
738
+ ]:
739
+ p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
740
+ p.add_argument('-o', '--output', type=str, help='Save summary report to specified file')
741
+
742
+ # -------------------------------------------------------------------------
743
+ # DEPENDENCY TRACK INSPECTION - Analyse Dependency Track project data
744
+ # -------------------------------------------------------------------------
745
+
746
+ # Dependency Track parser - handles inspection of DT project status and violations
747
+ p_dep_track_sub = p_inspect_sub.add_parser(
748
+ 'dependency-track',
749
+ aliases=['dt'],
750
+ description='Inspect and analyse Dependency Track project status and policy violations',
751
+ help='Analyse Dependency Track projects',
752
+ )
753
+
754
+ # Dependency Track sub-commands parser
755
+ p_inspect_dep_track_sub = p_dep_track_sub.add_subparsers(
756
+ title='Dependency Track Inspection Commands',
757
+ dest='subparser_subcmd',
758
+ description='Tools for analysing Dependency Track project data',
759
+ help='Choose a Dependency Track analysis type',
760
+ )
761
+
762
+ # Project violations inspection - analyses policy violations in DT projects
763
+ p_inspect_dt_project_violation = p_inspect_dep_track_sub.add_parser(
764
+ 'project-violations',
765
+ aliases=['pv'],
766
+ description='Analyse policy violations and compliance issues in Dependency Track projects',
767
+ help='Inspect project policy violations',
768
+ )
769
+ # Dependency Track connection and authentication options
770
+ p_inspect_dt_project_violation.add_argument(
771
+ '--url', required=True, type=str, help='Dependency Track server base URL (e.g., https://dtrack.example.com)'
772
+ )
773
+ p_inspect_dt_project_violation.add_argument(
774
+ '--upload-token',
775
+ '-ut',
776
+ required=False,
777
+ type=str,
778
+ help='Project-specific upload token for accessing DT project data',
779
+ )
780
+ p_inspect_dt_project_violation.add_argument(
781
+ '--project-id', '-pid', required=False, type=str, help='Dependency Track project UUID to inspect'
782
+ )
783
+ p_inspect_dt_project_violation.add_argument(
784
+ '--apikey', '-k', required=True, type=str, help='Dependency Track API key for authentication'
785
+ )
786
+ p_inspect_dt_project_violation.add_argument(
787
+ '--project-name', '-pn', required=False, type=str, help='Dependency Track project name'
788
+ )
789
+ p_inspect_dt_project_violation.add_argument(
790
+ '--project-version', '-pv', required=False, type=str, help='Dependency Track project version'
791
+ )
792
+ p_inspect_dt_project_violation.add_argument(
793
+ '--output', '-o', required=False, type=str, help='Save inspection results to specified file'
794
+ )
795
+ p_inspect_dt_project_violation.add_argument(
796
+ '--status', required=False, type=str, help='Save summary status report to specified file'
797
+ )
798
+ p_inspect_dt_project_violation.add_argument(
799
+ '--format',
800
+ '-f',
801
+ required=False,
802
+ choices=['json', 'md', 'jira_md'],
803
+ default='json',
804
+ help='Output format: json (default), md (Markdown) or jira_md (JIRA Markdown)',
805
+ )
806
+ p_inspect_dt_project_violation.add_argument(
807
+ '--timeout',
808
+ '-M',
809
+ required=False,
810
+ default=300,
811
+ type=float,
812
+ help='Timeout (in seconds) for API communication (optional - default 300 sec)',
813
+ )
814
+
815
+ # ==============================================================================
816
+ # GitLab Integration Parser
817
+ # ==============================================================================
818
+ # Main parser for GitLab-specific inspection commands and report generation
819
+ p_gitlab_sub = p_inspect_sub.add_parser(
820
+ 'gitlab',
821
+ aliases=['glc'],
822
+ description='Generate GitLab-compatible reports from SCANOSS scan results (Markdown summaries)',
823
+ help='Generate GitLab integration reports',
824
+ )
825
+
826
+ # GitLab sub-commands parser
827
+ # Provides access to different GitLab report formats and inspection tools
828
+ p_gitlab_sub_parser = p_gitlab_sub.add_subparsers(
829
+ title='GitLab Report Types',
830
+ dest='subparser_subcmd',
831
+ description='Available GitLab report formats for scan result analysis',
832
+ help='Select the type of GitLab report to generate',
833
+ )
834
+
835
+ # ==============================================================================
836
+ # GitLab Matches Summary Command
837
+ # ==============================================================================
838
+ # Analyzes scan results and generates a GitLab-compatible Markdown summary
839
+ p_gl_inspect_matches = p_gitlab_sub_parser.add_parser(
840
+ 'matches',
841
+ aliases=['ms'],
842
+ description='Generate a Markdown summary report of scan matches for GitLab integration',
843
+ help='Generate Markdown summary report of scan matches',
844
+ )
845
+
846
+ # Input file argument - SCANOSS scan results in JSON format
847
+ p_gl_inspect_matches.add_argument(
848
+ '-i', '--input', required=True, type=str, help='Path to SCANOSS scan results file (JSON format) to analyze'
849
+ )
850
+
851
+ # Line range prefix for GitLab file navigation
852
+ # Enables clickable file references in the generated report that link to specific lines in GitLab
853
+ p_gl_inspect_matches.add_argument(
854
+ '-lpr',
855
+ '--line-range-prefix',
856
+ required=True,
857
+ type=str,
858
+ help='Base URL prefix for GitLab file links with line ranges (e.g., https://gitlab.com/org/project/-/blob/main)',
859
+ )
605
860
 
606
- ########################################### END INSPECT SUBCOMMAND ###########################################
861
+ # Output file argument - where to save the generated Markdown report
862
+ p_gl_inspect_matches.add_argument(
863
+ '--output',
864
+ '-o',
865
+ required=False,
866
+ type=str,
867
+ help='Output file path for the generated Markdown report (default: stdout)',
868
+ )
869
+
870
+ # TODO Move to the command call def location
871
+ # RAW results
872
+ p_inspect_raw_undeclared.set_defaults(func=inspect_undeclared)
873
+ p_inspect_raw_copyleft.set_defaults(func=inspect_copyleft)
874
+ p_inspect_raw_license_summary.set_defaults(func=inspect_license_summary)
875
+ p_inspect_raw_component_summary.set_defaults(func=inspect_component_summary)
876
+ # Legacy backward compatibility commands
877
+ p_inspect_legacy_copyleft.set_defaults(func=inspect_copyleft)
878
+ p_inspect_legacy_undeclared.set_defaults(func=inspect_undeclared)
879
+ p_inspect_legacy_license_summary.set_defaults(func=inspect_license_summary)
880
+ p_inspect_legacy_component_summary.set_defaults(func=inspect_component_summary)
881
+ # Dependency Track
882
+ p_inspect_dt_project_violation.set_defaults(func=inspect_dep_track_project_violations)
883
+ # GitLab
884
+ p_gl_inspect_matches.set_defaults(func=inspect_gitlab_matches)
885
+
886
+ # =========================================================================
887
+ # END INSPECT SUBCOMMAND CONFIGURATION
888
+ # =========================================================================
889
+
890
+ # Sub-command: export
891
+ p_export = subparsers.add_parser(
892
+ 'export',
893
+ aliases=['exp'],
894
+ description=f'Export SBOM files to external platforms: {__version__}',
895
+ help='Export SBOM files to external platforms',
896
+ )
897
+
898
+ export_sub = p_export.add_subparsers(
899
+ title='Export Commands',
900
+ dest='subparsercmd',
901
+ description='export sub-commands',
902
+ help='export sub-commands',
903
+ )
904
+
905
+ # Export Sub-command: export dt (Dependency Track)
906
+ e_dt = export_sub.add_parser(
907
+ 'dt',
908
+ aliases=['dependency-track'],
909
+ description='Export SBOM to Dependency Track',
910
+ help='Upload SBOM files to Dependency Track',
911
+ )
912
+ e_dt.add_argument('-i', '--input', type=str, required=True, help='Input SBOM file (CycloneDX JSON format)')
913
+ e_dt.add_argument('--url', type=str, required=True, help='Dependency Track base URL')
914
+ e_dt.add_argument('--apikey', '-k', type=str, required=True, help='Dependency Track API key')
915
+ e_dt.add_argument('--output', '-o', type=str, help='File to save export token and uuid into')
916
+ e_dt.add_argument('--project-id', '-pid', type=str, help='Dependency Track project UUID')
917
+ e_dt.add_argument('--project-name', '-pn', type=str, help='Dependency Track project name')
918
+ e_dt.add_argument('--project-version', '-pv', type=str, help='Dependency Track project version')
919
+ e_dt.set_defaults(func=export_dt)
607
920
 
608
921
  # Sub-command: folder-scan
609
922
  p_folder_scan = subparsers.add_parser(
@@ -635,6 +948,27 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
635
948
  help='Filter results to only show those with rank value at or below this threshold (e.g., --rank-threshold 3 '
636
949
  'returns results with rank 1, 2, or 3). Lower rank values indicate higher quality matches.',
637
950
  )
951
+ p_folder_scan.add_argument(
952
+ '--depth',
953
+ type=int,
954
+ default=DEFAULT_HFH_DEPTH,
955
+ help=f'Defines how deep to scan the root directory (optional - default {DEFAULT_HFH_DEPTH})',
956
+ )
957
+ p_folder_scan.add_argument(
958
+ '--recursive-threshold',
959
+ type=float,
960
+ default=DEFAULT_HFH_RECURSIVE_THRESHOLD,
961
+ help=f'Minimum score threshold to consider a match (optional - default: {DEFAULT_HFH_RECURSIVE_THRESHOLD})',
962
+ )
963
+ p_folder_scan.add_argument(
964
+ '--min-accepted-score',
965
+ type=float,
966
+ default=DEFAULT_HFH_MIN_ACCEPTED_SCORE,
967
+ help=(
968
+ 'Only show results with a score at or above this threshold '
969
+ f'(optional - default: {DEFAULT_HFH_MIN_ACCEPTED_SCORE})'
970
+ ),
971
+ )
638
972
  p_folder_scan.set_defaults(func=folder_hashing_scan)
639
973
 
640
974
  # Sub-command: folder-hash
@@ -653,8 +987,38 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
653
987
  default='json',
654
988
  help='Result output format (optional - default: json)',
655
989
  )
990
+ p_folder_hash.add_argument(
991
+ '--depth',
992
+ type=int,
993
+ default=DEFAULT_HFH_DEPTH,
994
+ help=f'Defines how deep to hash the root directory (optional - default {DEFAULT_HFH_DEPTH})',
995
+ )
656
996
  p_folder_hash.set_defaults(func=folder_hash)
657
997
 
998
+ # Sub-command: delta
999
+ p_delta = subparsers.add_parser(
1000
+ 'delta',
1001
+ aliases=['dl'],
1002
+ description=f'SCANOSS Delta commands: {__version__}',
1003
+ help='Delta support commands',
1004
+ )
1005
+
1006
+ delta_sub = p_delta.add_subparsers(
1007
+ title='Delta Commands', dest='subparsercmd', description='Delta sub-commands', help='Delta sub-commands'
1008
+ )
1009
+
1010
+ # Delta Sub-command: copy
1011
+ p_copy = delta_sub.add_parser(
1012
+ 'copy',
1013
+ aliases=['cp'],
1014
+ description=f'Copy file list into delta dir: {__version__}',
1015
+ help='Copy the given list of files into a delta directory',
1016
+ )
1017
+ p_copy.add_argument('--input', '-i', type=str, required=True, help='Input file with diff list')
1018
+ p_copy.add_argument('--folder', '-fd', type=str, help='Delta folder to copy into')
1019
+ p_copy.add_argument('--root', '-rd', type=str, help='Root directory to place delta folder')
1020
+ p_copy.set_defaults(func=delta_copy)
1021
+
658
1022
  # Output options
659
1023
  for p in [
660
1024
  p_scan,
@@ -674,6 +1038,8 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
674
1038
  p_crypto_algorithms,
675
1039
  p_crypto_hints,
676
1040
  p_crypto_versions_in_range,
1041
+ c_licenses,
1042
+ p_copy,
677
1043
  ]:
678
1044
  p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
679
1045
 
@@ -707,25 +1073,11 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
707
1073
  help='Skip default settings file (scanoss.json) if it exists',
708
1074
  )
709
1075
 
710
- for p in [p_copyleft, p_undeclared]:
711
- p.add_argument('-i', '--input', nargs='?', help='Path to results file')
712
- p.add_argument(
713
- '-f',
714
- '--format',
715
- required=False,
716
- choices=['json', 'md', 'jira_md'],
717
- default='json',
718
- help='Output format (default: json)',
719
- )
720
- p.add_argument('-o', '--output', type=str, help='Save details into a file')
721
- p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
722
-
723
1076
  # Global Scan command options
724
1077
  for p in [p_scan, p_cs]:
725
1078
  p.add_argument(
726
1079
  '--apiurl', type=str, help='SCANOSS API URL (optional - default: https://api.osskb.org/scan/direct)'
727
1080
  )
728
- p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
729
1081
 
730
1082
  # Global Scan/Fingerprint filter options
731
1083
  for p in [p_scan, p_wfp]:
@@ -747,6 +1099,19 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
747
1099
  p.add_argument('--skip-md5', '-5', type=str, action='append', help='Skip files matching MD5.')
748
1100
  p.add_argument('--strip-hpsm', '-G', type=str, action='append', help='Strip HPSM string from WFP.')
749
1101
  p.add_argument('--strip-snippet', '-N', type=str, action='append', help='Strip Snippet ID string from WFP.')
1102
+ p.add_argument(
1103
+ '--skip-headers',
1104
+ '-skh',
1105
+ action='store_true',
1106
+ help='Skip license headers, comments and imports at the beginning of files.',
1107
+ )
1108
+ p.add_argument(
1109
+ '--skip-headers-limit',
1110
+ '-shl',
1111
+ type=int,
1112
+ default=0,
1113
+ help='Maximum number of lines to skip when filtering headers (default: 0 = no limit).',
1114
+ )
750
1115
 
751
1116
  # Global Scan/GRPC options
752
1117
  for p in [
@@ -761,6 +1126,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
761
1126
  p_crypto_algorithms,
762
1127
  p_crypto_hints,
763
1128
  p_crypto_versions_in_range,
1129
+ c_licenses,
764
1130
  ]:
765
1131
  p.add_argument(
766
1132
  '--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
@@ -799,6 +1165,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
799
1165
  p_crypto_algorithms,
800
1166
  p_crypto_hints,
801
1167
  p_crypto_versions_in_range,
1168
+ c_licenses,
802
1169
  ]:
803
1170
  p.add_argument(
804
1171
  '--api2url', type=str, help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)'
@@ -816,6 +1183,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
816
1183
  type=str,
817
1184
  help='Headers to be sent on request (e.g., -hdr "Name: Value") - can be used multiple times',
818
1185
  )
1186
+ p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
819
1187
 
820
1188
  # Syft options
821
1189
  for p in [p_cs, p_dep]:
@@ -832,6 +1200,24 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
832
1200
  help='Timeout (in seconds) for syft to complete (optional - default 600)',
833
1201
  )
834
1202
 
1203
+ # gRPC support options
1204
+ for p in [
1205
+ c_vulns,
1206
+ p_scan,
1207
+ p_cs,
1208
+ p_crypto_algorithms,
1209
+ p_crypto_hints,
1210
+ p_crypto_versions_in_range,
1211
+ c_semgrep,
1212
+ c_provenance,
1213
+ c_search,
1214
+ c_versions,
1215
+ c_licenses,
1216
+ p_folder_scan,
1217
+ ]:
1218
+ p.add_argument('--grpc', action='store_true', default=True, help='Use gRPC (default)')
1219
+ p.add_argument('--rest', action='store_true', dest='rest', help='Use REST instead of gRPC')
1220
+
835
1221
  # Help/Trace command options
836
1222
  for p in [
837
1223
  p_scan,
@@ -847,10 +1233,16 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
847
1233
  c_versions,
848
1234
  c_semgrep,
849
1235
  p_results,
850
- p_undeclared,
851
- p_copyleft,
852
- p_license_summary,
853
- p_component_summary,
1236
+ p_inspect_raw_undeclared,
1237
+ p_inspect_raw_copyleft,
1238
+ p_inspect_raw_license_summary,
1239
+ p_inspect_raw_component_summary,
1240
+ p_inspect_legacy_copyleft,
1241
+ p_inspect_legacy_undeclared,
1242
+ p_inspect_legacy_license_summary,
1243
+ p_inspect_legacy_component_summary,
1244
+ p_inspect_dt_project_violation,
1245
+ p_gl_inspect_matches,
854
1246
  c_provenance,
855
1247
  p_folder_scan,
856
1248
  p_folder_hash,
@@ -858,12 +1250,27 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
858
1250
  p_crypto_algorithms,
859
1251
  p_crypto_hints,
860
1252
  p_crypto_versions_in_range,
1253
+ c_licenses,
1254
+ e_dt,
1255
+ p_copy,
861
1256
  ]:
862
- p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
1257
+ p.add_argument(
1258
+ '--debug',
1259
+ '-d',
1260
+ action='store_true',
1261
+ default=os.environ.get('SCANOSS_DEBUG', '').lower() == 'true',
1262
+ help='Enable debug messages (can also be set via environment variable SCANOSS_DEBUG)',
1263
+ )
863
1264
  p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
864
1265
  p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
865
1266
 
866
1267
  args = parser.parse_args()
1268
+
1269
+ # TODO: Remove this hack once we go back to using REST as default
1270
+ # Handle --rest overriding --grpc default
1271
+ if hasattr(args, 'rest') and args.rest:
1272
+ args.grpc = False
1273
+
867
1274
  if args.version:
868
1275
  ver(parser, args)
869
1276
  sys.exit(0)
@@ -871,10 +1278,32 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
871
1278
  parser.print_help() # No sub command subcommand, print general help
872
1279
  sys.exit(1)
873
1280
  elif (
874
- args.subparser in ('utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins', 'crypto', 'cr')
1281
+ args.subparser
1282
+ in (
1283
+ 'utils',
1284
+ 'ut',
1285
+ 'component',
1286
+ 'comp',
1287
+ 'inspect',
1288
+ 'insp',
1289
+ 'ins',
1290
+ 'crypto',
1291
+ 'cr',
1292
+ 'export',
1293
+ 'exp',
1294
+ 'delta',
1295
+ 'dl',
1296
+ )
875
1297
  ) and not args.subparsercmd:
876
1298
  parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
877
1299
  sys.exit(1)
1300
+ elif (
1301
+ (args.subparser in 'inspect')
1302
+ and (args.subparsercmd in ('raw', 'dt', 'glc', 'gitlab'))
1303
+ and (args.subparser_subcmd is None)
1304
+ ):
1305
+ parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
1306
+ sys.exit(1)
878
1307
  args.func(parser, args) # Execute the function associated with the sub-command
879
1308
 
880
1309
 
@@ -908,16 +1337,14 @@ def file_count(parser, args):
908
1337
  print_stderr('Please specify a folder')
909
1338
  parser.parse_args([args.subparser, '-h'])
910
1339
  sys.exit(1)
911
- scan_output: str = None
912
1340
  if args.output:
913
- scan_output = args.output
914
- open(scan_output, 'w').close()
1341
+ initialise_empty_file(args.output)
915
1342
 
916
1343
  counter = FileCount(
917
1344
  debug=args.debug,
918
1345
  quiet=args.quiet,
919
1346
  trace=args.trace,
920
- scan_output=scan_output,
1347
+ scan_output=args.output,
921
1348
  hidden_files_folders=args.all_hidden,
922
1349
  )
923
1350
  if not os.path.exists(args.scan_dir):
@@ -946,10 +1373,8 @@ def wfp(parser, args):
946
1373
  sys.exit(1)
947
1374
  if args.strip_hpsm and not args.hpsm and not args.quiet:
948
1375
  print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
949
- scan_output: str = None
950
1376
  if args.output:
951
- scan_output = args.output
952
- open(scan_output, 'w').close()
1377
+ initialise_empty_file(args.output)
953
1378
 
954
1379
  # Load scan settings
955
1380
  scan_settings = None
@@ -979,18 +1404,20 @@ def wfp(parser, args):
979
1404
  strip_hpsm_ids=args.strip_hpsm,
980
1405
  strip_snippet_ids=args.strip_snippet,
981
1406
  scan_settings=scan_settings,
1407
+ skip_headers=args.skip_headers,
1408
+ skip_headers_limit=args.skip_headers_limit,
982
1409
  )
983
1410
  if args.stdin:
984
1411
  contents = sys.stdin.buffer.read()
985
- scanner.wfp_contents(args.stdin, contents, scan_output)
1412
+ scanner.wfp_contents(args.stdin, contents, args.output)
986
1413
  elif args.scan_dir:
987
1414
  if not os.path.exists(args.scan_dir):
988
1415
  print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
989
1416
  sys.exit(1)
990
1417
  if os.path.isdir(args.scan_dir):
991
- scanner.wfp_folder(args.scan_dir, scan_output)
1418
+ scanner.wfp_folder(args.scan_dir, args.output)
992
1419
  elif os.path.isfile(args.scan_dir):
993
- scanner.wfp_file(args.scan_dir, scan_output)
1420
+ scanner.wfp_file(args.scan_dir, args.output)
994
1421
  else:
995
1422
  print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
996
1423
  sys.exit(1)
@@ -1047,6 +1474,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1047
1474
  )
1048
1475
  parser.parse_args([args.subparser, '-h'])
1049
1476
  sys.exit(1)
1477
+ if args.no_wfp_output:
1478
+ print_stderr('Warning: --no-wfp-output is deprecated and has no effect. It will be removed in a future version')
1050
1479
  if args.pac and args.proxy:
1051
1480
  print_stderr('Please specify one of --proxy or --pac, not both')
1052
1481
  parser.parse_args([args.subparser, '-h'])
@@ -1071,9 +1500,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1071
1500
  'blacklist'
1072
1501
  )
1073
1502
  else:
1074
- scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new').set_scan_type(
1075
- 'identify'
1076
- )
1503
+ scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
1504
+
1077
1505
  except ScanossSettingsError as e:
1078
1506
  print_stderr(f'Error: {e}')
1079
1507
  sys.exit(1)
@@ -1088,10 +1516,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1088
1516
  if args.strip_hpsm and not args.hpsm and not args.quiet:
1089
1517
  print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
1090
1518
 
1091
- scan_output: str = None
1092
1519
  if args.output:
1093
- scan_output = args.output
1094
- open(scan_output, 'w').close()
1520
+ initialise_empty_file(args.output)
1095
1521
  output_format = args.format if args.format else 'plain'
1096
1522
  flags = args.flags if args.flags else None
1097
1523
  if args.debug and not args.quiet:
@@ -1131,9 +1557,6 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1131
1557
  if args.retry < 0:
1132
1558
  print_stderr(f'POST retry (--retry) too small: {args.retry}. Reverting to default.')
1133
1559
 
1134
- if not os.access(os.getcwd(), os.W_OK): # Make sure the current directory is writable. If not disable saving WFP
1135
- print_stderr(f'Warning: Current directory is not writable: {os.getcwd()}')
1136
- args.no_wfp_output = True
1137
1560
  if args.ca_cert and not os.path.exists(args.ca_cert):
1138
1561
  print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
1139
1562
  sys.exit(1)
@@ -1146,13 +1569,12 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1146
1569
  quiet=args.quiet,
1147
1570
  api_key=args.key,
1148
1571
  url=args.apiurl,
1149
- scan_output=scan_output,
1572
+ scan_output=args.output,
1150
1573
  output_format=output_format,
1151
1574
  flags=flags,
1152
1575
  nb_threads=args.threads,
1153
1576
  post_size=args.post_size,
1154
1577
  timeout=args.timeout,
1155
- no_wfp_file=args.no_wfp_output,
1156
1578
  all_extensions=args.all_extensions,
1157
1579
  all_folders=args.all_folders,
1158
1580
  hidden_files_folders=args.all_hidden,
@@ -1176,6 +1598,9 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
1176
1598
  strip_snippet_ids=args.strip_snippet,
1177
1599
  scan_settings=scan_settings,
1178
1600
  req_headers=process_req_headers(args.header),
1601
+ use_grpc=args.grpc,
1602
+ skip_headers=args.skip_headers,
1603
+ skip_headers_limit=args.skip_headers_limit,
1179
1604
  )
1180
1605
  if args.wfp:
1181
1606
  if not scanner.is_file_or_snippet_scan():
@@ -1258,16 +1683,15 @@ def dependency(parser, args):
1258
1683
  if not os.path.exists(args.scan_loc):
1259
1684
  print_stderr(f'Error: File or folder specified does not exist: {args.scan_loc}.')
1260
1685
  sys.exit(1)
1261
- scan_output: str = None
1262
1686
  if args.output:
1263
- scan_output = args.output
1264
- open(scan_output, 'w').close()
1687
+ initialise_empty_file(args.output)
1265
1688
 
1266
1689
  sc_deps = ScancodeDeps(
1267
1690
  debug=args.debug, quiet=args.quiet, trace=args.trace, sc_command=args.sc_command, timeout=args.sc_timeout
1268
1691
  )
1269
- if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=scan_output):
1692
+ if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=args.output):
1270
1693
  sys.exit(1)
1694
+ return None
1271
1695
 
1272
1696
 
1273
1697
  def convert(parser, args):
@@ -1300,148 +1724,421 @@ def convert(parser, args):
1300
1724
  print_stderr('Producing CSV report...')
1301
1725
  csvo = CsvOutput(debug=args.debug, output_file=args.output)
1302
1726
  success = csvo.produce_from_file(args.input)
1727
+ elif args.format == 'glc-codequality':
1728
+ if not args.quiet:
1729
+ print_stderr('Producing GitLab code quality report...')
1730
+ glc_code_quality = GitLabQualityReport(debug=args.debug, trace=args.trace, quiet=args.quiet)
1731
+ success = glc_code_quality.produce_from_file(args.input, output_file=args.output)
1303
1732
  else:
1304
1733
  print_stderr(f'ERROR: Unknown output format (--format): {args.format}')
1305
1734
  if not success:
1306
1735
  sys.exit(1)
1307
1736
 
1308
- ################################ INSPECT handlers ################################
1737
+
1738
+ # =============================================================================
1739
+ # INSPECT COMMAND HANDLERS - Functions that execute inspection operations
1740
+ # =============================================================================
1741
+
1742
+
1309
1743
  def inspect_copyleft(parser, args):
1310
1744
  """
1311
- Run the "inspect" sub-command
1745
+ Handle copyleft license inspection command.
1746
+
1747
+ Analyses scan results to identify components using copyleft licenses
1748
+ that may require compliance actions such as source code disclosure.
1749
+
1312
1750
  Parameters
1313
1751
  ----------
1314
- parser: ArgumentParser
1315
- command line parser object
1316
- args: Namespace
1317
- Parsed arguments
1752
+ parser : ArgumentParser
1753
+ Command line parser object for help display
1754
+ args : Namespace
1755
+ Parsed command line arguments containing:
1756
+ - input: Path to scan results file
1757
+ - output: Optional output file path
1758
+ - status: Optional status summary file path
1759
+ - format: Output format (json, md, jira_md)
1760
+ - include/exclude/explicit: License filter options
1318
1761
  """
1762
+ # Validate required input file parameter
1319
1763
  if args.input is None:
1320
- print_stderr('Please specify an input file to inspect')
1321
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1764
+ print_stderr('ERROR: Input file is required for copyleft inspection')
1765
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1322
1766
  sys.exit(1)
1323
- output: str = None
1767
+ # Initialise output file if specified
1324
1768
  if args.output:
1325
- output = args.output
1326
- open(output, 'w').close()
1327
-
1328
- status_output: str = None
1769
+ initialise_empty_file(args.output)
1770
+ # Initialise status summary file if specified
1329
1771
  if args.status:
1330
- status_output = args.status
1331
- open(status_output, 'w').close()
1332
-
1333
- i_copyleft = Copyleft(
1334
- debug=args.debug,
1335
- trace=args.trace,
1336
- quiet=args.quiet,
1337
- filepath=args.input,
1338
- format_type=args.format,
1339
- status=status_output,
1340
- output=output,
1341
- include=args.include,
1342
- exclude=args.exclude,
1343
- explicit=args.explicit,
1344
- )
1345
- status, _ = i_copyleft.run()
1346
- sys.exit(status)
1772
+ initialise_empty_file(args.status)
1773
+ try:
1774
+ # Create and configure copyleft inspector
1775
+ i_copyleft = Copyleft(
1776
+ debug=args.debug,
1777
+ trace=args.trace,
1778
+ quiet=args.quiet,
1779
+ filepath=args.input,
1780
+ format_type=args.format,
1781
+ status=args.status,
1782
+ output=args.output,
1783
+ include=args.include, # Additional licenses to check
1784
+ exclude=args.exclude, # Licenses to ignore
1785
+ explicit=args.explicit, # Explicit license list
1786
+ license_sources=args.license_sources, # License sources to check (list)
1787
+ )
1788
+ # Execute inspection and exit with appropriate status code
1789
+ status, _ = i_copyleft.run()
1790
+ sys.exit(status)
1791
+ except Exception as e:
1792
+ print_stderr(e)
1793
+ if args.debug:
1794
+ traceback.print_exc()
1795
+ sys.exit(1)
1347
1796
 
1348
1797
 
1349
1798
  def inspect_undeclared(parser, args):
1350
1799
  """
1351
- Run the "inspect" sub-command
1800
+ Handle undeclared components inspection command.
1801
+
1802
+ Analyses scan results to identify components that are present in the
1803
+ codebase but not declared in SBOM or manifest files, which may indicate
1804
+ security or compliance risks.
1805
+
1352
1806
  Parameters
1353
1807
  ----------
1354
- parser: ArgumentParser
1355
- command line parser object
1356
- args: Namespace
1357
- Parsed arguments
1808
+ parser : ArgumentParser
1809
+ Command line parser object for help display
1810
+ args : Namespace
1811
+ Parsed command line arguments containing:
1812
+ - input: Path to scan results file
1813
+ - output: Optional output file path
1814
+ - status: Optional status summary file path
1815
+ - format: Output format (json, md, jira_md)
1816
+ - sbom_format: SBOM format type (legacy, settings)
1358
1817
  """
1818
+ # Validate required input file parameter
1359
1819
  if args.input is None:
1360
- print_stderr('Please specify an input file to inspect')
1361
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1820
+ print_stderr('ERROR: Input file is required for undeclared component inspection')
1821
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1362
1822
  sys.exit(1)
1363
- output: str = None
1823
+
1824
+ # Initialise output file if specified
1364
1825
  if args.output:
1365
- output = args.output
1366
- open(output, 'w').close()
1826
+ initialise_empty_file(args.output)
1367
1827
 
1368
- status_output: str = None
1828
+ # Initialise status summary file if specified
1369
1829
  if args.status:
1370
- status_output = args.status
1371
- open(status_output, 'w').close()
1372
- i_undeclared = UndeclaredComponent(
1373
- debug=args.debug,
1374
- trace=args.trace,
1375
- quiet=args.quiet,
1376
- filepath=args.input,
1377
- format_type=args.format,
1378
- status=status_output,
1379
- output=output,
1380
- sbom_format=args.sbom_format,
1381
- )
1382
- status, _ = i_undeclared.run()
1383
- sys.exit(status)
1830
+ initialise_empty_file(args.status)
1831
+
1832
+ try:
1833
+ # Create and configure undeclared component inspector
1834
+ i_undeclared = UndeclaredComponent(
1835
+ debug=args.debug,
1836
+ trace=args.trace,
1837
+ quiet=args.quiet,
1838
+ filepath=args.input,
1839
+ format_type=args.format,
1840
+ status=args.status,
1841
+ output=args.output,
1842
+ sbom_format=args.sbom_format, # Format for SBOM comparison
1843
+ )
1844
+
1845
+ # Execute inspection and exit with appropriate status code
1846
+ status, _ = i_undeclared.run()
1847
+ sys.exit(status)
1848
+ except Exception as e:
1849
+ print_stderr(e)
1850
+ if args.debug:
1851
+ traceback.print_exc()
1852
+ sys.exit(1)
1853
+
1384
1854
 
1385
1855
  def inspect_license_summary(parser, args):
1386
1856
  """
1387
- Run the "inspect" sub-command
1388
- Parameters
1389
- ----------
1390
- parser: ArgumentParser
1391
- command line parser object
1392
- args: Namespace
1393
- Parsed arguments
1394
- """
1857
+ Handle license summary inspection command.
1858
+
1859
+ Generates comprehensive summary of all licenses detected in scan results,
1860
+ including license counts, risk levels, and compliance recommendations.
1861
+
1862
+ Parameters
1863
+ ----------
1864
+ parser : ArgumentParser
1865
+ Command line parser object for help display
1866
+ args : Namespace
1867
+ Parsed command line arguments containing:
1868
+ - input: Path to scan results file
1869
+ - output: Optional output file path
1870
+ - include/exclude/explicit: License filter options
1871
+ """
1872
+ # Validate required input file parameter
1395
1873
  if args.input is None:
1396
- print_stderr('Please specify an input file to inspect')
1397
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1874
+ print_stderr('ERROR: Input file is required for license summary')
1875
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1398
1876
  sys.exit(1)
1399
- output: str = None
1877
+
1878
+ # Initialise output file if specified
1400
1879
  if args.output:
1401
- output = args.output
1402
- open(output, 'w').close()
1880
+ initialise_empty_file(args.output)
1403
1881
 
1882
+ # Create and configure license summary generator
1404
1883
  i_license_summary = LicenseSummary(
1405
1884
  debug=args.debug,
1406
1885
  trace=args.trace,
1407
1886
  quiet=args.quiet,
1408
1887
  filepath=args.input,
1409
- output=output,
1410
- include=args.include,
1411
- exclude=args.exclude,
1412
- explicit=args.explicit,
1888
+ output=args.output,
1889
+ include=args.include, # Additional licenses to include
1890
+ exclude=args.exclude, # Licenses to exclude from summary
1891
+ explicit=args.explicit, # Explicit license list to summarize
1413
1892
  )
1414
- i_license_summary.run()
1893
+ try:
1894
+ # Execute summary generation
1895
+ i_license_summary.run()
1896
+ except Exception as e:
1897
+ print_stderr(e)
1898
+ if args.debug:
1899
+ traceback.print_exc()
1900
+ sys.exit(1)
1901
+
1415
1902
 
1416
1903
  def inspect_component_summary(parser, args):
1417
1904
  """
1418
- Run the "inspect" sub-command
1419
- Parameters
1420
- ----------
1421
- parser: ArgumentParser
1422
- command line parser object
1423
- args: Namespace
1424
- Parsed arguments
1425
- """
1905
+ Handle component summary inspection command.
1906
+
1907
+ Generates a comprehensive summary of all components detected in scan results,
1908
+ including component counts, versions, match types, and security information.
1909
+
1910
+ Parameters
1911
+ ----------
1912
+ parser : ArgumentParser
1913
+ Command line parser object for help display
1914
+ args : Namespace
1915
+ Parsed command line arguments containing:
1916
+ - input: Path to scan results file
1917
+ - output: Optional output file path
1918
+ """
1919
+ # Validate required input file parameter
1426
1920
  if args.input is None:
1427
- print_stderr('Please specify an input file to inspect')
1428
- parser.parse_args([args.subparser, args.subparsercmd, '-h'])
1921
+ print_stderr('ERROR: Input file is required for component summary')
1922
+ parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
1429
1923
  sys.exit(1)
1430
- output: str = None
1924
+
1925
+ # Initialise an output file if specified
1431
1926
  if args.output:
1432
- output = args.output
1433
- open(output, 'w').close()
1927
+ initialise_empty_file(args.output) # Create/clear output file
1434
1928
 
1929
+ # Create and configure component summary generator
1435
1930
  i_component_summary = ComponentSummary(
1436
1931
  debug=args.debug,
1437
1932
  trace=args.trace,
1438
1933
  quiet=args.quiet,
1439
1934
  filepath=args.input,
1440
- output=output,
1935
+ output=args.output,
1441
1936
  )
1442
- i_component_summary.run()
1443
1937
 
1444
- ################################ End inspect handlers ################################
1938
+ try:
1939
+ # Execute summary generation
1940
+ i_component_summary.run()
1941
+ except Exception as e:
1942
+ print_stderr(e)
1943
+ if args.debug:
1944
+ traceback.print_exc()
1945
+ sys.exit(1)
1946
+
1947
+
1948
+ def inspect_dep_track_project_violations(parser, args):
1949
+ """
1950
+ Handle Dependency Track project inspection command.
1951
+
1952
+ Analyses Dependency Track projects for policy violations, security issues,
1953
+ and compliance status. Connects to DT API to retrieve project data and
1954
+ generate detailed violation reports.
1955
+
1956
+ Parameters
1957
+ ----------
1958
+ parser : ArgumentParser
1959
+ Command line parser object for help display
1960
+ args : Namespace
1961
+ Parsed command line arguments containing:
1962
+ - url: Dependency Track base URL
1963
+ - apikey: API key for authentication
1964
+ - project_id: Project UUID to inspect
1965
+ - project_name: Project name to inspect
1966
+ - project_version: Project version to inspect
1967
+ - upload_token: Upload token for project access
1968
+ - output: Optional output file path
1969
+ - format: Output format (json, md)
1970
+ - timeout: Optional timeout for API requests
1971
+
1972
+ """
1973
+ # Make sure we have project id/project name and version
1974
+ _dt_args_validator(parser, args)
1975
+ # Initialise the output file if specified
1976
+ if args.output:
1977
+ initialise_empty_file(args.output)
1978
+ # Create and configure Dependency Track inspector
1979
+ try:
1980
+ dt_proj_violations = DependencyTrackProjectViolationPolicyCheck(
1981
+ debug=args.debug,
1982
+ trace=args.trace,
1983
+ quiet=args.quiet,
1984
+ output=args.output,
1985
+ status=args.status,
1986
+ format_type=args.format,
1987
+ url=args.url, # DT server URL
1988
+ api_key=args.apikey, # Authentication key
1989
+ project_id=args.project_id, # Target project UUID
1990
+ upload_token=args.upload_token, # Upload access token
1991
+ project_name=args.project_name, # DT project name
1992
+ project_version=args.project_version, # DT project version
1993
+ timeout=args.timeout,
1994
+ )
1995
+ # Execute inspection and exit with appropriate status code
1996
+ status = dt_proj_violations.run()
1997
+ sys.exit(status)
1998
+ except Exception as e:
1999
+ print_stderr(e)
2000
+ if args.debug:
2001
+ traceback.print_exc()
2002
+ sys.exit(1)
2003
+
2004
+
2005
+ def inspect_gitlab_matches(parser, args):
2006
+ """
2007
+ Handle GitLab matches the summary inspection command.
2008
+
2009
+ Analyzes SCANOSS scan results and generates a GitLab-compatible Markdown summary
2010
+ report of component matches. The report includes match details, file locations,
2011
+ and optionally clickable links to source files in GitLab repositories.
2012
+
2013
+ This command processes SCANOSS scan output and creates human-readable Markdown.
2014
+
2015
+ Parameters
2016
+ ----------
2017
+ parser : ArgumentParser
2018
+ Command line parser object for help display
2019
+ args : Namespace
2020
+ Parsed command line arguments containing:
2021
+ - input: Path to SCANOSS scan results file (JSON format) to analyze
2022
+ - line_range_prefix: Base URL prefix for generating GitLab file links with line ranges
2023
+ (e.g., 'https://gitlab.com/org/project/-/blob/main')
2024
+ - output: Optional output file path for the generated Markdown report (default: stdout)
2025
+ - debug: Enable debug output for troubleshooting
2026
+ - trace: Enable trace-level logging
2027
+ - quiet: Suppress informational messages
2028
+
2029
+ Notes
2030
+ -----
2031
+ - The output is formatted in Markdown for optimal display in GitLab
2032
+ - Line range prefix enables clickable file references in the report
2033
+ - If output is not specified, the report is written to stdout
2034
+ """
2035
+
2036
+ if args.input is None:
2037
+ parser.parse_args([args.subparser, '-h'])
2038
+ sys.exit(1)
2039
+
2040
+ if args.line_range_prefix is None:
2041
+ parser.parse_args([args.subparser, '-h'])
2042
+ sys.exit(1)
2043
+
2044
+ # Initialize output file if specified (create/truncate)
2045
+ if args.output:
2046
+ initialise_empty_file(args.output)
2047
+
2048
+ try:
2049
+ # Create GitLab matches summary generator with configuration
2050
+ match_summary = MatchSummary(
2051
+ debug=args.debug,
2052
+ trace=args.trace,
2053
+ quiet=args.quiet,
2054
+ scanoss_results_path=args.input, # Path to SCANOSS JSON results
2055
+ output=args.output, # Output file path or None for stdout
2056
+ line_range_prefix=args.line_range_prefix, # GitLab URL prefix for file links
2057
+ )
2058
+
2059
+ # Execute the summary generation
2060
+ match_summary.run()
2061
+ except Exception as e:
2062
+ # Handle any errors during report generation
2063
+ print_stderr(e)
2064
+ if args.debug:
2065
+ traceback.print_exc()
2066
+ sys.exit(1)
2067
+
2068
+
2069
+ # =============================================================================
2070
+ # END INSPECT COMMAND HANDLERS
2071
+ # =============================================================================
2072
+
2073
+
2074
+ def export_dt(parser, args):
2075
+ """
2076
+ Validates and exports a Software Bill of Materials (SBOM) to a Dependency-Track server.
2077
+
2078
+ Parameters:
2079
+ parser (argparse.ArgumentParser): The argument parser to validate input arguments.
2080
+ args (argparse.Namespace): Parsed arguments passed to the command.
2081
+
2082
+ Raises:
2083
+ SystemExit: If argument validation fails or uploading the SBOM to the Dependency-Track server
2084
+ is unsuccessful.
2085
+ """
2086
+ # Make sure we have project id/project name and version
2087
+ _dt_args_validator(parser, args)
2088
+ if args.output:
2089
+ initialise_empty_file(args.output)
2090
+ if not args.quiet:
2091
+ print_stderr(f'Outputting export data result to: {args.output}')
2092
+ try:
2093
+ dt_exporter = DependencyTrackExporter(
2094
+ url=args.url,
2095
+ apikey=args.apikey,
2096
+ output=args.output,
2097
+ debug=args.debug,
2098
+ trace=args.trace,
2099
+ quiet=args.quiet,
2100
+ )
2101
+ success = dt_exporter.upload_sbom_file(
2102
+ args.input, args.project_id, args.project_name, args.project_version, args.output
2103
+ )
2104
+ if not success:
2105
+ sys.exit(1)
2106
+ except Exception as e:
2107
+ print_stderr(f'ERROR: {e}')
2108
+ if args.debug:
2109
+ traceback.print_exc()
2110
+ sys.exit(1)
2111
+
2112
+
2113
+ def _dt_args_validator(parser, args):
2114
+ """
2115
+ Validates command-line arguments related to project identification.
2116
+
2117
+ Parameters
2118
+ ----------
2119
+ parser : argparse.ArgumentParser
2120
+ An argument parser instance for handling command-line arguments.
2121
+ args : argparse.Namespace
2122
+ Parsed arguments from the command line containing project-related information.
2123
+
2124
+ Raises
2125
+ ------
2126
+ SystemExit
2127
+ If neither a project ID nor the required combination of project name and
2128
+ project version is provided, or if any of the compulsory arguments
2129
+ are missing.
2130
+ """
2131
+ if not args.project_id and not args.project_name and not args.project_version:
2132
+ print_stderr(
2133
+ 'Please specify either a project ID (--project-id) or a project name (--project-name) and '
2134
+ 'version (--project-version)'
2135
+ )
2136
+ parser.parse_args([args.subparser, '-h'])
2137
+ sys.exit(1)
2138
+ if not args.project_id and (not args.project_name or not args.project_version):
2139
+ print_stderr('Please supply a project name (--project-name) and version (--project-version)')
2140
+ sys.exit(1)
2141
+
1445
2142
 
1446
2143
  def utils_certloc(*_):
1447
2144
  """
@@ -1706,6 +2403,8 @@ def comp_vulns(parser, args):
1706
2403
  pac=pac_file,
1707
2404
  timeout=args.timeout,
1708
2405
  req_headers=process_req_headers(args.header),
2406
+ ignore_cert_errors=args.ignore_cert_errors,
2407
+ use_grpc=args.grpc,
1709
2408
  )
1710
2409
  if not comps.get_vulnerabilities(args.input, args.purl, args.output):
1711
2410
  sys.exit(1)
@@ -1741,6 +2440,7 @@ def comp_semgrep(parser, args):
1741
2440
  pac=pac_file,
1742
2441
  timeout=args.timeout,
1743
2442
  req_headers=process_req_headers(args.header),
2443
+ use_grpc=args.grpc,
1744
2444
  )
1745
2445
  if not comps.get_semgrep_details(args.input, args.purl, args.output):
1746
2446
  sys.exit(1)
@@ -1779,6 +2479,7 @@ def comp_search(parser, args):
1779
2479
  pac=pac_file,
1780
2480
  timeout=args.timeout,
1781
2481
  req_headers=process_req_headers(args.header),
2482
+ use_grpc=args.grpc,
1782
2483
  )
1783
2484
  if not comps.search_components(
1784
2485
  args.output,
@@ -1824,6 +2525,7 @@ def comp_versions(parser, args):
1824
2525
  pac=pac_file,
1825
2526
  timeout=args.timeout,
1826
2527
  req_headers=process_req_headers(args.header),
2528
+ use_grpc=args.grpc,
1827
2529
  )
1828
2530
  if not comps.get_component_versions(args.output, json_file=args.input, purl=args.purl, limit=args.limit):
1829
2531
  sys.exit(1)
@@ -1859,11 +2561,48 @@ def comp_provenance(parser, args):
1859
2561
  pac=pac_file,
1860
2562
  timeout=args.timeout,
1861
2563
  req_headers=process_req_headers(args.header),
2564
+ use_grpc=args.grpc,
1862
2565
  )
1863
2566
  if not comps.get_provenance_details(args.input, args.purl, args.output, args.origin):
1864
2567
  sys.exit(1)
1865
2568
 
1866
2569
 
2570
+ def comp_licenses(parser, args):
2571
+ """
2572
+ Run the "component licenses" sub-command
2573
+ Parameters
2574
+ ----------
2575
+ parser: ArgumentParser
2576
+ command line parser object
2577
+ args: Namespace
2578
+ Parsed arguments
2579
+ """
2580
+ if (not args.purl and not args.input) or (args.purl and args.input):
2581
+ print_stderr('ERROR: Please specify an input file or purl to decorate (--purl or --input)')
2582
+ parser.parse_args([args.subparser, args.subparsercmd, '-h'])
2583
+ sys.exit(1)
2584
+ if args.ca_cert and not os.path.exists(args.ca_cert):
2585
+ print_stderr(f'ERROR: Certificate file does not exist: {args.ca_cert}.')
2586
+ sys.exit(1)
2587
+ pac_file = get_pac_file(args.pac)
2588
+ comps = Components(
2589
+ debug=args.debug,
2590
+ trace=args.trace,
2591
+ quiet=args.quiet,
2592
+ grpc_url=args.api2url,
2593
+ api_key=args.key,
2594
+ ca_cert=args.ca_cert,
2595
+ proxy=args.proxy,
2596
+ grpc_proxy=args.grpc_proxy,
2597
+ pac=pac_file,
2598
+ timeout=args.timeout,
2599
+ req_headers=process_req_headers(args.header),
2600
+ use_grpc=args.grpc,
2601
+ )
2602
+ if not comps.get_licenses(args.input, args.purl, args.output):
2603
+ sys.exit(1)
2604
+
2605
+
1867
2606
  def results(parser, args):
1868
2607
  """
1869
2608
  Run the "results" sub-command
@@ -1959,6 +2698,10 @@ def folder_hashing_scan(parser, args):
1959
2698
  client=client,
1960
2699
  scanoss_settings=scanoss_settings,
1961
2700
  rank_threshold=args.rank_threshold,
2701
+ depth=args.depth,
2702
+ recursive_threshold=args.recursive_threshold,
2703
+ min_accepted_score=args.min_accepted_score,
2704
+ use_grpc=args.grpc,
1962
2705
  )
1963
2706
 
1964
2707
  if scanner.scan():
@@ -1992,6 +2735,7 @@ def folder_hash(parser, args):
1992
2735
  scan_dir=args.scan_dir,
1993
2736
  config=folder_hasher_config,
1994
2737
  scanoss_settings=scanoss_settings,
2738
+ depth=args.depth,
1995
2739
  )
1996
2740
 
1997
2741
  folder_hasher.hash_directory(args.scan_dir)
@@ -2046,7 +2790,77 @@ def get_scanoss_settings_from_args(args):
2046
2790
  except ScanossSettingsError as e:
2047
2791
  print_stderr(f'Error: {e}')
2048
2792
  sys.exit(1)
2049
- return scanoss_settings
2793
+ return scanoss_settings
2794
+
2795
+
2796
+ def initialise_empty_file(filename: str):
2797
+ """
2798
+ Initialises an empty file with the specified name. If the file already exists,
2799
+ it truncates its content. Ensures proper error handling in case of failure.
2800
+
2801
+ Args:
2802
+ filename (str): The name of the file to be initialised.
2803
+
2804
+ Raises:
2805
+ SystemExit: If the file cannot be created or written due to an exception,
2806
+ the function prints an error message and exits the program.
2807
+
2808
+ Note:
2809
+ This function writes an empty file and handles exceptions to ensure the
2810
+ program does not continue execution in case of an error.
2811
+ """
2812
+ if filename:
2813
+ try:
2814
+ open(filename, 'w').close()
2815
+ except Exception as e:
2816
+ print_stderr(f'Error: Unable to create output file {filename}: {e}')
2817
+ sys.exit(1)
2818
+
2819
+
2820
+ def delta_copy(parser, args):
2821
+ """
2822
+ Handle delta copy command.
2823
+
2824
+ Copies files listed in an input file to a target directory while preserving
2825
+ their directory structure. Creates a unique delta directory if none is specified.
2826
+
2827
+ Parameters
2828
+ ----------
2829
+ parser : ArgumentParser
2830
+ Command line parser object for help display
2831
+ args : Namespace
2832
+ Parsed command line arguments containing:
2833
+ - input: Path to file containing list of files to copy
2834
+ - folder: Optional target directory path
2835
+ - output: Optional output file path
2836
+ """
2837
+ # Validate required input file parameter
2838
+ if args.input is None:
2839
+ print_stderr('ERROR: Input file is required for copying')
2840
+ parser.parse_args([args.subparser, args.subparsercmd, '-h'])
2841
+ sys.exit(1)
2842
+ # Initialise output file if specified
2843
+ if args.output:
2844
+ initialise_empty_file(args.output)
2845
+ try:
2846
+ # Create and configure delta copy command
2847
+ delta = Delta(
2848
+ debug=args.debug,
2849
+ trace=args.trace,
2850
+ quiet=args.quiet,
2851
+ filepath=args.input,
2852
+ folder=args.folder,
2853
+ output=args.output,
2854
+ root_dir=args.root,
2855
+ )
2856
+ # Execute copy and exit with appropriate status code
2857
+ status, _ = delta.copy()
2858
+ sys.exit(status)
2859
+ except Exception as e:
2860
+ print_stderr(e)
2861
+ if args.debug:
2862
+ traceback.print_exc()
2863
+ sys.exit(1)
2050
2864
 
2051
2865
 
2052
2866
  def main():