拆分代码;约束cli命令
This commit is contained in:
+78
-64
@@ -37,7 +37,7 @@ def context_command_path(click_ctx: click.Context | None) -> tuple[str, ...]:
|
||||
|
||||
def _build_click_context(path: tuple[str, ...]) -> click.Context | None:
|
||||
root = _click_root_command()
|
||||
ctx: click.Context = click.Context(root, info_name="tjwater")
|
||||
ctx: click.Context = click.Context(root, info_name="tjwater-cli")
|
||||
command: click.Command = root
|
||||
for token in path:
|
||||
if not isinstance(command, click.Group):
|
||||
@@ -54,7 +54,7 @@ def build_usage(path: tuple[str, ...]) -> str | None:
|
||||
ctx = _build_click_context(path)
|
||||
if ctx is None:
|
||||
return None
|
||||
parts = ["tjwater", *path]
|
||||
parts = ["tjwater-cli", *path]
|
||||
for parameter in ctx.command.params:
|
||||
if not isinstance(parameter, click.Option):
|
||||
continue
|
||||
@@ -165,7 +165,7 @@ def _build_example(path: tuple[str, ...], *, existing_examples: list[str] | None
|
||||
has_required_options = all(f"--{option_name}" in example for option_name in required_option_names)
|
||||
if has_auth and has_required_options:
|
||||
return example
|
||||
parts = ["tjwater", "--auth-context", "auth.json", *path]
|
||||
parts = ["tjwater-cli", "--auth-context", "auth.json", *path]
|
||||
if ctx is None:
|
||||
return " ".join(parts)
|
||||
for parameter in ctx.command.params:
|
||||
@@ -198,8 +198,8 @@ def _enrich_index_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
path = tuple(command_item["command"].split())
|
||||
doc = get_command_doc(path)
|
||||
if doc is None and has_subcommands(path):
|
||||
command_item["usage"] = f"tjwater {' '.join(path)} help"
|
||||
command_item["example"] = f"tjwater {' '.join(path)} help"
|
||||
command_item["usage"] = f"tjwater-cli {' '.join(path)} help"
|
||||
command_item["example"] = f"tjwater-cli {' '.join(path)} help"
|
||||
else:
|
||||
existing_examples = [] if doc is None else list(doc.get("examples", []))
|
||||
command_item["usage"] = build_usage(path) or command_item.get("usage")
|
||||
@@ -220,11 +220,8 @@ def resolve_help_payload(path: tuple[str, ...]) -> tuple[dict[str, Any] | None,
|
||||
return None, False
|
||||
|
||||
|
||||
def emit_help_payload(payload: dict[str, Any], *, json_output: bool, is_index: bool) -> None:
|
||||
if json_output:
|
||||
typer.echo(json.dumps(payload, ensure_ascii=False))
|
||||
else:
|
||||
typer.echo(render_help_text(payload, is_index=is_index))
|
||||
def emit_help_payload(payload: dict[str, Any]) -> None:
|
||||
typer.echo(json.dumps(payload, ensure_ascii=False))
|
||||
|
||||
|
||||
def merge_next_commands(*groups: list[str] | None) -> list[str]:
|
||||
@@ -259,12 +256,12 @@ def build_error_guidance(click_ctx: click.Context | None) -> tuple[Any, list[str
|
||||
return (
|
||||
{
|
||||
"command_group": " ".join(group_path),
|
||||
"usage": f"tjwater {' '.join(group_path)} help",
|
||||
"examples": [f"tjwater {' '.join(group_path)} help", f"tjwater help {' '.join(group_path)}"],
|
||||
"usage": f"tjwater-cli {' '.join(group_path)} help",
|
||||
"examples": [f"tjwater-cli {' '.join(group_path)} help", f"tjwater-cli help {' '.join(group_path)}"],
|
||||
},
|
||||
merge_next_commands(
|
||||
[f"tjwater {' '.join(group_path)} help", f"tjwater help {' '.join(group_path)}"],
|
||||
["tjwater help"],
|
||||
[f"tjwater-cli {' '.join(group_path)} help", f"tjwater-cli help {' '.join(group_path)}"],
|
||||
["tjwater-cli help"],
|
||||
),
|
||||
)
|
||||
payload, is_index = resolve_help_payload(command_path)
|
||||
@@ -275,21 +272,21 @@ def build_error_guidance(click_ctx: click.Context | None) -> tuple[Any, list[str
|
||||
"usage": payload.get("usage") or usage,
|
||||
"examples": payload.get("examples", []),
|
||||
},
|
||||
merge_next_commands([f"tjwater help {' '.join(command_path)}"], ["tjwater help"]),
|
||||
merge_next_commands([f"tjwater-cli help {' '.join(command_path)}"], ["tjwater-cli help"]),
|
||||
)
|
||||
if payload is not None and is_index:
|
||||
return (
|
||||
{
|
||||
"command_group": " ".join(command_path),
|
||||
"usage": f"tjwater {' '.join(command_path)} help",
|
||||
"examples": [f"tjwater {' '.join(command_path)} help", f"tjwater help {' '.join(command_path)}"],
|
||||
"usage": f"tjwater-cli {' '.join(command_path)} help",
|
||||
"examples": [f"tjwater-cli {' '.join(command_path)} help", f"tjwater-cli help {' '.join(command_path)}"],
|
||||
},
|
||||
merge_next_commands(
|
||||
[f"tjwater {' '.join(command_path)} help", f"tjwater help {' '.join(command_path)}"],
|
||||
["tjwater help"],
|
||||
[f"tjwater-cli {' '.join(command_path)} help", f"tjwater-cli help {' '.join(command_path)}"],
|
||||
["tjwater-cli help"],
|
||||
),
|
||||
)
|
||||
return ({"usage": usage} if usage else None, ["tjwater help"])
|
||||
return ({"usage": usage} if usage else None, ["tjwater-cli help"])
|
||||
|
||||
|
||||
def classify_click_error(exc: click.ClickException) -> tuple[str, str]:
|
||||
@@ -305,55 +302,61 @@ def classify_click_error(exc: click.ClickException) -> tuple[str, str]:
|
||||
return "CLI 参数错误", "USAGE_ERROR"
|
||||
|
||||
|
||||
def render_help_text(payload: dict[str, Any], *, is_index: bool) -> str:
|
||||
lines: list[str] = [str(payload.get("summary", ""))]
|
||||
if is_index:
|
||||
is_top_level = payload.get("menu_level") == 1
|
||||
lines.append("")
|
||||
lines.append("Commands:")
|
||||
for command in payload.get("commands", []):
|
||||
lines.append(f" {command['command']}: {command['summary']}")
|
||||
if not is_top_level and command.get("usage"):
|
||||
lines.append(f" usage: {command['usage']}")
|
||||
if not is_top_level and command.get("example"):
|
||||
lines.append(f" example: {command['example']}")
|
||||
lines.append("")
|
||||
if is_top_level:
|
||||
lines.append("Use `tjwater <menu> help` to see subcommands.")
|
||||
else:
|
||||
lines.append("Use `tjwater help --json` for structured output.")
|
||||
return "\n".join(lines)
|
||||
def _build_root_help_epilog() -> str:
|
||||
return "\n".join(
|
||||
[
|
||||
"\b",
|
||||
"Examples:",
|
||||
" tjwater-cli help",
|
||||
" tjwater-cli help simulation run",
|
||||
" tjwater-cli simulation run --help",
|
||||
]
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(f"Command: {payload['command']}")
|
||||
lines.append(f"Description: {payload['description']}")
|
||||
if payload.get("usage"):
|
||||
lines.append(f"Usage: {payload['usage']}")
|
||||
|
||||
options = payload.get("options", [])
|
||||
if options:
|
||||
lines.append("")
|
||||
lines.append("Options:")
|
||||
for option in options:
|
||||
suffix = " (required)" if option.get("required") else ""
|
||||
lines.append(f" --{option['name']}{suffix}: {option['description']}")
|
||||
|
||||
def _build_leaf_help_epilog(path: tuple[str, ...], payload: dict[str, Any]) -> str:
|
||||
lines = ["\b"]
|
||||
description = payload.get("description")
|
||||
usage = payload.get("usage")
|
||||
examples = payload.get("examples", [])
|
||||
next_commands = payload.get("next_commands", [])
|
||||
if description:
|
||||
lines.extend([f"Description: {description}", ""])
|
||||
if usage:
|
||||
lines.extend([f"Usage example: {usage}", ""])
|
||||
if examples:
|
||||
lines.append("")
|
||||
lines.append("Examples:")
|
||||
for example in examples:
|
||||
lines.append(f" {example}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("Use `tjwater help --json` for structured output.")
|
||||
lines.extend(f" {example}" for example in examples)
|
||||
lines.append("")
|
||||
if next_commands:
|
||||
lines.append("Next steps:")
|
||||
lines.extend(f" {command}" for command in next_commands)
|
||||
lines.append("")
|
||||
lines.extend(["Structured JSON:", f" tjwater-cli help {' '.join(path)}"])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _build_group_help_epilog(path: tuple[str, ...], payload: dict[str, Any]) -> str:
|
||||
lines = ["\b", "Examples:", f" tjwater-cli help {' '.join(path)}"]
|
||||
for command in payload.get("commands", [])[:2]:
|
||||
example = command.get("example")
|
||||
if example:
|
||||
lines.append(f" {example}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_group_help_appendix(click_ctx: click.Context | None) -> str | None:
|
||||
path = context_command_path(click_ctx)
|
||||
if not path:
|
||||
return _build_root_help_epilog()
|
||||
payload, is_index = resolve_help_payload(path)
|
||||
if payload is None or not is_index:
|
||||
return None
|
||||
return _build_group_help_epilog(path, payload)
|
||||
|
||||
|
||||
def make_group_help_handler(path_prefix: tuple[str, ...]):
|
||||
def group_help(
|
||||
json_output: Annotated[bool, typer.Option("--json", help="输出 JSON")] = False,
|
||||
) -> None:
|
||||
def group_help() -> None:
|
||||
payload, is_index = resolve_help_payload(path_prefix)
|
||||
if payload is None:
|
||||
raise CLIError(
|
||||
@@ -361,9 +364,9 @@ def make_group_help_handler(path_prefix: tuple[str, ...]):
|
||||
code="COMMAND_NOT_FOUND",
|
||||
message=f"unknown command path: {' '.join(path_prefix)}",
|
||||
exit_code=2,
|
||||
next_commands=["tjwater help"],
|
||||
next_commands=["tjwater-cli help"],
|
||||
)
|
||||
emit_help_payload(payload, json_output=json_output, is_index=is_index)
|
||||
emit_help_payload(payload)
|
||||
|
||||
group_help.__name__ = f"{'_'.join(path_prefix)}_help"
|
||||
return group_help
|
||||
@@ -375,19 +378,30 @@ def register_group_help_commands() -> None:
|
||||
|
||||
|
||||
def apply_typer_help_metadata() -> None:
|
||||
app.help = "TJWater agent CLI"
|
||||
app.help = "\n".join(
|
||||
[
|
||||
"TJWater agent CLI",
|
||||
"",
|
||||
"Examples:",
|
||||
" tjwater-cli help",
|
||||
" tjwater-cli help simulation run",
|
||||
" tjwater-cli simulation run --help",
|
||||
]
|
||||
)
|
||||
app.short_help = "TJWater agent CLI"
|
||||
for group_app, path_prefix in GROUP_HELP_APPS:
|
||||
for command_info in group_app.registered_commands:
|
||||
command_path = (*path_prefix, command_info.name)
|
||||
if command_info.name == "help":
|
||||
command_info.help = f"显示 {' '.join(path_prefix)} 的帮助信息。"
|
||||
command_info.help = f"输出 {' '.join(path_prefix)} 的 JSON 帮助信息。"
|
||||
command_info.short_help = command_info.help
|
||||
command_info.epilog = "\n".join(["\b", "Example:", f" tjwater-cli help {' '.join(path_prefix)}"])
|
||||
command_info.hidden = False
|
||||
continue
|
||||
payload = get_command_doc(command_path)
|
||||
command_info.help = None if payload is None else str(payload.get("summary", ""))
|
||||
command_info.short_help = command_info.help
|
||||
command_info.epilog = None if payload is None else _build_leaf_help_epilog(command_path, payload)
|
||||
command_info.hidden = is_hidden_path(command_path)
|
||||
for group_info in group_app.registered_groups:
|
||||
group_path = (*path_prefix, group_info.name)
|
||||
|
||||
Reference in New Issue
Block a user