@@ -574,21 +574,21 @@ class McastDispatch(mtool):
574574
575575 """
576576
577- __proc__ = "multicast"
577+ __proc__ : str = "multicast"
578578
579- __prologue__ = "The Main Entrypoint."
579+ __prologue__ : str = "The Main Entrypoint."
580580
581- __epilogue__ = "Called from the command line, the __main__ component handles the CLI dispatch."
581+ __epilogue__ : str = "Called from the command line, this main component handles the CLI dispatch."
582582
583583 @classmethod
584- def setupArgs (cls , parser ):
584+ def setupArgs (cls , parser ) -> None :
585585 if parser is not None : # pragma: no branch
586586 for sub_tool in sorted (TASK_OPTIONS .keys ()):
587587 sub_parser = parser .add_parser (sub_tool , help = "..." )
588588 type (TASK_OPTIONS [sub_tool ]).setupArgs (sub_parser )
589589
590590 @staticmethod
591- def useTool (tool , ** kwargs ):
591+ def useTool (tool , ** kwargs ) -> tuple :
592592 """Will Handle launching the actual task functions."""
593593 theResult = None
594594 cached_list = sorted (TASK_OPTIONS .keys ())
@@ -601,7 +601,7 @@ def useTool(tool, **kwargs):
601601 _is_done = False
602602 return (_is_done , theResult ) # noqa
603603
604- def doStep (self , * args , ** kwargs ):
604+ def doStep (self , * args , ** kwargs ) -> tuple :
605605 """
606606 Executes the multicast tool based on parsed arguments.
607607
@@ -614,21 +614,21 @@ def doStep(self, *args, **kwargs):
614614 Returns:
615615 A tuple containing the exit status and the result of the tool execution.
616616 """
617- __EXIT_MSG = (64 , exceptions .EXIT_CODES [64 ][1 ])
617+ _response : tuple = (64 , exceptions .EXIT_CODES [64 ][1 ])
618618 try :
619619 (argz , _ ) = type (self ).parseArgs (* args )
620620 service_cmd = argz .cmd_tool
621621 argz .__dict__ .__delitem__ ("cmd_tool" )
622622 _TOOL_MSG = (self .useTool (service_cmd , ** argz .__dict__ ))
623623 if _TOOL_MSG [0 ]:
624- __EXIT_MSG = (0 , _TOOL_MSG )
624+ _response = (0 , _TOOL_MSG )
625625 else :
626- __EXIT_MSG = (70 , _TOOL_MSG )
626+ _response = (70 , _TOOL_MSG )
627627 if (sys .stdout .isatty ()): # pragma: no cover
628628 print (str (_TOOL_MSG ))
629629 except Exception as _cause : # pragma: no branch
630630 exit_code = exceptions .get_exit_code_from_exception (_cause )
631- __EXIT_MSG = (
631+ _response = (
632632 1 ,
633633 f"CRITICAL - Attempted '[{ __name__ } ]: { args } ' just failed! :: { exit_code } { _cause } " # noqa
634634 )
@@ -638,21 +638,23 @@ def doStep(self, *args, **kwargs):
638638 file = sys .stderr ,
639639 )
640640 print (f"{ exceptions .EXIT_CODES [exit_code ][1 ]} : { _cause } \n { _cause .args } " , file = sys .stderr )
641- return __EXIT_MSG # noqa
641+ return _response # noqa
642642
643643
644644@exceptions .exit_on_exception
645- def main (* argv ):
645+ def main (* argv ) -> tuple :
646646 """
647647 Do main event stuff.
648648
649649 Executes the multicast command-line interface, by parsing command-line arguments and dispatching
650650 the appropriate multicast operations.
651651
652- The main(*args) function in multicast is expected to return a POSIX compatible exit code.
653- Regardless of errors the result as an 'exit code' (int) is returned.
654- The only exception is multicast.__main__.main(*args) which will exit with the underlying
655- return codes.
652+ The main(*args) function in multicast is expected to return a POSIX compatible exit code and
653+ optional detail message. Regardless of errors the result as an 'exit code' (int) is returned,
654+ even if the optional details are not (eg. `tuple(int(exit_code), None)`).
655+ The only exception is when the error results in exiting the process, which will exit the
656+ python runtime with the underlying return codes, instead of returning directly to the then
657+ unreachable caller. See `multicast.exceptions.exit_on_exception` for the mechanisms involved.
656658 The expected return codes are as follows:
657659 = 0: Any nominal state (i.e. no errors and possibly success)
658660 ≥ 1: Any erroneous state (includes simple failure)
@@ -748,14 +750,91 @@ def main(*argv):
748750
749751
750752 """
751- dispatch = McastDispatch ()
753+ dispatch : McastDispatch = McastDispatch ()
752754 return dispatch (* argv )
753755
754756
755- if __name__ in '__main__' :
756- __EXIT_CODE = (1 , exceptions .EXIT_CODES [1 ][1 ])
757+ def cli () -> int :
758+ """
759+ Do main console script stuff.
760+
761+ Along with `main()`, `cli()` provides a main entrypoint for console usage of multicast.
762+
763+ `cli()` just calls `main()` with arguments from `sys.argv` and returns only the exit-code.
764+
765+ Through calling `multicast.__main__.main(sys.argv[1:])`, `cli()` ...
766+ > Executes the multicast command-line interface, by parsing command-line arguments and
767+ > dispatching the appropriate multicast operations.
768+ >
769+ > The main(*args) function in multicast is expected to return a POSIX compatible exit code...
770+
771+ `cli()` versus `main(*args)`:
772+ - The primary difference is the return types, whereas `main(*args)` returns a `tuple`,
773+ `cli()` returns only the first element as an `int`.
774+ - The secondary difference between `cli()` and `main(*args)` is that `main(*args)` requires
775+ arguments to be passed, whereas `cli()` will use `sys.argv` instead.
776+
777+ __Except__ in the case of errors, the result as an 'exit code' (int) is returned by `cli()`.
778+ The expected return codes are the same as those from `main(*args)`.
779+
780+ Args:
781+ None: Uses `sys.argv` instead.
782+
783+ Returns:
784+ int: the underlying exit code int
785+
786+ Minimal Acceptance Testing:
787+
788+ First set up test fixtures by importing multicast.
789+
790+ >>> import multicast
791+ >>> multicast.__main__.main is not None
792+ True
793+ >>>
794+
795+ Testcase 0: calls to cli should return an int.
796+ A: Test that the call to the `cli` function returns an int 0-3.
797+
798+ >>> tst_argv_args = ['''SAY''', '''--port=1234''', '''--message''', '''is required''']
799+ >>> sys.argv = tst_argv_args # normally arguments are aoutomaticly already in argv
800+ >>> test_code = multicast.__main__.cli()
801+ >>> test_code is not None
802+ True
803+ >>> type(test_code) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
804+ <...int...>
805+ >>> int(test_code) >= int(0)
806+ True
807+ >>> int(test_code) < int(4)
808+ True
809+ >>>
810+
811+ Testcase 1: main should error with usage.
812+ A: Test that the multicast component is initialized.
813+ B: Test that the recv component is initialized.
814+ C: Test that the main(recv) function is initialized.
815+ D: Test that the main(recv) function errors with a usage hint by default.
816+
817+ >>> multicast.__main__.main is not None
818+ True
819+ >>> test_code = multicast.__main__.cli() #doctest: +ELLIPSIS
820+ usage: multicast [-h | -V] [--use-std] [--daemon] CMD ...
821+ multicast...
822+ CRITICAL...
823+ >>> type(test_code) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
824+ <...int...>
825+ >>> int(test_code) >= int(0)
826+ True
827+ >>> int(test_code) < int(4)
828+ True
829+ >>>
830+ """
831+ __EXIT_CODE : tuple = (1 , exceptions .EXIT_CODES [1 ][1 ])
757832 if (sys .argv is not None ) and (len (sys .argv ) > 1 ):
758833 __EXIT_CODE = main (sys .argv [1 :])
759834 elif (sys .argv is not None ):
760835 __EXIT_CODE = main ([__name__ , "-h" ])
761- exit (__EXIT_CODE [0 ]) # skipcq: PYL-R1722 - intentionally allow overwriteing exit for testing
836+ return __EXIT_CODE [0 ]
837+
838+
839+ if __name__ in '__main__' :
840+ exit (cli ()) # skipcq: PYL-R1722 - intentionally allow overwriteing exit for testing
0 commit comments