Search code examples
pythoncommand-line-interfaceargparse

Argparse, displaying custom help text without any of the boilerplate argparse text


After looking at about a dozen questions, I can't seem to find an answer.

I have a python CLI i've written using argparse. I have a main command that does nothing but regurgitate help text and then 4 subcommands. My boss wants a very specific output for the help text. He has me write it out as a text file and then we use that text file to display the help text.

However, in some circumstances, it STILL outputs parts of the argparse help text.

For example, if I run my program with no subcommands, it just outputs our help text from the file. But if I use "-h" or "--help" it will output our help text, followed by the list of positional and optional arguments and other argparse stuff. We don't want that.

I could use "add_help=False" but we want the user to be able to type -h and still get our help text. If we set add help to false, it will display our help text followed by the error "-h not recognized".

Also doing this does nothing for when the user uses -h after a subcommand. I set help=None and usage is set to my custom help text for each subcommand, but it still shows the boilerplate argparse info at the end.

This is what I want to happen: user types in the main command with no subcommands prints my custom help text and nothing else. The user types the main command, no subcommand, followed by -h/--help and it prints my custom help text and nothing else. User types in the main command, one of the subcommands, followed by -h/--help and it outputs my help text and nothing else. User types the main command, a subcommand, and then wrong arguments or too many/ too few arguments displays my help text and nothing else. Basically I only ever want it to print nothing, or print just my help text.

how do I do that? here is my main function where the parsers and subparsers are all configured:

def main():
    # Import help text from file
    p = Path(__file__).with_name("help.txt")
    with p.open() as file:
        help_text = file.read()

    # Configure the top level Parser
    parser = argparse.ArgumentParser(prog='myprog', description='My program', usage=help_text)
    subparsers = parser.add_subparsers()

    # Create Subparsers to give subcommands
    parser_one = subparsers.add_parser('subcommandone', prog='subcommandone', usage=help_text, help=None)
    parser_one.add_argument('arg1', type=str)
    parser_one.add_argument('-o', '--option1', default='mydefault', type=str)
    parser_two= subparsers.add_parser('subcommandtwo', usage=help_text, help=None, prog='subcommandtwo')
    parser_three= subparsers.add_parser('subcommandthree', usage=help_text, help=None, prog='subcommandthree')
    parser_four= subparsers.add_parser('subcommandfour', usage=help_text, help=None, prog='subcommandfour')

    # Assign subparsers to their respective functions
    parser_one.set_defaults(func=functionone)
    parser_two.set_defaults(func=functiontwo)
    parser_three.set_defaults(func=functionthree)
    parser_four.set_defaults(func=functionfour)
    parser.set_defaults(func=base_case)

    # Parse the arguments and call appropriate functions
    args = parser.parse_args()
    if len(sys.argv) == 1:
        args.func(args, parser)
    else:
        args.func(args)

Any thoughts?


Solution

  • You can use sys.exit() after the help text has been displayed, and before the parsing has begun, to avoid problems with "-h not recognized".

    So anywhere before the line

    # Parse the arguments and call appropriate functions
    

    add

    if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv:
        print(help_text)
        sys.exit(1)
    

    In situations where that is not good enough you can subclass argparse.HelpFormatter like so

    usage_help_str = 'myscript command [options]'
    epilog_str = "More info can be found at https://..."
    
    class Formatter(argparse.HelpFormatter):
        # override methods and stuff
    
    def formatter(prog):
        return Formatter(prog)
    
    parser = argparse.ArgumentParser(formatter_class=formatter, epilog=epilog_str, usage=usage_help_str, add_help=False)
    

    I tried looking around for documentation on subclassing the helpFormatter, but I couldn't find anything. It looks like people are just looking at the source code to figure out how to subclass it.