Title: argparse: GNU-style help formatter
Type: enhancement Stage: patch review
Components: Library (Lib) Versions: Python 3.10
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: paul.j3, rhettinger, will
Priority: normal Keywords: patch

Created on 2021-01-20 18:44 by will, last changed 2021-01-23 02:57 by will.

Pull Requests
URL Status Linked Edit
PR 24275 open will, 2021-01-20 20:08
Messages (6)
msg385359 - (view) Author: Will Noble (will) * Date: 2021-01-20 18:44
argparse allows GNU-style long options (with '=' in between the option and the value as opposed to a space):

Call it pickiness, but I'd like to be able to print the help message in that format. Looking at the code, a simple refactor would be much cleaner than implementing my own custom help formatter and having to copy-paste a bunch of implementation details that are potentially subject to change in future releases. I'll submit a patch shortly.
msg385391 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-01-21 05:04
This sounds reasonable to me and the patch is non-invasive.  Paul, what do you think?
msg385447 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2021-01-21 21:12
The refactoring looks reasonable.

But while we are tweaking:

    def _format_action_invocation(self, action):

I wonder if we also give users more control over how multiple option strings are formatted.  Currently if 

     parser.add_argument('-f', '--foo', help='something')

the help line will be

     -f FOO, --foo FOO     something

with this alternate formatter it would be (I think)

     -f FOO, --foo=FOO     something

But with longer option strings users often want

     -f, --foo FOO         something

or even just

     -f, --foo             something

we can almost get the last with `metavar=''`

     -f , --foo            something

which has a superfluous space before the comma.

I don't recall if there's already a bug/issue for this or not.  Maybe the fix is a subclass with a complete replacement of this _format_action_invocation method.  I'll have do more research (here and on Stackoverflow).
msg385453 - (view) Author: Will Noble (will) * Date: 2021-01-21 22:15
The main contribution of my PR is simply factoring out _format_option_with_args as an overridable method. Note that this actually enables subclassing HelpFormatter to produce all the examples you presented above with 1-3 trivial lines of code, as opposed to overriding the entire _format_action_invocation which is 20 lines in the base class.

If HelpFormatter had a stable internal structure (i.e. some sort of assurance that it's overridable methods were not subject to change), I wouldn't have bothered including an implementation of GnuStyleLongOptionsHelpFormatter in this PR, since I could just implement it downstream as-needed. However, I chose to push it upstream because a) it's trivial and non-invasive and b) it establishes a use-case of _format_option_with_args for posterity, just in case anybody came in later and saw such a trivial, "private" (underscore-prefixed) method and decided to inline it.

This lack of perceived stability is alluded to in My preferred solution would be to refactor HelpFormatter to have more overridable methods like this, make them non-"private", document them properly, and keep them relatively stable. In order to do that, we'd want to establish all the desired use-cases and be sure to craft the method structure in such a way that they're all supported. Since that would be a somewhat daunting design task and probably not high on the priority list atm, I think this small incremental improvement pushes things in the right direction; establishing a desirable use-case without committing to too many implementation details yet.
msg385507 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2021-01-22 19:08
I was thinking of a refactoring that included the ', '.join(...) loop, but on further thought your refactoring might be enough for the case I raised.

For example:

def _format_option_with_args(self, option_string, args_string):
        if option_string.startswith('--'):
            return '%s %s' % (option_string, args_string)
        return '%s' % option_string

would produce a

     -f, --foo FOO    something


def _format_option_with_args(self, option_string, args_string):
        return option_string
would produce

     -f, --foo       something

I wonder if this refactoring merits some mention in the documentation.  Or maybe the GnuStyleLongOptionsHelpFormatter subclass is enough of an example.  The details of how other formatter subclasses work aren't documented, but they are available to developers who can read the code.
msg385520 - (view) Author: Will Noble (will) * Date: 2021-01-23 02:57
Ya I was following the precedent of non-documentation figuring that formal documentation should be done for the entire class and that would be a much larger undertaking and maybe involve further refactoring. I could potentially undertake that task in the future but I think it's out-of-scope for this change.
Date User Action Args
2021-01-23 02:57:48willsetmessages: + msg385520
2021-01-22 19:08:35paul.j3setmessages: + msg385507
2021-01-21 22:15:58willsetmessages: + msg385453
2021-01-21 21:12:38paul.j3setmessages: + msg385447
2021-01-21 05:04:47rhettingersetmessages: + msg385391
2021-01-21 04:35:19shihai1991setnosy: + rhettinger, paul.j3
2021-01-20 20:08:05willsetkeywords: + patch
stage: patch review
pull_requests: + pull_request23098
2021-01-20 18:44:19willcreate