Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How should argument parser help and usage message be displayed? #18687

Open
arezaii opened this issue Nov 4, 2021 · 2 comments
Open

How should argument parser help and usage message be displayed? #18687

arezaii opened this issue Nov 4, 2021 · 2 comments
Assignees

Comments

@arezaii
Copy link
Contributor

arezaii commented Nov 4, 2021

When the argument parser generates and displays help, how should it be formatted?

Depending on feedback here and on #18648, I was thinking of some format similar to this:

<pre-help>

<program name>
<about>
<author>
<version>

<usage>
<arguments>
<options>
<subcommands>

<post help>

Other parsers give you some ability to modify the message format, with Rust having probably the most flexible/generous by way of custom templates. I'm not proposing anything that elegant, and initially the format would be fixed.

For a simple program like the quickstart example, the help output might look like:

USAGE: ArgumentParserQuickstart <POSITIONAL> [-h, --help] [--debug] [--optional <OPTIONAL>]
ARGUMENTS:
        POSITIONAL
OPTIONS:
        -h, --help      Display this message and exit
        --optional <OPTIONAL>
        --debug

There is still work to be done with pretty printing the help output with perfect column alignment and awareness of the screen size, this example is just using dumb tabs.

Usage Messages

For usage messages, I would propose ordering the parts something like this:

USAGE: <binary name> <positional arguments> <flags/options> <subcommand>

Where optional arguments or values are wrapped in [ ] and required ones are wrapped in < >

Subcommands are a little tricky because no individual subcommand can be required, but a program may require some selected subcommand to do anything useful. Ideally the main program should always support a call to help so technically the subcommand isn't required . Therefore, I propose that they are identified in the usage message as [SUBCOMMAND] and have the help message list the available commands under the SUBCOMMANDS: heading.

There is a question of how multiple values should be indicated, and for simplicity I propose using ... to indicate 'or more' values. Some output examples to demonstrate:

  • optional flag with 1 value expected [--flag <VAL>]
  • optional flag with 0 values expected [--flag]
  • optional flag with 1 or more values '' [--flag <VAL ...>]
  • optional flag with 0 or more values '' [--flag [VAL ...]]
  • required versions the same except outer [ ] replaced with < >

It is less clear how to represent arguments which expect a specific range, say 4..10 values, or exactly 8 values.

Other questions:

  1. Should default values for arguments and options be displayed in the help output? In the usage message? If so, how/where? This could be at the end of the help message for an argument/option, but not displayed at all in the case of flags and subcommands.

  2. For flags/options with multiple possible identifiers, should they all be represented in the usage message? e.g. the usage message from the quickstart includes [-h, --help], should it just display [-h] there and the longer version in the help output under OPTIONS:?

  3. For parsers that operate on subcommands, how to give them the whole path from the parent? That is, for a command like mason add -h, how to tell the add subcommand parser that the usage message should append mason? There could be a "parent" property on the argument parser, or that might be a reason to have separate binaryName and program arguments, where binaryName represents the path to the subcommand parser, whose name is represented in program.

@arezaii arezaii self-assigned this Nov 4, 2021
@bradcray
Copy link
Member

bradcray commented Nov 5, 2021

@arezaii

Something not obvious to me (quite possibly due to lack of experience with argparse systems) is what the benefit of a number of predefined fields in the help/usage message is, as follows:

<pre-help>

<program name>
<about>
<author>
<version>

<usage>

rather than having a single multi-line <prefix> string which could include any or all of these that the user want to print? Are there other contexts in which the distinct fields would be used individually, distinctly from this help message prefix?

Also, if one of these is left blank / undefined, do we get a blank line, or is the entire line suppressed?

Other comments:

In:

USAGE: ArgumentParserQuickstart <POSITIONAL> [-h, --help] [--debug] [--optional <OPTIONAL>]
ARGUMENTS:
        POSITIONAL
OPTIONS:

to my eye, the all-caps headers look very old-school / man-page-y. The first thing I thought to look at to see what modern programs do was git, whose default message when I type git reads much more cleanly to me. The second was llvm which is more all-caps-y like what you show here (though interestingly, gcc, despite being older, is more lowercase-based). The third was brew which is more in the git camp. The fourth was spack which is also more lower-case-y.

This makes me wonder whether we could identify programs that we'd consider to be good examples of modern usage/help messages that we'd want to model our choices after and use to guide answers to questions. That said, I'll take a stab at them all the same:

It is less clear how to represent arguments which expect a specific range, say 4..10 values, or exactly 8 values.

I think the user doesn't need to know the range indices, right, just the number of values? If that's correct, I'd probably come up with some way of indicating the number of arguments expected and keeping the indices to the implementation.

Should default values for arguments and options be displayed in the help output? In the usage message? If so, how/where?

