Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

136 lines
4.6 KiB
Python

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