diff --git a/locales/en.json b/locales/en.json index 618e78e0..bede54c5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -20,6 +20,7 @@ "ldap_attribute_already_exists" : "Attribute already exists: '{:s}={:s}'", "invalid_usage" : "Invalid usage, pass --help to see help", + "deprecated_command" : "'{prog} {old}' is deprecated and will be removed in the future, use '{prog} {new}' instead", "argument_required" : "Argument {:s} is required", "invalid_argument": "Invalid argument '{:s}': {:s}", "pattern_not_match": "Does not match pattern", diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 422878c7..56c1a94c 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -437,11 +437,53 @@ class _CallbackAction(argparse.Action): else: setattr(namespace, self.dest, value) -class _OptionalSubParsersAction(argparse._SubParsersAction): +class _ExtendedSubParsersAction(argparse._SubParsersAction): + """Subparsers with extended properties for argparse + + It provides the following additional properties at initialization, + e.g. using `parser.add_subparsers`: + - required -- Either the subparser is required or not (default: False) + + It also provides the following additional properties for parsers, + e.g. using `subparsers.add_parser`: + - deprecated -- A list of deprecated command names + + """ def __init__(self, *args, **kwargs): required = kwargs.pop('required', False) - super(_OptionalSubParsersAction, self).__init__(*args, **kwargs) + super(_ExtendedSubParsersAction, self).__init__(*args, **kwargs) + self.required = required + self._deprecated_command_map = {} + + def add_parser(self, name, **kwargs): + deprecated = kwargs.pop('deprecated', []) + parser = super(_ExtendedSubParsersAction, self).add_parser( + name, **kwargs) + + # Append each deprecated command name + for command in deprecated: + self._deprecated_command_map[command] = name + self._name_parser_map[command] = parser + + return parser + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + + try: + # Check for deprecated command name + correct_name = self._deprecated_command_map[parser_name] + except KeyError: + pass + else: + # Warn the user and set the proper parser_name + logger.warning(m18n.g('deprecated_command', prog=parser.prog, + old=parser_name, new=correct_name)) + values[0] = correct_name + + return super(_ExtendedSubParsersAction, self).__call__( + parser, namespace, values, option_string) class ExtendedArgumentParser(argparse.ArgumentParser): @@ -450,7 +492,7 @@ class ExtendedArgumentParser(argparse.ArgumentParser): # Register additional actions self.register('action', 'callback', _CallbackAction) - self.register('action', 'parsers', _OptionalSubParsersAction) + self.register('action', 'parsers', _ExtendedSubParsersAction) def enqueue_callback(self, namespace, callback, values): queue = self._get_callbacks_queue(namespace) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index bacda7bc..68940fa5 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -264,17 +264,20 @@ class ActionsMapParser(BaseActionsMapParser): 'title': "actions", 'required': True }) - def add_action_parser(self, name, tid, action_help=None, **kwargs): + def add_action_parser(self, name, tid, action_help=None, deprecated=[], + **kwargs): """Add a parser for an action Keyword arguments: - action_help -- A brief description for the action + - deprecated -- A list of deprecated action names Returns: A new ExtendedArgumentParser object for the action """ - return self._subparsers.add_parser(name, help=action_help) + return self._subparsers.add_parser(name, help=action_help, + deprecated=deprecated) def parse_args(self, args, **kwargs): try: