From 0d6e5d4a137d105b205f602fa1c0969fdd6b50f2 Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Thu, 30 Jan 2020 19:29:39 -0800 Subject: [PATCH] Let subparser_betterhelp inspect parser for aliases. --- voussoirkit/betterhelp.py | 53 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/voussoirkit/betterhelp.py b/voussoirkit/betterhelp.py index 9c9a2bf..7d791c7 100644 --- a/voussoirkit/betterhelp.py +++ b/voussoirkit/betterhelp.py @@ -1,5 +1,5 @@ +import argparse import functools -import textwrap HELPSTRINGS = {'', 'help', '-h', '--help'} @@ -41,7 +41,14 @@ def add_previews(docstring, sub_docstrings): docstring = docstring.format(**previews) return docstring -def betterhelp(docstring): +def betterhelp(parser, docstring): + ''' + This decorator actually doesn't need the `parser`, but the + subparser_betterhelp decorator does, so in the interest of having similar + function signatures I'm making it required here too. I figure it's the + lesser of two evils. Plus, maybe someday I'll find a need for it and won't + have to make any changes to do it. + ''' def wrapper(main): @functools.wraps(main) def wrapped(argv): @@ -55,7 +62,47 @@ def betterhelp(docstring): return wrapped return wrapper -def subparser_betterhelp(main_docstring, sub_docstrings): +def get_subparser_action(parser): + for action in parser._actions: + if isinstance(action, argparse._SubParsersAction): + return action + raise TypeError('Couldn\'t locate the SubParsersAction.') + + +def set_alias_docstrings(sub_docstrings, subparser_action): + ''' + When using subparser aliases: + + subp = parser.add_subparser('command', aliases=['comm']) + + The _SubParsersAction will contain a dictionary `choices` of + {'command': ArgumentParser, 'comm': ArgumentParser}. + This choices dict does not indicate which one was the original name; + all aliases are equal. So, we'll identify which names are aliases because + their ArgumentParsers will have the same ID in memory. And, as long as one + of those aliases is in the provided docstrings, all the other aliases will + get that docstring too. + ''' + sub_docstrings = {name.lower(): docstring for (name, docstring) in sub_docstrings.items()} + aliases = {} + primary_aliases = {} + for (sp_name, sp) in subparser_action.choices.items(): + sp_name = sp_name.lower() + aliases.setdefault(id(sp), []).append(sp_name) + if sp_name in sub_docstrings: + primary_aliases[id(sp)] = sp_name + + for (sp_id, sp_aliases) in aliases.items(): + primary_alias = primary_aliases[sp_id] + for sp_alias in sp_aliases: + sub_docstrings[sp_alias] = sub_docstrings[primary_alias] + + return sub_docstrings + +def subparser_betterhelp(parser, main_docstring, sub_docstrings): + subparser_action = get_subparser_action(parser) + sub_docstrings = set_alias_docstrings(sub_docstrings, subparser_action) + def wrapper(main): @functools.wraps(main) def wrapped(argv):