Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

max_help_position is not works in python argparse library

Hi colleagues I have the code (max_help_position is 2000):

formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=2000)
parser = argparse.ArgumentParser(formatter_class=formatter_class)


subparsers = parser.add_subparsers(title="Commands", metavar="<command>")

cmd_parser = subparsers.add_parser('long_long_long_long_long_long_long',
                                   help='- jksljdalkjda',
                                   formatter_class=formatter_class)

args = parser.parse_args(['-h'])
print args

we have

optional arguments:
  -h, --help                          show this help message and exit

Commands:
  <command>
    long_long_long_long_long_long_long
                                      - jksljdalkjda
    small                             - descr

instead

optional arguments:
  -h, --help  show this help message and exit

Commands:
  <command>
    long_long_long_long_long_long_long - jksljdalkjda
    small                              - descr

Do you know simply way how to fix this?

The code:

class MyFormatter(argparse.HelpFormatter):
    def __init__(self, prog):
        super(MyFormatter, self).__init__(prog, max_help_position=2000)
        self._max_help_position = 2000
        self._action_max_length += 4

formatter_class = MyFormatter
parser = argparse.ArgumentParser(formatter_class=formatter_class)

got same result.

The code (with width=2000)

formatter_class = lambda prog: argparse.HelpFormatter(prog,
                  max_help_position=2000, width=2000)

got same result.

Thank you.

P.S. Also some additional small issue: this is odd spaces in "optional arguments". Do you know how to separate "Commands" and "optional arguments" for do not have spaces in "optional arguments" and have spaces in "Commands" since these are different essences?

like image 771
Alexander Symonenko Avatar asked Oct 01 '15 13:10

Alexander Symonenko


1 Answers

You need to increase the width as well

try:

formatter_class=lambda prog: argparse.HelpFormatter(prog,
    max_help_position=100, width=200)

As my earlier thoughts (below) show, the formatting considers overall width as well as the max_position value.


(earlier)

In my limited testing, your formatter subclass seems to work. But I haven't pushed the limits.

You many need to dig more into the Formatter code.

For example there is a format_action method that actually uses the max_width

def _format_action(self, action):
    # determine the required width and the entry label
    help_position = min(self._action_max_length + 2,
                        self._max_help_position)
    help_width = self._width - help_position
    action_width = help_position - self._current_indent - 2
    ...

Notice that it interacts with the width. Then it goes on to actually format the help lines and perform wrapping. So the actual implementation is not simple.

I'm not following your question about spaces. format_help commands the formatter to format a number of sections (including argument groups). The sections (usually) end with a couple of line feeds. Upon assembling them the formatter removes 'unnecessary' line feeds, leaving one space between groups. The subparser doesn't fit other categories, so I'd have to study the code to see exactly how it is handled.


Your lambda definition works as well. I haven't seen it before, and I don't think it's what the developers intended, but Python that doesn't matter - if it works.

Playing around with values and strings, I see that max_position up to about 56 works. Then it sort of sticks. But if I also change width (default is from CONSOLE), I can increase max_position further.


I was testing this with a long parser argument. Adding

parser.add_argument('-l','--long','--longlonglonglong', help='help after long option strings')

produces:

usage: issue25297.py [-h] [-l LONG] <command> ...

optional arguments:
  -h, --help                                     show this help message and
                                                 exit
  -l LONG, --long LONG, --longlonglonglong LONG  help after long option
                                                 strings

Commands:
  <command>
    long_long_long_long_long_long_long           - jksljdalkjda

So max_help_position does work in regular parser formatting. But for some reason, when only the subparser names are long, it does not. That section requires some special formatting. It is indented, and the subparser names are not real actions (arguments) but rather choices the subparsers argument. I'll have study it in more detail.

The subparser name string is indented 2 extra characters (compared to other arguments). The code that collects self._action_max_length does not take this into account. Hence if the subparser name is the longest string, this max_length will end up 2 spaces short. Compare actual v desired:

long_long_long_long_long_long_long
                                  - jksljdalkjda
long_long_long_long_long_long_long  - jksljdalkjda

(Formatting is done in 2 steps; once to calculate values like this _action_max_length, and a 2nd time to produce the actual output).

Subparsers are formatted with a recursive call to _format_action, so I'm not optimistic about an easy fix.


Corrected formatter

Here's a patched Formatter that correctly accounts for the indenting of subactions (sub parsers). When an argument (action) is added to the Formatter, this function figures how wide its invocation strings are, and adjusts self._max_action_length. This is used latter to indent the help strings.

class MyFormatter(argparse.HelpFormatter):
    """
    Corrected _max_action_length for the indenting of subactions
    """
    def add_argument(self, action):
        if action.help is not argparse.SUPPRESS:

            # find all invocations
            get_invocation = self._format_action_invocation
            invocations = [get_invocation(action)]
            current_indent = self._current_indent
            for subaction in self._iter_indented_subactions(action):
                # compensate for the indent that will be added
                indent_chg = self._current_indent - current_indent
                added_indent = 'x'*indent_chg
                invocations.append(added_indent+get_invocation(subaction))
            # print('inv', invocations)

            # update the maximum item length
            invocation_length = max([len(s) for s in invocations])
            action_length = invocation_length + self._current_indent
            self._action_max_length = max(self._action_max_length,
                                          action_length)

            # add the item to the list
            self._add_item(self._format_action, [action])

An example of its use (without going real wide):

# call class with alternate parameters
formatter_class=lambda prog: MyFormatter(prog, max_help_position=40,width=100)

parser = argparse.ArgumentParser(formatter_class=formatter_class)

parser.add_argument('-l','--long', help='help after long option strings')

subparsers = parser.add_subparsers(title="Commands", metavar="<command>")

cmd_parser = subparsers.add_parser('long_long_cmd',
                                   help='longish command',
                                   formatter_class=formatter_class,
                                   aliases=['long', 'long_cmd'])
                                   # newer arpgarse take aliases
sht_parser = subparsers.add_parser('short', help = 'short cmd')

args = parser.parse_args(['-h'])

which displays:

usage: issue25297.py [-h] [-l LONG] <command> ...

optional arguments:
  -h, --help                        show this help message and exit
  -l LONG, --long LONG              help after long option strings

Commands:
  <command>
    long_long_cmd (long, long_cmd)  longish command
    short                           short cmd
like image 78
hpaulj Avatar answered Nov 15 '22 22:11

hpaulj