I think they should definitely be available somewhere/somehow, but don't have an intuition as to where/how. What do other programs do? I think for chpl, we had trouble fitting them into the --help message in a way that didn't seem clunky, so we added a separate --help-settings option to get the values (not saying that's a great solution, simply that it could be "via some other mechanism" if they didn't fit in the other outputs.

For flags/options with multiple possible identifiers, should they all be represented in the usage message? e.g. the usage message from the quickstart includes [-h, --help], should it just display [-h] there and the longer version in the help output under OPTIONS:?

It feels to me like printing both options in usage is overkill, but I'm also not sure whether I'd print just the shorter or longer versions. The longer versions obviously take more space, but are also more self-descriptive. I think mostly it seems to me that a program with a lot of options is probably going to need a manually curated usage line rather than an automatically generated one (which is not to say that the default one shouldn't do something reasonable, but I'm mentally comparing what an auto-generated usage would look like for chpl in contrast to chpl --help's usage line or man chpl's 'synopsis'.

For parsers that operate on subcommands, how to give them the whole path from the parent?

No strong insight here.

@arezaii
Copy link
Contributor Author

arezaii commented Nov 8, 2021

...what the benefit of a number of predefined fields in the help/usage message is?

The goal here is to provide flexibility to the user to customize the message in part, without having to replace the entire message or losing out on the auto-generated parts.

For example, say I have a requirement to print out a short disclaimer before my help message, I can add that using pre-help, but I will still get the benefit of having the argument parser generate the usage string (and potentially the program name, author, and version if we implement planned future work to read them from a .toml file).

I can also take advantage of generated field headings, alignment and spacing without having to fiddle with the format of my input.

Also, if one of these is left blank / undefined, do we get a blank line, or is the entire line suppressed?

The entire line would be suppressed, so the help message takes up only the vertical space it needs based on the defined fields.

...to my eye, the all-caps headers look very old-school / man-page-y ... what modern programs do ...?

I didn't look at individual apps so much as other argument parsing libraries. Python has adopted the lowercase headings, but both Rust and Swift have maintained uppercase headings.

Here are some sample outputs from the other libraries I looked at:

Python:

$ PROG.py -h

usage: PROG [-h] [--foo [FOO]] bar [bar ...]

positional arguments:
 bar          bar help

options:
 -h, --help   show this help message and exit
 --foo [FOO]  foo help

Rust:

$ helptest -h

helptest

USAGE:
   helptest [FLAGS]

FLAGS:
    --config     Some help text describing the --config arg
-h, --help       Prints help information
-V, --version    Prints version information
$ helptest --help

helptest

USAGE:
   helptest [FLAGS]

FLAGS:
   --config
        The config file used by the myprog must be in JSON format
        with only valid keys and may not contain other nonsense
        that cannot be read by this program. Obviously I'm going on
        and on, so I'll stop now.

-h, --help
        Prints help information

-V, --version
        Prints version information

Swift:

$ repeat --help
USAGE: repeat [--count <count>] [--include-counter] <phrase>

ARGUMENTS:
  <phrase>                The phrase to repeat.

OPTIONS:
  --include-counter       Include a counter with each repetition.
  -c, --count <count>     The number of times to repeat 'phrase'.
  -h, --help              Show help for this command.

The commonality is that it looks like we could improve readability by adding an extra line between each heading.

Apart from that, I prefer the look of the Swift output, but we can adapt/adopt any style we find reasonable.

It is less clear how to represent arguments which expect a specific range, say 4..10 values, or exactly 8 values.

I think the user doesn't need to know the range indices, right, just the number of values? If that's correct, I'd probably come up with some way of indicating the number of arguments expected and keeping the indices to the implementation.

Not sure if I got the right meaning. In the example, I was trying to express that the number of values is constrained by the range, so 4..10 expects at least 4 and at most 10 values and I am not sure how/if we should indicate that in the usage string. It may make more sense for the developer to choose to include that information in the help text as opposed to us generating something like:

USAGE: myProg [--foo <foo> <foo> <foo> <foo> [foo ..10]]

I didn't find much in the way of precedent to follow for these types of indicators.

Should default values for arguments and options be displayed in the help output? In the usage message? If so,
how/where?

I think they should definitely be available somewhere/somehow, but don't have an intuition as to where/how. What do other programs do?

There's mixed support for this. Python has an option to add them programmatically, but it appears to be all or nothing across every option/argument. Rust has the ability to specify the display property of a default value for every argument and sets them to display by default. Swift displays the defaults and it's not clear to me if this can be disabled.

A compromise might be leaving it up to the developer to add them to the help message as they see fit. Perhaps a %default% placeholder in the help string could indicate that we should print the default there, if at all. Initially, we may decide to leave it up to the developer to add the default values they want displayed into the help message, with possible support for generated values in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants