@@ -731,3 +731,354 @@ class SubcommandInitializer:
731
731
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
732
732
# End of the SubcommandInitializer class
733
733
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
734
+
735
+ def perror (* args , ** kwargs ):
736
+ """Print a message to the standard error stream."""
737
+ print (* args , file = sys .stderr , ** kwargs )
738
+
739
+
740
+ def handle_error (e , exit_code ):
741
+ """Print an error message and exit the script.
742
+
743
+ Args:
744
+ e (Exception): The exception object caught.
745
+ exit_code (int): The exit code to use when exiting the script.
746
+ """
747
+ error_message = str (e ).strip ("'\" " )
748
+ perror (f"Error: { error_message } " )
749
+ sys .exit (exit_code )
750
+
751
+
752
+ def run_command_with_error_handling (args , qmctl , parser ):
753
+ """Execute the subcommand with centralized error handling.
754
+
755
+ Args:
756
+ args: The parsed command-line arguments.
757
+ qmctl: An instance of the QMCTL class.
758
+ parser: The argparse parser object, used for printing usage.
759
+ """
760
+ try :
761
+ if not args .subcommand :
762
+ parser .print_usage ()
763
+ perror ("Error: requires a subcommand" )
764
+ sys .exit (errno .EINVAL )
765
+ args .func (args , qmctl )
766
+ except IndexError as e :
767
+ handle_error (e , errno .EINVAL )
768
+ except KeyError as e :
769
+ handle_error (e , 1 )
770
+ except NotImplementedError as e :
771
+ handle_error (e , errno .ENOTSUP )
772
+ except subprocess .CalledProcessError as e :
773
+ handle_error (e , e .returncode )
774
+ except KeyboardInterrupt :
775
+ sys .exit (0 )
776
+ except ConnectionError as e :
777
+ handle_error (e , errno .EINVAL )
778
+ except ValueError as e :
779
+ handle_error (e , errno .EINVAL )
780
+ except IOError as e :
781
+ handle_error (e , errno .EIO )
782
+
783
+
784
+ def create_argument_parser (description ):
785
+ """Create and configure the argument parser for the CLI.
786
+
787
+ Args:
788
+ description (str): The description text for the CLI tool.
789
+
790
+ Returns:
791
+ An initialized ArgumentParser for the CLI.
792
+ """
793
+ parser = ArgumentParserWithDefaults (
794
+ prog = "qmctl" ,
795
+ description = description ,
796
+ formatter_class = argparse .RawTextHelpFormatter ,
797
+ )
798
+ configure_arguments (parser )
799
+ return parser
800
+
801
+
802
+ def configure_arguments (parser ):
803
+ """Configure the base command-line arguments for the parser.
804
+
805
+ Args:
806
+ parser (argparse.ArgumentParser): The main argument parser.
807
+ """
808
+ verbosity_group = parser .add_mutually_exclusive_group ()
809
+ verbosity_group .add_argument (
810
+ "-v" , "--verbose" , action = "store_true" , help = "Enable verbose output."
811
+ )
812
+
813
+
814
+ def parse_arguments (parser ):
815
+ """Parse command line arguments.
816
+
817
+ Args:
818
+ parser (argparse.ArgumentParser): The argument parser.
819
+
820
+ Returns:
821
+ The parsed arguments object.
822
+ """
823
+ return parser .parse_args ()
824
+
825
+
826
+ def post_parse_setup (args ):
827
+ """Perform additional setup after parsing arguments.
828
+
829
+ Args:
830
+ args: The parsed command-line arguments.
831
+ """
832
+ pass
833
+
834
+
835
+ def init_cli ():
836
+ """Initialize the QMCTL CLI, parse arguments, and set up the environment.
837
+
838
+ Returns:
839
+ A tuple containing the configured parser and the parsed arguments.
840
+ """
841
+ description = get_description ()
842
+ parser = create_argument_parser (description )
843
+ configure_subcommands (parser )
844
+ args = parse_arguments (parser )
845
+ post_parse_setup (args )
846
+ return parser , args
847
+
848
+
849
+ def get_description ():
850
+ """Return the description of the QMCTL tool.
851
+
852
+ Returns:
853
+ A string containing the tool's description.
854
+ """
855
+ return "QmCTL: Command Line Interface for QMCTL"
856
+
857
+
858
+ def configure_subcommands (parser ):
859
+ """Add subcommand parsers to the main argument parser.
860
+
861
+ Args:
862
+ parser (argparse.ArgumentParser): The main argument parser.
863
+ """
864
+ subparsers = parser .add_subparsers (dest = "subcommand" )
865
+ subparsers .required = False
866
+ init_show_subcommand (subparsers )
867
+ init_exec_subcommand (subparsers )
868
+ init_execin_subcommand (subparsers )
869
+ init_cp_subcommand (subparsers )
870
+
871
+
872
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
873
+ # Start of the Subcommand initialization functions
874
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
875
+
876
+
877
+ def init_show_subcommand (subparsers ):
878
+ """Initialize the 'show' subcommand for displaying container info.
879
+
880
+ Args:
881
+ subparsers: The subparser object from the main parser.
882
+ """
883
+ name = "show"
884
+ help_text = "Show information about the QM container"
885
+ default_func = handle_show_command
886
+ args_config = [
887
+ {
888
+ 'name' : ['show_command_topic' ],
889
+ 'nargs' : '?' ,
890
+ 'default' : 'container' ,
891
+ 'choices' : [
892
+ "container" ,
893
+ "unix-domain-sockets" ,
894
+ "shared-memory" ,
895
+ "resources" ,
896
+ "available-devices" ,
897
+ "namespaces" ,
898
+ "all" ,
899
+ ],
900
+ 'help' : "What to show"
901
+ },
902
+ {
903
+ 'name' : ['--json' ],
904
+ 'action' : 'store_true' ,
905
+ 'help' : "Output as JSON"
906
+ }
907
+ ]
908
+ SubcommandInitializer (
909
+ subparsers , name , help_text , default_func , args_config
910
+ ).initialize ()
911
+
912
+
913
+ def init_exec_subcommand (subparsers ):
914
+ """Initialize the 'exec' subcommand for running commands.
915
+
916
+ Args:
917
+ subparsers: The subparser object from the main parser.
918
+ """
919
+ name = "exec"
920
+ help_text = "Run command inside QM container"
921
+ default_func = handle_exec_command
922
+ args_config = [
923
+ {
924
+ 'name' : ['cmd' ],
925
+ 'nargs' : argparse .REMAINDER ,
926
+ 'help' : "Command to execute"
927
+ },
928
+ {
929
+ 'name' : ['--json' ],
930
+ 'action' : 'store_true' ,
931
+ 'help' : "Output as JSON"
932
+ }
933
+ ]
934
+ SubcommandInitializer (
935
+ subparsers , name , help_text , default_func , args_config
936
+ ).initialize ()
937
+
938
+
939
+ def init_execin_subcommand (subparsers ):
940
+ """Initialize the 'execin' subcommand for nested execution.
941
+
942
+ Args:
943
+ subparsers: The subparser object from the main parser.
944
+ """
945
+ name = "execin"
946
+ help_text = "Run command inside QM container in a nested container"
947
+ default_func = handle_execin_command
948
+ args_config = [
949
+ {
950
+ 'name' : ['cmd' ],
951
+ 'nargs' : argparse .REMAINDER ,
952
+ 'help' : "Command to execute in the nested container"
953
+ },
954
+ {
955
+ 'name' : ['--json' ],
956
+ 'action' : 'store_true' ,
957
+ 'help' : "Output as JSON"
958
+ }
959
+ ]
960
+ SubcommandInitializer (
961
+ subparsers , name , help_text , default_func , args_config
962
+ ).initialize ()
963
+
964
+
965
+ def init_cp_subcommand (subparsers ):
966
+ """Initialize the 'cp' subcommand for copying files.
967
+
968
+ Args:
969
+ subparsers: The subparser object from the main parser.
970
+ """
971
+ name = "cp"
972
+ help_text = "Copy files between host and QM container"
973
+ default_func = handle_cp_command
974
+ args_config = [
975
+ {
976
+ 'name' : ['paths' ],
977
+ 'nargs' : 2 ,
978
+ 'help' : ("Source and destination paths (e.g., "
979
+ "/path/to/file /path/in/container)" )
980
+ },
981
+ {
982
+ 'name' : ['--json' ],
983
+ 'action' : 'store_true' ,
984
+ 'help' : "Output as JSON"
985
+ }
986
+ ]
987
+ SubcommandInitializer (
988
+ subparsers , name , help_text , default_func , args_config
989
+ ).initialize ()
990
+
991
+
992
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
993
+ # End of the Subcommand initialization functions
994
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
995
+
996
+
997
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
998
+ # Start of the handle command functions
999
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1000
+
1001
+
1002
+ def handle_show_command (args , qmctl ):
1003
+ """Handle the logic for the 'show' subcommand.
1004
+
1005
+ Args:
1006
+ args: The parsed command-line arguments.
1007
+ qmctl: An instance of the QMCTL class.
1008
+ """
1009
+ all_show_commands = {
1010
+ "all" : qmctl .show_all ,
1011
+ "container" : qmctl .show_container ,
1012
+ "unix-domain-sockets" : qmctl .show_unix_sockets ,
1013
+ "shared-memory" : qmctl .show_shared_memory ,
1014
+ "resources" : qmctl .show_resources ,
1015
+ "available-devices" : qmctl .show_available_devices ,
1016
+ "namespaces" : qmctl .show_namespaces ,
1017
+ }
1018
+
1019
+ command_to_execute = all_show_commands .get (
1020
+ args .show_command_topic ,
1021
+ qmctl .show_container if args .show_command_topic is None else None )
1022
+
1023
+ if command_to_execute :
1024
+ command_to_execute (output_json = args .json , pretty = True )
1025
+ else :
1026
+ print (f"Error: Unknown show command '{ args .show_command_topic } '." )
1027
+
1028
+
1029
+ def handle_exec_command (args , qmctl ):
1030
+ """Handle the logic for the 'exec' subcommand.
1031
+
1032
+ Args:
1033
+ args: The parsed command-line arguments.
1034
+ qmctl: An instance of the QMCTL class.
1035
+ """
1036
+ qmctl .exec_in_container (
1037
+ command = args .cmd , output_json = args .json , pretty = True
1038
+ )
1039
+
1040
+
1041
+ def handle_execin_command (args , qmctl ):
1042
+ """Handle the logic for the 'execin' subcommand.
1043
+
1044
+ Args:
1045
+ args: The parsed command-line arguments.
1046
+ qmctl: An instance of the QMCTL class.
1047
+ """
1048
+ qmctl .execin_in_container (
1049
+ command = args .cmd , output_json = args .json , pretty = True
1050
+ )
1051
+
1052
+
1053
+ def handle_cp_command (args , qmctl ):
1054
+ """Handle the logic for the 'cp' subcommand.
1055
+
1056
+ Args:
1057
+ args: The parsed command-line arguments.
1058
+ qmctl: An instance of the QMCTL class.
1059
+ """
1060
+ qmctl .copy_in_container (
1061
+ paths = args .paths , output_json = args .json , pretty = True
1062
+ )
1063
+
1064
+
1065
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1066
+ # End of the handle command functions
1067
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1068
+
1069
+
1070
+ def main ():
1071
+ """Define the main execution flow of the script."""
1072
+ parser , args = init_cli ()
1073
+ qmctl = QMCTL (verbose = args .verbose )
1074
+ try :
1075
+ import argcomplete
1076
+ argcomplete .autocomplete (parser )
1077
+ except Exception :
1078
+ pass
1079
+
1080
+ run_command_with_error_handling (args , qmctl , parser )
1081
+
1082
+
1083
+ if __name__ == "__main__" :
1084
+ main ()
0 commit comments