baky
baky

Reputation: 671

Customize help among groups in argparse

I'm using argparse and I have various groups which have set of its own options.

Now with the --help option I do not want to show all the options by default. Only a set of groups options are to be shown for --help.

Other group options should be shown based on other help options, as --help_1, --help_2:

For example:

--help' to show Group 2 and 3
--help_1' to show Group 11 and 12
--help_2' to show Group 22 and 23

I know that we can disable the default --help option with using add_help=False but how do I get to display only selected group specific helps.

We can get the list of groups from the parser using _action_groups attribute, but they do not expose any print_help() option as such.

My sample code:

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('--help_a', action='store_true')
parser.add_argument('--help_b', action='store_true')

group1 = parser.add_argument_group("Feature 1")
group1.add_argument('--foo1')
group2 = parser.add_argument_group("Feature 2")
group2.add_argument('--foo2')
group3 = parser.add_argument_group("Feature 3")
group3.add_argument('--foo3')

# TODO: --help_a to only print "Feature 1" groups help
# and --help_b to print Feature 2 and 3's help.

EDIT: Using subparser and adding parsers(instead of group) will solve the above. But subparser doesn't fit in my case, as I am parsing it always, I only need to customize help to be displayed.

Upvotes: 3

Views: 1479

Answers (2)

hpaulj
hpaulj

Reputation: 231365

Here's the custom format_help approach:

import argparse

def format_help(self, groups=None):
    # self == parser
    formatter = self._get_formatter()

    # usage
    formatter.add_usage(self.usage, self._actions,
                        self._mutually_exclusive_groups)

    # description
    formatter.add_text(self.description)

    if groups is None:
        groups = self._action_groups

    # positionals, optionals and user-defined groups
    for action_group in groups:
        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()

<your parser>

args = parser.parse_args()
# _action_groups[:2] are the default ones
if args.help_a:
    print(format_help(parser, [parser._action_groups[2]]))
    parser.exit()
if args.help_b:
    print(format_help(parser, parser._action_groups[3:]))
    parser.exit()

Sample runs

1444:~/mypy$ python stack40718566.py --help_a
usage: stack40718566.py [-h] [--help_a] [--help_b] [--foo1 FOO1] [--foo2 FOO2]
                        [--foo3 FOO3]

Feature 1:
  --foo1 FOO1

1444:~/mypy$ python stack40718566.py --help_b
usage: stack40718566.py [-h] [--help_a] [--help_b] [--foo1 FOO1] [--foo2 FOO2]
                        [--foo3 FOO3]

Feature 2:
  --foo2 FOO2

Feature 3:
  --foo3 FOO3

So it's just like the default format_help, except it takes a groups parameter. It could even replace the default method in an ArgumentParser subclass.

We could also create a custom Help Action class that behaves like the standard help, except that it takes some sort of group_list parameter. But this post-parsing action is simpler to code and test.

Upvotes: 1

Nils Werner
Nils Werner

Reputation: 36757

I recommend against what you are trying to do.

You are solving a problem that isn't yours to solve. It is the job of your script to return usage information. It isn't your problem if that is a lot of text. The thing that you could do, you are doing: Put arguments into groups that make sense for the user. But the amount of text is not a problem of data structure but of data presentation.

Secondly, you would be following a convention nobody is using. There usually are

man command
command --help
command subcommand --help

Anything else would be confusing to first time users.

Also, if you have a lot of argument groups a person would always need to consult --help to find out which --help_* they would have to consult next. This can be frustrating to users when you could just present it in --help right away.

If you use multiple help pages, you would prevent the reuse of your help text. Searching, for example: Multiple pages cannot be searched without switching between them manually.

The right way to do is pass text through a paginator like less. This allows users to read the text page by page, search through it (press /) or save it to file:

command --help | less

For convenience some commands, like git log, even check if the output is an interactive terminal and automatically pass the output through less. This would mean

command --help > help.txt

saves the help to file, while

command --help

shows the help text in pagination, and searchable.

So my recommendation for you on both Windows and UNIX is

import os
import sys
import argparse
import subprocess


def less(data):
    if sys.stdout.isatty():
        if os.name == 'posix':
            cmd = "less"
        elif os.name == 'nt':
            cmd = "more" 

        process = subprocess.Popen([cmd], stdin=subprocess.PIPE)

        try:
            process.stdin.write(data)
            process.communicate()
        except IOError:
            pass
    else:
        print data


class MyArgumentParser(argparse.ArgumentParser):
    def print_help(self, file=None):
        less(self.format_help())
        self.exit()


parser = MyArgumentParser(prog='PROG')
group1 = parser.add_argument_group("Feature 1")
group1.add_argument('--foo1')
group2 = parser.add_argument_group("Feature 2")
group2.add_argument('--foo2')
group3 = parser.add_argument_group("Feature 3")
group3.add_argument('--foo3')
# parse some argument lists
print parser.parse_args()

Upvotes: 0

Related Questions