I'm working on a Python project that uses the argparse
module to handle command-line arguments. I would like to print the tree structure of the subparsers
associated with my main parser, so I can easily visualize the hierarchy of available commands.
Some requirements I have in mind for this functionality are:
It should be able to handle subparsers defined with add_subparsers
.
Each subparser should be identified by its name and should include any associated options.
The tree structure should be printed in a readable and well-formatted manner.
I've tried implementing this behavior using argparse, but I'm stuck at the stage of printing the tree structure.
Thank you in advance for any help or suggestions you can provide!
After some effort, I managed to develop a solution to print the tree structure of subparsers
of an argparse
parser in Python. This solution is implemented using a recursive function that traverses the subparsers
and prints the tree structure.
def __option_strings_formatter(parser) -> str:
retStr = ""
if parser._optionals: # Add options if present
options_str = []
positional_str = []
for action in parser._optionals._actions:
if isinstance(action, argparse._SubParsersAction):
continue # Skip argparse._SubParsersAction because tree vision use it to move
pars = [] # Init Str
if action.option_strings: # if optional argument, add option selector, short priority
pars.append(f"{action.option_strings[0]}")
# MetaVar of option, custom or default
if action.metavar:
pars.append(f"{"...".join(action.metavar)}" if not isinstance(action.metavar, str) else f"{action.metavar}")
elif (action.nargs or action.required) and action.dest:
pars.append(f"{action.dest.upper()}")
# Add to the right list only if something are present
if pars:
parStr = " ".join(pars)
parStr = f"{parStr}" if action.required else f"[{parStr}]"
options_str.append(parStr) if action.option_strings else positional_str.append(parStr)
# Order the list and add to line
if positional_str:
positional_str.sort() # Order to keep before required parameters and positional arguments
retStr += " " + " ".join(positional_str)
if options_str:
options_str.sort() # Order to keep before required parameters and positional arguments
retStr += " " + " ".join(options_str)
return retStr
def subparserTree(parser: argparse.ArgumentParser, start="", down=("│", " "), leaf=("├", "╰"), item=("┬", "╴"), indInc=1) -> str:
subparsers_actions = [action for action in parser._actions if isinstance(action, argparse._SubParsersAction)]
strOut = f"{parser.prog} {__option_strings_formatter(parser)}\n" if not start else ""
for subparsers_action in subparsers_actions:
for choice, subparser in subparsers_action.choices.items():
endSublist = choice == list(subparsers_action.choices.keys())[-1]
strOut += start + (leaf[endSublist] + "─" * indInc)
retString = subparserTree(subparser, start=start + (down[endSublist] + " " * indInc), down=down, leaf=leaf, indInc=indInc)
strOut += item[0] if retString else ""
strOut += item[1] + choice + __option_strings_formatter(subparser) + "\n" + retString
return strOut
To use this function, simply call it with the main parser and print the result:
print(subparserTree(parser))
If you have any improvements to propose, I would be very happy to accept them!