Search code examples
pythonformattingargparseflagscolorama

argparse: Remove whitespace after flags in --help and colorizing help output with colorama


I've added some args to a script with argparse which function fine. Now I'm trying to format the --help output. I've added metavar='' to each one which has produced a much cleaner output, however there are spaces after the single flags making the text rag oddly.

Problems

Flag will display as -m , --model instead of -m, --model

Flags with type=bool with const and nargs display as -x [], --xip [], having the extra space and [] added.

Not finding much info on how to clean this up. Did find discussions on python.org that the extra space is a known problem and using metavar='' is not ideal.

example code:

import argparse
import colorama
from colorama import init, Fore, Style
init(autoreset=True)

parser = argparse.ArgumentParser(description=Fore.RED + 'Some descriptive Text and such' + Fore.WHITE, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
add_arg = parser.add_argument
add_arg('-m',   '--model',  type=str, default='model_name',                     help="some help text", metavar='')
add_arg('-d',   '--derp',   type=str, default='burp',                           help="some more help text", metavar='')
add_arg('-x',   '--xip',    type=bool, default=False, const=True, nargs='?',    help="some other help text", metavar='')
args = parser.parse_args()

running python script.py -h produces:

usage: script.py [-h] [-m] [-d] [-x ]

Some descriptive Text and such # <-- this line displays as RED

optional arguments:
  -h, --help           show this help message and exit
  -m , --model         some help text (default: model_name)
  -d , --derp          some more help text (default: burp)
  -x [], --xip []      some other help text (default: False)

changing metavar to metavar='\b' produces this:

usage: script.py [-h] [-m] [-d] [-x ]] # <- extra bracket ]

Some descriptive Text and such

optional arguments:
  -h, --help         show this help message and exit
  -m, --model    some help text (default: model_name)   # indent broken
  -d, --derp     some more help text (default: burp)    # indent broken
  -x ], --xip ]  some other help text (default: False)  # indent broken

Coloring Output

I'd also like to know how to properly color the --help output with colorama. Coloring the description is easy enough, but trying to add coloring to the flags and such produced expected errors. Adding color to help worked if added before, like help=Fore.YELLOW + 'some help text', but it colors everything after it, and can't add anymroe Fore after it without errors. Adding it anywhere else produces an error.

Is there a way to define how to color flags and help text, maybe outside of where they are being set?

Besides that, are there any packages for argparse formatting? I found some older ones that didn't work quite right but nothing new.


Solution

  • Well, I managed to figure it out after finding some old posts on SO.

    The code from This Post sets up an add_argument_group. You then add your args and hide the help text by setting help=argparse.SUPPRESS.

    The actual text that is displayed in --help is the title and description of the group. This makes it very easy because you're just concatenating strings at that point and can add in your colorama styling easily with + where needed.

    The code from the post:

    parser = argparse.ArgumentParser(description='Postfix Queue Administration Tool',
            prog='pqa',
            usage='%(prog)s [-h] [-v,--version]',
            formatter_class=argparse.RawDescriptionHelpFormatter,
            )
    parser.add_argument('-l', '--list', action='store_true',
            help='Shows full overview of all queues')
    g = parser.add_argument_group(title='information options',
            description='''-q, --queue <queue>     Show information for <queue>
    -d, --domain <domain>   Show information about a specific <domain>''')
    g.add_argument('-q', '--queue', action='store', metavar='', dest='queue',
            help=argparse.SUPPRESS)
    g.add_argument('-d', '--domain', action='store', metavar='<domain>', dest='domain',
            help=argparse.SUPPRESS)
    parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
    parser.print_help()
    

    My modified example code based on that post:

    import argparse
    from colorama import init, Fore, Style
    init(autoreset=True)
    
    parser = argparse.ArgumentParser(
        description=Fore.LIGHTBLACK_EX + 'Some descriptive Text and such' + Fore.WHITE,
        prog='script.py',
        usage=Fore.WHITE + '%(prog)s [-m] [-d] [-x]',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        )
    g = parser.add_argument_group(title=Fore.LIGHTBLACK_EX + 'args',
            description=Fore.RED + '''
    -m   --model        ''' + Fore.WHITE + Style.DIM + '''some help text       || default: model_name''' + Fore.RED + Style.NORMAL + '''
    -d   --derp         ''' + Fore.WHITE + Style.DIM + '''some more help text  || default: burp''' + Fore.RED + Style.NORMAL + '''
    -x   --xip          ''' + Fore.WHITE + Style.DIM + '''some other help text || default: False''' + Fore.RED + Style.NORMAL + '''\n\n''')
    g.add_argument('-m',   '--model',   type=str, default='model_name',                      help=argparse.SUPPRESS, metavar='')
    g.add_argument('-d',   '--derp',    type=str, default='burp',                            help=argparse.SUPPRESS, metavar='')
    g.add_argument('-x',   '--xip',     type=str2bool, default=False, const=True, nargs='?', help=argparse.SUPPRESS, metavar='')
    
    args = parser.parse_args()
    
    

    The output:

    usage: script.py [-m] [-d] [-x]
    
    Some descriptive Text and such
    
    optional arguments:
      -h, --help  show this help message and exit
    
    args:
      
      -m   --model        some help text       || default: model_name
      -d   --derp         some more help text  || default: burp
      -x   --xip          some other help text || default: False
    

    Attached image of what the output looks like in Terminal: enter image description here

    Lastly, as per @hpaulj 's advice, I found this function so I can change the type from bool and make sure it always resolves correctly.

    def str2bool(v):
        if isinstance(v, bool):
            return v
        if v.lower() in ('yes', 'true', 't', 'y', '1'):
            return True
        elif v.lower() in ('no', 'false', 'f', 'n', '0'):
            return False
        else:
            raise argparse.ArgumentTypeError('Boolean value expected.')