From 633ae37a6a5a803337a96b94a51b2cf909ca90bf Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Thu, 25 Jul 2024 16:05:08 -0500 Subject: [PATCH] Add support for command invocation with a 'default verb' (#656) There are no plans to use this feature to allow the 'colcon' executable to be invoked without a verb, but other tools built on colcon-core's framework may not want multiple verbs. --- colcon_core/command.py | 30 ++++++++++++++++++++++++------ test/test_command.py | 23 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/colcon_core/command.py b/colcon_core/command.py index 1328b3b5..92fc7b62 100644 --- a/colcon_core/command.py +++ b/colcon_core/command.py @@ -93,7 +93,7 @@ def register_command_exit_handler(handler): def main( *, command_name='colcon', argv=None, verb_group_name=None, - environment_variable_group_name=None, + environment_variable_group_name=None, default_verb=None, ): """ Execute the main logic of the command. @@ -114,13 +114,19 @@ def main( :param str command_name: The name of the command invoked :param list argv: The list of arguments + :param str verb_group_name: The extension point group name for verbs + :param str environment_variable_group_name: The extension point group name + for environment variables + :param Type default_verb: The verb class type to invoke if no explicit + verb was provided on the command line :returns: The return code """ try: return _main( command_name=command_name, argv=argv, verb_group_name=verb_group_name, - environment_variable_group_name=environment_variable_group_name) + environment_variable_group_name=environment_variable_group_name, + default_verb=default_verb) except KeyboardInterrupt: return signal.SIGINT finally: @@ -132,6 +138,7 @@ def main( def _main( *, command_name, argv, verb_group_name, environment_variable_group_name, + default_verb ): # default log level, for searchability: COLCON_LOG_LEVEL colcon_logger.setLevel(logging.WARNING) @@ -151,6 +158,13 @@ def _main( parser = create_parser(environment_variable_group_name) + if default_verb is not None: + default_verb_instance = default_verb() + parser.set_defaults( + verb_parser=parser, verb_extension=default_verb_instance, + main=default_verb_instance.main) + add_parser_arguments(parser, default_verb_instance) + verb_extensions = get_verb_extensions(group_name=verb_group_name) # add subparsers for all verb extensions but without arguments for now @@ -163,7 +177,7 @@ def _main( known_args, _ = parser.parse_known_args(args=argv) # add the arguments for the requested verb - if known_args.verb_name: + if known_args.verb_name is not None: add_parser_arguments(known_args.verb_parser, known_args.verb_extension) args = parser.parse_args(args=argv) @@ -175,18 +189,22 @@ def _main( colcon_logger.debug(f'Parsed command line arguments: {args}') - # error: no verb provided - if args.verb_name is None: + # verify that one of the verbs set the 'main' attribute to be invoked later + if getattr(args, 'main', None) is None: print(parser.format_usage()) return 'Error: No verb provided' # set default locations for log files, for searchability: COLCON_LOG_PATH now = datetime.datetime.now() now_str = str(now)[:-7].replace(' ', '_').replace(':', '-') + if args.verb_name is None: + subdirectory = now_str + else: + subdirectory = f'{args.verb_name}_{now_str}' set_default_log_path( base_path=args.log_base, env_var=f'{command_name}_LOG_PATH'.upper(), - subdirectory=f'{args.verb_name}_{now_str}') + subdirectory=subdirectory) # add a file handler writing all levels if logging isn't disabled log_path = get_log_path() diff --git a/test/test_command.py b/test/test_command.py index c8c6b6e9..e4339fe3 100644 --- a/test/test_command.py +++ b/test/test_command.py @@ -96,6 +96,29 @@ def test_main_no_verbs_or_env(): assert e.value.code == 0 +def test_main_default_verb(): + with ExtensionPointContext(): + with patch( + 'colcon_core.argument_parser.get_argument_parser_extensions', + return_value={} + ): + with pytest.raises(SystemExit) as e: + main(argv=['--help'], default_verb=Extension1) + assert e.value.code == 0 + + with pytest.raises(SystemExit) as e: + main( + argv=['--log-level', 'invalid'], + default_verb=Extension1) + assert e.value.code == 2 + + with patch.object(Extension1, 'main', return_value=0) as mock_main: + assert not main( + argv=['--log-base', '/dev/null'], + default_verb=Extension1) + mock_main.assert_called_once() + + def test_create_parser(): with ExtensionPointContext(): parser = create_parser('colcon_core.environment_variable')