Search code examples
python-3.xtreecommand-line-interface

How can I print the tree structure of subparsers of an argparse parser in Python?


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:

  1. 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.

  2. The tree structure should be printed in a readable and well-formatted manner.

  3. 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!


Solution

  • 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))
    

    The output, in my case, is: Format tree output

    If you have any improvements to propose, I would be very happy to accept them!