Sorting case-sensitively and alphabetically argparse's help output by POSIX style options

import argparse

common_opts = argparse.ArgumentParser(add_help = False)
common_opts.add_argument("-F", "--foo")
common_opts.add_argument("-t", "--test")


specific_opts = argparse.ArgumentParser(
        parents = [common_opts],
        )
specific_opts.add_argument("-c", "--rfc")
specific_opts.add_argument("-f", "--foobar")
specific_opts.add_argument("-T", "--other-test")
specific_args = specific_opts.parse_args()

If I execute the above script (argparse_parents.py -h) I get:

usage: argparse_parents.py [-h] [-F FOO] [-t TEST] [-c RFC] [-f FOOBAR]
                           [-T OTHER_TEST]

options:
  -h, --help            show this help message and exit
  -F, --foo FOO
  -t, --test TEST
  -c, --rfc RFC
  -f, --foobar FOOBAR
  -T, --other-test OTHER_TEST

Where I would like it to be:

usage: argparse-parents.py [-h] [-c RFC] [-f FOOBAR] [-F FOO] [-t TEST]
                           [-T OTHER_TEST]

options:
  -h, --help            show this help message and exit
  -c, --rfc RFC
  -f, --foobar FOOBAR
  -F, --foo FOO
  -t, --test TEST
  -T, --other-test OTHER_TEST

I’m at a lost on how to get the desired output and I would appreciate a friendly hand.

The args are displayed in the order they were added to the parser in…

Moved to Ideas per @picnixz suggestion on GitHub.

1 Like

I see you want -h/--help to be kept in the front, and lowercase options to be sorted before uppercase options, which is against the typical lexicographical order, so it’s best that you write a custom argparse.HelpFormatter class with a custom sorting key function:

def _option_sort_key(action):
    if not action.option_strings:
        return 0,
    if action.option_strings == ['-h', '--help']:
        return 1,
    option = min(action.option_strings, key=len).lstrip('-')
    return 2, option.lower(), option[:1].isupper()


class SortedHelpFormatter(argparse.HelpFormatter):
    def add_arguments(self, actions):
        super().add_arguments(sorted(actions, key=_option_sort_key))

    def add_usage(self, usage, actions, groups, prefix=None):
        if actions:
            actions = sorted(actions, key=_option_sort_key)
        super().add_usage(usage, actions, groups, prefix)

so that you can construct an ArgumentParser with formatter_class specified to the custom formatter:

common_opts = argparse.ArgumentParser(
    add_help=False,
    formatter_class=SortedHelpFormatter
)
common_opts.add_argument("-F", "--foo")
common_opts.add_argument("-t", "--test")

specific_opts = argparse.ArgumentParser(
    parents=[common_opts],
    formatter_class=SortedHelpFormatter
)
specific_opts.add_argument("-c", "--rfc")
specific_opts.add_argument("-f", "--foobar")
specific_opts.add_argument("-T", "--other-test")

specific_args = specific_opts.parse_args(['-h'])

This outputs:

usage: test.py [-h] [-c RFC] [-f FOOBAR] [-F FOO] [-t TEST] [-T OTHER_TEST]

options:
  -h, --help            show this help message and exit
  -c, --rfc RFC
  -f, --foobar FOOBAR
  -F, --foo FOO
  -t, --test TEST
  -T, --other-test OTHER_TEST

As for turning this into a plausible proposal, maybe we can add a sort_options_by option to ArgumentParser’s constructor so users can avoid having to write a custom HelpFormatter subclass (which is frankly rather user-unfriendly) when all they want to do is to sort the options in help/usage in certain ways.

2 Likes