3

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.

baky
  • 631
  • 1
  • 8
  • 17
  • Why not pass it through `less`? `python command.py --help | less` – Nils Werner Nov 21 '16 at 11:19
  • That doesn't seem right in my case. I don't want the external user using my API or script to parse on their side. The script should handle it on its own. – baky Nov 21 '16 at 11:22
  • I don't understand. Can you add some more information what groups you are using and why you'd want to show different help pages? – Nils Werner Nov 21 '16 at 11:23
  • Updated the question with sample code. I want to show different help because my script has a lot of options and showing all of it makes it too verbose and less useful. And there are a lot of varied users using the single script based on their requirement. So, by default I do not want it to show all the options. – baky Nov 21 '16 at 11:35
  • Look at the code for `parser.format_help`. That's the function that creates a formatter object, and passes parser text and content to it. It loops over `action_groups`. So a custom version could loop over a subset of those groups. – hpaulj Nov 21 '16 at 18:37
  • Thanks, your answer helped. – baky Nov 23 '16 at 08:54

2 Answers2

1

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.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Thanks, I too was thinking on similar lines, as in override the _ActionGroup class itself which takes as extra parameter as 'help_bucket', meaning which help to use. But this one will do the trick and is cleaner and simple too. One thing though, usage displays all the options by default, this is can be resolved easily by adding only the needed actions in add_usage. Thanks again – baky Nov 22 '16 at 06:19
0

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()
Community
  • 1
  • 1
Nils Werner
  • 34,832
  • 7
  • 76
  • 98
  • Thanks for the detailed answer. Regarding your above comment mentioning how people won't be able to look into which help to use, I will be adding a 'help_all' option, so that this is not a concern. BTW, i will still look if something similar to my question is possible – baky Nov 21 '16 at 12:20
  • @hpaulj have posted solution for the same. Please have a look – baky Dec 15 '16 at 06:49