19

I have the following code:

parser = argparse.ArgumentParser(description='Postfix Queue Administration Tool',
        prog='pqa',
        usage='%(prog)s [-h] [-v,--version]')
parser.add_argument('-l', '--list', action='store_true',
        help='Shows full overview of all queues')
parser.add_argument('-q', '--queue', action='store', metavar='<queue>', dest='queue',
        help='Show information for <queue>')
parser.add_argument('-d', '--domain', action='store', metavar='<domain>', dest='domain',
        help='Show information about a specific <domain>')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
args = parser.parse_args()

Which gives me output like this:

%./pqa                                                                                                                        
usage: pqa [-h] [-v,--version]

Postfix Queue Administration Tool

optional arguments:
  -h, --help            show this help message and exit
  -l, --list            Shows full overview of all queues
  -q <queue>, --queue <queue>
                        Show information for <queue>
  -d <domain>, --domain <domain>
                        Show information about a specific <domain>
  -v, --version         show program's version number and exit

I would very much like to know how I can 'group' commands that have two versions (ie. long options) which each also show a metavar.

This is mostly an aesthetic issue on my side, but I would still like to fix this. I have been reading manuals and texts on the internet, but either the information just isn't there or I am totally missing something here :)

Peter van Arkel
  • 519
  • 1
  • 5
  • 14

3 Answers3

24

Putting hpaulj's answer into actual code, something like this works:

class CustomHelpFormatter(argparse.HelpFormatter):
    def _format_action_invocation(self, action):
        if not action.option_strings or action.nargs == 0:
            return super()._format_action_invocation(action)
        default = self._get_default_metavar_for_optional(action)
        args_string = self._format_args(action, default)
        return ', '.join(action.option_strings) + ' ' + args_string

fmt = lambda prog: CustomHelpFormatter(prog)
parser = argparse.ArgumentParser(formatter_class=fmt)

To additionally extend the default column size for help variables, add constructor to CustomHelpFormatter:

def __init__(self, prog):
    super().__init__(prog, max_help_position=40, width=80)

Seeing it in action:

usage: bk set [-h] [-p] [-s r] [-f] [-c] [-b c] [-t x y] [-bs s] [-bc c]
              [--crop x1 y1 x2 y2] [-g u r d l]
              monitor [path]

positional arguments:
  monitor                    monitor number
  path                       input image path

optional arguments:
  -h, --help                 show this help message and exit
  -p, --preview              previews the changes without applying them
  -s, --scale r              scales image by given factor
  -f, --fit                  fits the image within monitor work area
  -c, --cover                makes the image cover whole monitor work area
  -b, --background c         selects background color
  -t, --translate x y        places the image at given position
  -bs, --border-size s       selects border width
  -bc, --border-color c      selects border size
  --crop x1 y1 x2 y2         selects crop area
  -g, --gap, --gaps u r d l  keeps "border" around work area
rr-
  • 14,303
  • 6
  • 45
  • 67
  • While this also does what I want, I feel that hpaulj's code in the accepted answer is easier to read and did exactly what I asked in my question. Thanks for the extra elaboration though! :) – Peter van Arkel Jun 30 '15 at 20:54
  • Nothing wrong with the accepted answer, but I much prefer this, although it took me a bit to work out that my default python version was 2.7 and this didn't play too nicely, when run under 3.4 it worked like a charm. Thanks! – Madivad Jun 24 '16 at 09:38
  • Also, if you know how to mould to work in Python<3 it would be appreciated for other scripts that I can't convert – Madivad Jun 26 '16 at 10:34
9

Another solution, using custom descriptions

if you set the metavar='', the help line becomes:

-q , --queue          Show information for <queue>

Here I suppress the regular help lines, and replace them with the description lines for a group:

parser = argparse.ArgumentParser(description='Postfix Queue Administration Tool',
        prog='pqa',
        usage='%(prog)s [-h] [-v,--version]',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        )
parser.add_argument('-l', '--list', action='store_true',
        help='Shows full overview of all queues')
g = parser.add_argument_group(title='information options',
        description='''-q, --queue <queue>     Show information for <queue>
-d, --domain <domain>   Show information about a specific <domain>''')
g.add_argument('-q', '--queue', action='store', metavar='', dest='queue',
        help=argparse.SUPPRESS)
g.add_argument('-d', '--domain', action='store', metavar='<domain>', dest='domain',
        help=argparse.SUPPRESS)
parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
parser.print_help()

usage: pqa [-h] [-v,--version]

Postfix Queue Administration Tool

optional arguments:
  -h, --help     show this help message and exit
  -l, --list     Shows full overview of all queues
  -v, --version  show program's version number and exit

information options:
  -q, --queue <queue>     Show information for <queue>
  -d, --domain <domain>   Show information about a specific <domain>

Or you could put that information in the regular description. You already are using a custom usage line.

pppery
  • 3,731
  • 22
  • 33
  • 46
hpaulj
  • 221,503
  • 14
  • 230
  • 353
2

Is the problem that <domain> is repeated in the help line?:

-d <domain>, --domain <domain>

The argparse HelpFormatter does not give the user much control over this part of the display. As you have shown, you can set the usage line, the help text, and the metavar.

You would have to subclass the HelpFormatter, and change one of the functions to produce something like:

-d, --domain <domain>

It doesn't look like a complicated change, probably to the HelpFormatter._format_action_invocation method. But you need to be more explicit about what you want.

hpaulj
  • 221,503
  • 14
  • 230
  • 353