import re from argparse import _HelpAction, _SubParsersAction class NavigationError(Exception): pass def parser_navigate(parser_result, path, current_path=None): if isinstance(path, str): if path == "": return parser_result path = re.split(r"\s+", path) current_path = current_path or [] if len(path) == 0: return parser_result if "children" not in parser_result: raise NavigationError( "Current parser have no children elements. (path: {})".format( " ".join(current_path), ), ) next_hop = path.pop(0) for child in parser_result["children"]: # identifer is only used for aliased subcommands identifier = child["identifier"] if "identifier" in child else child["name"] if identifier == next_hop: current_path.append(next_hop) return parser_navigate(child, path, current_path) raise NavigationError( f"Current parser have no children element with name: {next_hop} (path: %s)" % " ".join(current_path), ) def _try_add_parser_attribute(data, parser, attribname): attribval = getattr(parser, attribname, None) if attribval is None: return if not isinstance(attribval, str): return if len(attribval) > 0: data[attribname] = attribval def _format_usage_without_prefix(parser): """ Use private argparse APIs to get the usage string without the 'usage: ' prefix. """ fmt = parser._get_formatter() fmt.add_usage( parser.usage, parser._actions, parser._mutually_exclusive_groups, prefix="", ) return fmt.format_help().strip() def parse_parser(parser, data=None, **kwargs): if data is None: data = { "name": "", "usage": parser.format_usage().strip(), "bare_usage": _format_usage_without_prefix(parser), "prog": parser.prog, } _try_add_parser_attribute(data, parser, "description") _try_add_parser_attribute(data, parser, "epilog") for action in parser._get_positional_actions(): if isinstance(action, _HelpAction): continue if isinstance(action, _SubParsersAction): helps = {} for item in action._choices_actions: helps[item.dest] = item.help # commands which share an existing parser are an alias, # don't duplicate docs subsection_alias = {} subsection_alias_names = set() for name, subaction in action._name_parser_map.items(): if subaction not in subsection_alias: subsection_alias[subaction] = [] else: subsection_alias[subaction].append(name) subsection_alias_names.add(name) for name, subaction in action._name_parser_map.items(): if name in subsection_alias_names: continue subalias = subsection_alias[subaction] subaction.prog = f"{parser.prog} {name}" subdata = { "name": name if not subalias else "{} ({})".format(name, ", ".join(subalias)), "help": helps.get(name, ""), "usage": subaction.format_usage().strip(), "bare_usage": _format_usage_without_prefix(subaction), } if subalias: subdata["identifier"] = name parse_parser(subaction, subdata, **kwargs) data.setdefault("children", []).append(subdata) continue if "args" not in data: data["args"] = [] arg = { "name": action.dest, "help": action.help or "", "metavar": action.metavar, } if action.choices: arg["choices"] = action.choices data["args"].append(arg) show_defaults = ("skip_default_values" not in kwargs) or ( kwargs["skip_default_values"] is False ) for action in parser._get_optional_actions(): if isinstance(action, _HelpAction): continue if "options" not in data: data["options"] = [] option = { "name": action.option_strings, "default": action.default if show_defaults else "==SUPPRESS==", "help": action.help or "", } if action.choices: option["choices"] = action.choices if "==SUPPRESS==" not in option["help"]: data["options"].append(option) return data