99import multiprocessing
1010import os
1111import pathlib
12+ import platform
1213import shutil
1314import subprocess
1415import sys
1516import typing
1617import uuid
1718
18- __version__ = '1.2.7 '
19+ __version__ = '1.2.8 '
1920
2021DEFAULT_THREAD_COUNT = min (multiprocessing .cpu_count (), 64 )
2122
@@ -59,7 +60,7 @@ def ranged_float_type(value):
5960 return f
6061
6162 parser = argparse .ArgumentParser (
62- description = f'%(prog)s v. { __version__ } : calculate ANI and cluster '
63+ description = f'%(prog)s v{ __version__ } : calculate ANI and cluster '
6364 'virus (meta)genome sequences' ,
6465 add_help = False ,
6566 )
@@ -117,7 +118,7 @@ def ranged_float_type(value):
117118 '--min-kmers' ,
118119 metavar = "<int>" ,
119120 type = int ,
120- default = 10 ,
121+ default = 20 ,
121122 help = 'Filter genome pairs based on minimum number of shared k-mers '
122123 '[%(default)s]'
123124 )
@@ -531,7 +532,7 @@ def ranged_float_type(value):
531532 '--bin' ,
532533 metavar = '<file>' ,
533534 type = pathlib .Path ,
534- dest = "BIN_CLUSTY " ,
535+ dest = "bin_clusty " ,
535536 default = f'{ BIN_CLUSTY } ' ,
536537 help = 'Path to the Clusty binary [%(default)s]'
537538 )
@@ -603,8 +604,8 @@ def get_uuid() -> str:
603604 return f'vclust-{ str (uuid .uuid4 ().hex )[:10 ]} '
604605
605606
606- def validate_binary (bin_path : pathlib .Path ) -> pathlib .Path :
607- """Validates the existence and executability of a binary file.
607+ def _validate_binary (bin_path : pathlib .Path ) -> pathlib .Path :
608+ """Validates the presence and executability of a binary file.
608609
609610 This function checks if the provided path points to an existing binary file
610611 and if it is executable. It also attempts to run the binary to ensure it
@@ -618,16 +619,16 @@ def validate_binary(bin_path: pathlib.Path) -> pathlib.Path:
618619 pathlib.Path: The resolved path to the binary file.
619620
620621 Raises:
621- SystemExit : If the binary file does not exist, is not executable, or
622- if running the binary encounters an error.
622+ RuntimeError : If the binary file does not exist, is not executable,
623+ or if running the binary encounters an error.
623624 """
624625 bin_path = bin_path .resolve ()
625626
626627 if not bin_path .exists ():
627- exit (f'error: Executable not found: { bin_path } ' )
628+ raise RuntimeError (f'File not found: { bin_path } ' )
628629
629630 if not bin_path .is_file () or not os .access (bin_path , os .X_OK ):
630- exit (f'error: Binary file not executable: { bin_path } ' )
631+ raise RuntimeError (f'Binary file not executable: { bin_path } ' )
631632
632633 try :
633634 subprocess .run (
@@ -638,14 +639,21 @@ def validate_binary(bin_path: pathlib.Path) -> pathlib.Path:
638639 check = True
639640 )
640641 except subprocess .CalledProcessError as e :
641- exit (f'error: Running { bin_path } failed with message: { e .stderr } ' )
642+ raise RuntimeError (f'Running { bin_path } failed with message: { e .stderr } ' )
642643 except OSError as e :
643- exit (f'error: OSError in { bin_path } - { e } ' )
644+ raise RuntimeError (f'OSError in { bin_path } - { e } ' )
644645 except Exception as e :
645- exit (f'error: Unexpected error in binary { bin_path } - { e } ' )
646+ raise RuntimeError (f'Unexpected error in binary { bin_path } - { e } ' )
646647 return bin_path
647648
648649
650+ def validate_binary (bin_path : pathlib .Path ) -> pathlib .Path :
651+ try :
652+ return _validate_binary (bin_path )
653+ except RuntimeError as e :
654+ sys .exit (f'error: { e } ' )
655+
656+
649657def validate_args_fasta_input (args , parser ) -> argparse .Namespace :
650658 """Validates the arguments for FASTA input."""
651659 args .is_multifasta = True
@@ -732,13 +740,13 @@ def run(
732740 )
733741 except subprocess .CalledProcessError as e :
734742 logger .error (f'Process { " " .join (cmd )} failed with message: { e .stderr } ' )
735- exit (1 )
743+ sys . exit (1 )
736744 except OSError as e :
737745 logger .error (f'OSError: { " " .join (cmd )} failed with message: { e } ' )
738- exit (1 )
746+ sys . exit (1 )
739747 except Exception as e :
740748 logger .error (f'Unexpected: { " " .join (cmd )} failed with message: { e } ' )
741- exit (1 )
749+ sys . exit (1 )
742750 logger .info (f'Done' )
743751 return process
744752
@@ -1145,11 +1153,75 @@ def cmd_clusty(
11451153 return cmd
11461154
11471155
1148- def vclust_info ():
1149- print (f'Vclust { __version__ } ' )
1150- for bin_path in [BIN_KMERDB , BIN_FASTASPLIT , BIN_LZANI , BIN_CLUSTY ]:
1151- validate_binary (bin_path )
1152- print (f'{ bin_path .name :<20} ok' )
1156+ def vclust_info () -> None :
1157+ """
1158+ Displays the Vclust version, installation paths, and binary dependencies.
1159+ Checks for the presence and executable status of required binaries.
1160+
1161+ Exits with a non-zero status if any dependencies are missing or
1162+ not executable.
1163+
1164+ Returns:
1165+ None
1166+
1167+ Raises:
1168+ SystemExit: If any binary dependencies are missing or not executable.
1169+
1170+ """
1171+ # ANSI color codes for terminal output.
1172+ GREEN = '\033 [92m'
1173+ RED = '\033 [91m'
1174+ RESET = '\033 [0m'
1175+
1176+ binaries = {
1177+ 'Kmer-db' : BIN_KMERDB ,
1178+ 'LZ-ANI' : BIN_LZANI ,
1179+ 'Clusty' : BIN_CLUSTY ,
1180+ 'multi-fasta-split' : BIN_FASTASPLIT ,
1181+ }
1182+
1183+ output_lines = [
1184+ f'Vclust version { __version__ } (Python { platform .python_version ()} )' ,
1185+ '' ,
1186+ 'Installed at:' ,
1187+ f' { pathlib .Path (__file__ ).resolve ()} ' ,
1188+ f' { BIN_DIR .resolve ()} ' ,
1189+ '' ,
1190+ 'Binary dependencies:' ,
1191+ ]
1192+
1193+ errors = [] # List to collect any errors encountered during binary checks.
1194+
1195+ # Check each binary's presence and version.
1196+ for name , path in binaries .items ():
1197+ try :
1198+ _validate_binary (path )
1199+ version = subprocess .run (
1200+ [str (path ), '-version' if name == 'Kmer-db' else '--version' ],
1201+ stdout = subprocess .PIPE ,
1202+ stderr = subprocess .PIPE ,
1203+ text = True ,
1204+ check = True
1205+ ).stderr .strip ()
1206+ output_lines .append (f' { name :<20} v{ version :<10} ' )
1207+ except Exception as e :
1208+ output_lines .append (f' { name :<20} [error]' )
1209+ errors .append ((name , e ))
1210+
1211+ # Append the status summary based on any encountered errors.
1212+ output_lines .append ('' )
1213+
1214+ if errors :
1215+ output_lines .append (f'{ RED } Status: error{ RESET } ' )
1216+ output_lines .extend (f" - { name } : { error } " for name , error in errors )
1217+ else :
1218+ output_lines .append (f'{ GREEN } Status: ok{ RESET } ' )
1219+
1220+ # Output the complete information.
1221+ print ('\n ' .join (output_lines ))
1222+
1223+ if errors :
1224+ sys .exit (1 )
11531225
11541226
11551227class CustomHelpFormatter (argparse .HelpFormatter ):
@@ -1324,7 +1396,7 @@ def main():
13241396
13251397 # Cluster
13261398 elif args .command == 'cluster' :
1327- args .BIN_CLUSTY = validate_binary (args .BIN_CLUSTY )
1399+ args .bin_clusty = validate_binary (args .bin_clusty )
13281400 args = validate_args_cluster (args , parser )
13291401
13301402 cmd = cmd_clusty (
@@ -1344,7 +1416,7 @@ def main():
13441416 leiden_resolution = args .leiden_resolution ,
13451417 leiden_beta = args .leiden_beta ,
13461418 leiden_iterations = args .leiden_iterations ,
1347- bin_path = args .BIN_CLUSTY ,
1419+ bin_path = args .bin_clusty ,
13481420 )
13491421 p = run (cmd , args .verbose , logger )
13501422
0 commit comments