2

I have an application with several dozens of CLI parameters. I use python argparse to parse arguments. Most of them are rarely used, only 5 or 6 are frequent. I don't want to clutter up the output of --help for simple cases, however there still should be a possibility to see description for all parameters somehow.

It is possible to have several verbosity levels for help in argparse? Here's how I expect it to be.

$ myapp -h
optional arguments:
 --foo         Do foo stuff
 --bar         Do bar stuff

$ myapp -hh # or myapp --expert-mode -h
optional arguments:
  --foo         Do foo stuff
  --bar         Do bar stuff

expert arguments:
  --use-warp-drive
  --no-fasten-seatbelts
... 50 more arguments

My first idea was to create two ArgumentParser-s, the basic one and the verbose one. First, parse_args of the basic was called. If --expert flag is present, the verbose parser is created and called. The approach has two downsides. First, -h is unconditionally handled by the first parser, so I should implement my own analogous help flag. Second, expert options are not even parsed without --expert flag, and I want them to be parsed unconditionally (but not to pop up in help).

Note: the solution should be python2 compliant.


Update: using the ideas from Lior Cohen's answer and this question, I created a working example. In my case it was easier to make a whitelist of "simple" options, so it was even unnecessary to use tagged action types.

Illustrating code follows.

class ExpertHelpFormatter(argparse.HelpFormatter):
    skip_expert_section = True
    whitelist = set(("foo",))

    def add_usage(self, usage, actions, groups, prefix=None):
        if self.skip_expert_section:
            actions = [action for action in actions if action.dest in self.whitelist]
        ret = super(ExpertHelpFormatter, self).add_usage(usage, actions, groups, prefix)
        if self.skip_expert_section:
            self.add_text("Use -hh for detailed help.")
        return ret

    def add_argument(self, action):
        if self.skip_expert_section and action.dest not in self.whitelist:
            return
        super(ExpertHelpFormatter, self).add_argument(action)

def main():
    parser = argparse.ArgumentParser(add_help=False, formatter_class=ExpertHelpFormatter)

    parser.add_argument("-h", "--help", action="count", default=0)
    parser.add_argument("--foo")
    parser.add_argument("--use-warp-drive", action="store_true")

    args = parser.parse_args()

    if args.help == 1:
        print parser.format_help()
        return
    elif args.help > 1:
        ExpertHelpFormatter.skip_expert_section = False
        print parser.format_help()
        return
Ivan Smirnov
  • 4,365
  • 19
  • 30

1 Answers1

1

Here is a layout that should give you what you need, though it is not "free of work".

  1. Disable default help by add_help = False (see here). this will let you still use -h and --help with action you want.
  2. You need to "tag" your expert arguments by type that will store somehow the fact that foo is "'simple" and use-warp-drive is "expert". You can add an attribute to the Action or save a global dict in the parser itself.
  3. Write an Help Formatter (you can see example). Then, according to the above "tag", you can suppress the "expert" help by returning "" in case of "simple" mode.

Hope this is helping.

Lior Cohen
  • 5,570
  • 2
  • 14
  • 30
  • Thanks, custom formatters did their job. I included the example to my question in the edit. However, the docs for argparse state that methods of HelpFormatter are not a part of API, only class names are. In the solution I had to override `add_usage` and `add_argument`. Did you had in mind any solutions without exploiting non-public methods? – Ivan Smirnov Dec 27 '20 at 22:44
  • IMHO this can be done w/o override. `type` is a documented argument that takes your argument and do something, `formatter_class` is documented too – Lior Cohen Dec 28 '20 at 20:52
  • While those are indeed documented, specific behavior of the formatter on actions is not. How can I adjust it without implementing my own override of `add_argument`? I can't see any way of injecting the dependency on type otherwise. – Ivan Smirnov Dec 28 '20 at 21:36
  • for example you can create a callable `expert_int(arg)` that wraps `int` but also inject the fact that it is arg is "expert" to a dict you hold on the parser as another attribute. – Lior Cohen Dec 28 '20 at 22:57
  • The problem is not in checking if the arg is expert or not: I have a whitelist of non-expert items, and it can be simply hardcoded. The problem is how to detect expert arguments in the formatter without overriding its methods and choose whether to show help. – Ivan Smirnov Dec 28 '20 at 23:24