3131from  .scancodedeps  import  ScancodeDeps 
3232from  .scantype  import  ScanType 
3333from  .filecount  import  FileCount 
34+ from  .cyclonedx  import  CycloneDx 
35+ from  .spdxlite  import  SpdxLite 
36+ from  .csvoutput  import  CsvOutput 
3437from  . import  __version__ 
3538
3639
@@ -46,6 +49,8 @@ def setup_args() -> None:
4649    Setup all the command line arguments for processing 
4750    """ 
4851    parser  =  argparse .ArgumentParser (description = f'SCANOSS Python CLI. Ver: { __version__ }  , License: MIT' )
52+     parser .add_argument ('--version' , '-v' , action = 'store_true' , help = 'Display version details' )
53+ 
4954    subparsers  =  parser .add_subparsers (title = 'Sub Commands' , dest = 'subparser' , description = 'valid subcommands' ,
5055                                       help = 'sub-command help' 
5156                                       )
@@ -131,6 +136,35 @@ def setup_args() -> None:
131136    p_fc .add_argument ('--output' , '-o' , type = str , help = 'Output result file name (optional - default stdout).'  )
132137    p_fc .add_argument ('--all-hidden' , action = 'store_true' , help = 'Scan all hidden files/folders' )
133138
139+     # Sub-command: convert 
140+     p_cnv  =  subparsers .add_parser ('convert' , aliases = ['cv' , 'cnv' , 'cvrt' ],
141+                                    description = f'Convert results files between formats: { __version__ }  ' ,
142+                                    help = 'Convert file format' )
143+     p_cnv .set_defaults (func = convert )
144+     p_cnv .add_argument ('--input' , '-i' , type = str , required = True , help = 'Input file name' )
145+     p_cnv .add_argument ('--output' ,'-o' , type = str , help = 'Output result file name (optional - default stdout).'  )
146+     p_cnv .add_argument ('--format' ,'-f' , type = str , choices = ['cyclonedx' , 'spdxlite' , 'csv' ], default = 'spdxlite' ,
147+                        help = 'Output format (optional - default: spdxlite)' 
148+                        )
149+     p_cnv .add_argument ('--input-format' , type = str , choices = ['plain' ], default = 'plain' ,
150+                        help = 'Input format (optional - default: plain)' 
151+                        )
152+ 
153+     # Sub-command: utils 
154+     p_util  =  subparsers .add_parser ('utils' , aliases = ['ut' , 'util' ],
155+                                    description = f'SCANOSS Utility commands: { __version__ }  ' ,
156+                                    help = 'General utility support commands' )
157+ 
158+     utils_sub  =  p_util .add_subparsers (title = 'Utils Commands' , dest = 'utilsubparser' , description = 'utils sub-commands' ,
159+                                        help = 'utils sub-commands' 
160+                                        )
161+ 
162+     # Utils Sub-command: utils certloc 
163+     p_c_loc  =  utils_sub .add_parser ('certloc' , aliases = ['cl' ],
164+                                    description = f'Show location of Python CA Certs: { __version__ }  ' ,
165+                                    help = 'Display the location of Python CA Certs' )
166+     p_c_loc .set_defaults (func = utils_certloc )
167+ 
134168    # Global command options 
135169    for  p  in  [p_scan ]:
136170        p .add_argument ('--key' , '-k' , type = str ,
@@ -142,16 +176,31 @@ def setup_args() -> None:
142176        p .add_argument ('--api2url' , type = str ,
143177                       help = 'SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)' 
144178                       )
179+         p .add_argument ('--proxy' , type = str , help = 'Proxy URL to use for connections (optional). ' 
180+                                                  'Can also use the environment variable "HTTPS_PROXY=<ip>:<port>" ' 
181+                                                  'and "grcp_proxy=<ip>:<port>" for gRPC' 
182+                        )
183+         p .add_argument ('--ca-cert' , type = str , help = 'Alternative certificate PEM file (optional). ' 
184+                                                    'Can also use the environment variable ' 
185+                                                    '"REQUESTS_CA_BUNDLE=/path/to/cacert.pem" and ' 
186+                                                    '"GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/path/to/cacert.pem" for gRPC' 
187+                        )
145188        p .add_argument ('--ignore-cert-errors' , action = 'store_true' , help = 'Ignore certificate errors' )
146189
147-     for  p  in  [p_scan , p_wfp , p_dep , p_fc ]:
190+     for  p  in  [p_scan , p_wfp , p_dep , p_fc ,  p_cnv ,  p_c_loc ]:
148191        p .add_argument ('--debug' , '-d' , action = 'store_true' , help = 'Enable debug messages' )
149192        p .add_argument ('--trace' , '-t' , action = 'store_true' , help = 'Enable trace messages, including API posts' )
150193        p .add_argument ('--quiet' , '-q' , action = 'store_true' , help = 'Enable quiet mode' )
151194
152195    args  =  parser .parse_args ()
196+     if  args .version :
197+         ver (parser , args )
198+         exit (0 )
153199    if  not  args .subparser :
154-         parser .print_help ()
200+         parser .print_help ()  # No sub command subcommand, print general help 
201+         exit (1 )
202+     elif  args .subparser  ==  'utils'  and  not  args .utilsubparser :  # No utils sub command supplied 
203+         parser .parse_args ([args .subparser , '--help' ])  # Force utils helps to be displayed 
155204        exit (1 )
156205    args .func (parser , args )  # Execute the function associated with the sub-command 
157206
@@ -326,14 +375,20 @@ def scan(parser, args):
326375            print_stderr (f'Changing scanning POST timeout to: { args .timeout }  ...' )
327376        if  args .obfuscate :
328377            print_stderr ("Obfuscating file fingerprints..." )
378+         if  args .proxy :
379+             print_stderr (f'Using Proxy { arg .proxy }  ...' )
380+         if  args .ca_cert :
381+             print_stderr (f'Using Certificate { arg .ca_cert }  ...' )
329382    elif  not  args .quiet :
330383        if  args .timeout  <  5 :
331384            print_stderr (f'POST timeout (--timeout) too small: { args .timeout }  . Reverting to default.' )
332385
333386    if  not  os .access ( os .getcwd (), os .W_OK  ):  # Make sure the current directory is writable. If not disable saving WFP 
334387        print_stderr (f'Warning: Current directory is not writable: { os .getcwd ()}  ' )
335388        args .no_wfp_output  =  True 
336- 
389+     if  args .ca_cert  and  not  os .path .exists (args .ca_cert ):
390+         print_stderr (f'Error: Certificate file does not exist: { args .ca_cert }  .' )
391+         exit (1 )
337392    scan_options  =  get_scan_options (args )   # Figure out what scanning options we have 
338393
339394    scanner  =  Scanner (debug = args .debug , trace = args .trace , quiet = args .quiet , api_key = args .key , url = args .apiurl ,
@@ -342,7 +397,8 @@ def scan(parser, args):
342397                      timeout = args .timeout , no_wfp_file = args .no_wfp_output , all_extensions = args .all_extensions ,
343398                      all_folders = args .all_folders , hidden_files_folders = args .all_hidden ,
344399                      scan_options = scan_options , sc_timeout = args .sc_timeout , sc_command = args .sc_command ,
345-                       grpc_url = args .api2url , obfuscate = args .obfuscate , ignore_cert_errors = args .ignore_cert_errors 
400+                       grpc_url = args .api2url , obfuscate = args .obfuscate ,
401+                       ignore_cert_errors = args .ignore_cert_errors , proxy = args .proxy , ca_cert = args .ca_cert 
346402                      )
347403    if  args .wfp :
348404        if  not  scanner .is_file_or_snippet_scan ():
@@ -397,6 +453,54 @@ def dependency(parser, args):
397453    if  not  sc_deps .get_dependencies (what_to_scan = args .scan_dir , result_output = scan_output ):
398454        exit (1 )
399455
456+ def  convert (parser , args ):
457+     """ 
458+     Run the "convert" sub-command 
459+     Parameters 
460+     ---------- 
461+         parser: ArgumentParser 
462+             command line parser object 
463+         args: Namespace 
464+             Parsed arguments 
465+     """ 
466+     if  not  args .input :
467+         print_stderr ('Please specify an input file to convert' )
468+         parser .parse_args ([args .subparser , '-h' ])
469+         exit (1 )
470+     success  =  False 
471+     if  args .format  ==  'cyclonedx' :
472+         if  not  args .quiet :
473+             print_stderr (f'Producing CycloneDX report...' )
474+         cdx  =  CycloneDx (debug = args .debug , output_file = args .output )
475+         success  =  cdx .produce_from_file (args .input )
476+     elif  args .format  ==  'spdxlite' :
477+         if  not  args .quiet :
478+             print_stderr (f'Producing SPDX Lite report...' )
479+         spdxlite  =  SpdxLite (debug = args .debug , output_file = args .output )
480+         success  =  spdxlite .produce_from_file (args .input )
481+     elif  args .format  ==  'csv' :
482+         if  not  args .quiet :
483+             print_stderr (f'Producing CSV report...' )
484+         csvo  =  CsvOutput (debug = args .debug , output_file = args .output )
485+         success  =  csvo .produce_from_file (args .input )
486+     else :
487+         print_stderr (f'ERROR: Unknown output format (--format): { args .format }  ' )
488+     if  not  success :
489+         exit (1 )
490+ 
491+ def  utils_certloc (parser , args ):
492+     """ 
493+     Run the "utils certloc" sub-command 
494+     Parameters 
495+     ---------- 
496+         parser: ArgumentParser 
497+             command line parser object 
498+         args: Namespace 
499+             Parsed arguments 
500+     """ 
501+     import  certifi 
502+     print (f'CA Cert File: { certifi .where ()}  ' )
503+ 
400504def  main ():
401505    """ 
402506    Run the ScanOSS CLI 
0 commit comments