0
votes

I'm using python argparse to manage commandline options. When the number of options becomes large, the help message (what argparse prints when you pass --help) is getting intimidating and hard to read, simply because it is to long. Other programs sometimes solve this issue by modularizing the help message: they only show "core options" with --help and have options like --help-modulea, --help-moduleb, etc.

I think that argparse allows for something similar in the presence of subcommands, essentially like git does: ./script.py command --help. My application however has no such thing as subcommands, only a big set of options (that can be grouped nicely, though).

Is there any (reasonable) way to do this with argparse?

1
You may just be looking for argument groups, though they don't help with suppressing some of the output. For that, you'll need to define your own option handlers. - chepner
I'm already using argument groups to group the arguments. (obviously) I'm not sure what you mean by "option handlers", though... - nafur
Look at the code for print_help and format_help. You'll see how the HelpFormatter is called, and how the groups are passed to it. Also look at the _help_action class (or some such name). I can imagine creating similar Action subclasses and/or formatters that display a subset of your argument_groups. - hpaulj

1 Answers

0
votes

Thanks to @hpaulj answer, I had a proper look at the argparse internals and came up with the following. While it has some caveats, I feel it's a good start:

class ModularHelpEnabler(argparse.Action):
    def __call__(self, parser, namespace, values, option_string = None):
        parser.enable_modular_help(self.const)

class ModularArgumentParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        self._modular_action_groups = {}
        modular_help_groups = kwargs.pop('modular_help_groups', [])
        super().__init__(*args, **kwargs)
        self._modular_help_groups = {}
        for name in modular_help_groups:
            self._modular_help_groups[name] = self.add_argument_group(name)

    def add_argument_group(self, *args, **kwargs):
        name = kwargs.pop('help_name', None)
        help_group = kwargs.pop('help_group', None)
        help_text = kwargs.pop('help_group', 'show help for {}')
        grp = super().add_argument_group(*args, **kwargs)
        if name is not None:
            self._modular_action_groups[grp] = name
            parser = self
            if help_group is not None:
                parser = self._modular_help_groups[help_group]
            parser.add_argument('--help-{}'.format(name), action=ModularHelpEnabler, nargs=0, const=grp, help = help_text.format(name))
        return grp
    def enable_modular_help(self, grp):
        del self._modular_action_groups[grp]
    def format_help(self):
        self._action_groups = [
            ag for ag in self._action_groups
            if ag not in self._modular_action_groups
        ]
        return super().format_help()

Caveats (that are not trivial to fix, AFAIK):

  • --help-foo does not imply --help.
  • --help-foo needs to be given before --help.

Unfortunately, --help directly triggers printing the help and exits. If we retain this behavior, everything that modifies the help message needs to be specified before --help itself. We could defer printing the help until after parsing has finished, however this would mean that --help no longer shadows parsing errors (as it does right now), changing the current behavior significantly.