From 6a6fb89fd0aba3c9d4cd694478de418a0ba0068a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 23 Jul 2017 15:47:20 -0400 Subject: [PATCH] Dirty hack to be able to display 'actions' and 'subcategories' in different section in --help --- moulinette/interfaces/__init__.py | 64 ++++++++++++++++++++++++++++++- moulinette/interfaces/cli.py | 11 ++++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 6d5d332b..31b83a7c 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -5,7 +5,8 @@ import os import errno import logging import argparse -from collections import deque +import copy +from collections import deque, OrderedDict from moulinette.core import (init_authenticator, MoulinetteError) @@ -460,7 +461,7 @@ class _ExtendedSubParsersAction(argparse._SubParsersAction): self.required = required self._deprecated_command_map = {} - def add_parser(self, name, **kwargs): + def add_parser(self, name, type_=None, **kwargs): deprecated = kwargs.pop('deprecated', False) deprecated_alias = kwargs.pop('deprecated_alias', []) @@ -477,6 +478,8 @@ class _ExtendedSubParsersAction(argparse._SubParsersAction): self._deprecated_command_map[command] = name self._name_parser_map[command] = parser + parser.type = type_ + return parser def __call__(self, parser, namespace, values, option_string=None): @@ -557,6 +560,63 @@ class ExtendedArgumentParser(argparse.ArgumentParser): action, arg_strings) return value + # Adapted from : + # https://github.com/python/cpython/blob/af26c15110b76195e62a06d17e39176d42c0511c/Lib/argparse.py#L2293-L2314 + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + + # Dirty hack to separate 'subcommands' + # into 'actions' and 'subcategories' + if action_group.title == "subcommands": + + # Make a copy of the "action group actions"... + choice_actions = action_group._group_actions[0]._choices_actions + actions_subparser = copy.copy(action_group._group_actions[0]) + subcategories_subparser = copy.copy(action_group._group_actions[0]) + + # Filter "action"-type and "subcategory"-type commands + actions_subparser.choices = OrderedDict([(k,v) for k,v in actions_subparser.choices.items() if v.type == "action"]) + subcategories_subparser.choices = OrderedDict([(k,v) for k,v in subcategories_subparser.choices.items() if v.type == "subcategory"]) + + actions_choices = actions_subparser.choices.keys() + subcategories_choices = subcategories_subparser.choices.keys() + + actions_subparser._choices_actions = [ c for c in choice_actions if c.dest in actions_choices ] + subcategories_subparser._choices_actions = [ c for c in choice_actions if c.dest in subcategories_choices ] + + # Display each section (actions and subcategories) + if actions_choices != []: + formatter.start_section("actions") + formatter.add_arguments([actions_subparser]) + formatter.end_section() + + if subcategories_choices != []: + formatter.start_section("subcategories") + formatter.add_arguments([subcategories_subparser]) + formatter.end_section() + + else: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + # This is copy-pasta from the original argparse.HelpFormatter : # https://github.com/python/cpython/blob/1e73dbbc29c96d0739ffef92db36f63aa1aa30da/Lib/argparse.py#L293-L383 diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 433da026..e6c23b7d 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -268,7 +268,7 @@ class ActionsMapParser(BaseActionsMapParser): """ parser = self._subparsers.add_parser(name, help=category_help, **kwargs) return self.__class__(self, parser, { - 'title': "actions", 'required': True + 'title': "subcommands", 'required': True }) def add_subcategory_parser(self, name, subcategory_help=None, **kwargs): @@ -281,7 +281,10 @@ class ActionsMapParser(BaseActionsMapParser): A new ActionsMapParser object for the category """ - parser = self._subparsers.add_parser(name, help=subcategory_help, **kwargs) + parser = self._subparsers.add_parser(name, + type_="subcategory", + help=subcategory_help, + **kwargs) return self.__class__(self, parser, { 'title': "actions", 'required': True }) @@ -299,7 +302,9 @@ class ActionsMapParser(BaseActionsMapParser): A new ExtendedArgumentParser object for the action """ - return self._subparsers.add_parser(name, help=action_help, + return self._subparsers.add_parser(name, + type_="action", + help=action_help, deprecated=deprecated, deprecated_alias=deprecated_alias)