from __future__ import annotations from typing import Any, Dict, Sequence, List, Optional import shlex import sys from cmdlet._shared import Cmdlet, CmdletArg, parse_cmdlet_args from SYS.logger import log from result_table import ResultTable import pipeline as ctx def _normalize_choice_list(arg_names: Optional[List[str]]) -> List[str]: return sorted(set(arg_names or [])) def _examples_for_cmd(name: str) -> List[str]: """Return example invocations for a given command (best-effort).""" lookup = { ".adjective": [ '.adjective -add "example"', '.adjective -delete "example"', ], } key = name.replace("_", "-").lower() return lookup.get(key, []) def _find_cmd_metadata(name: str, metadata: Dict[str, Dict[str, Any]]) -> Optional[Dict[str, Any]]: target = name.replace("_", "-").lower() for cmd_name, meta in metadata.items(): if target == cmd_name: return meta aliases = meta.get("aliases", []) or [] if target in aliases: return meta return None def _render_list(metadata: Dict[str, Dict[str, Any]], filter_text: Optional[str], args: Sequence[str]) -> None: table = ResultTable("Help") table.set_source_command(".help", list(args)) items: List[Dict[str, Any]] = [] needle = (filter_text or "").lower().strip() for name in sorted(metadata.keys()): meta = metadata[name] summary = meta.get("summary", "") or "" if needle and needle not in name.lower() and needle not in summary.lower(): continue row = table.add_row() row.add_column("Cmd", name) aliases = ", ".join(meta.get("aliases", []) or []) row.add_column("Aliases", aliases) arg_names = [a.get("name") for a in meta.get("args", []) if a.get("name")] row.add_column("Args", ", ".join(f"-{a}" for a in arg_names)) table.set_row_selection_args(len(table.rows) - 1, ["-cmd", name]) items.append(meta) ctx.set_last_result_table(table, items) ctx.set_current_stage_table(table) from rich_display import stdout_console stdout_console().print(table) def _render_detail(meta: Dict[str, Any], args: Sequence[str]) -> None: title = f"Help: {meta.get('name', '') or 'cmd'}" table = ResultTable(title) table.set_source_command(".help", list(args)) header_lines: List[str] = [] summary = meta.get("summary", "") usage = meta.get("usage", "") aliases = meta.get("aliases", []) or [] examples = _examples_for_cmd(meta.get("name", "")) first_example_tokens: List[str] = [] first_example_cmd: Optional[str] = None if examples: try: split_tokens = shlex.split(examples[0]) if split_tokens: first_example_cmd = split_tokens[0] first_example_tokens = split_tokens[1:] except Exception: pass if summary: header_lines.append(summary) if usage: header_lines.append(f"Usage: {usage}") if aliases: header_lines.append("Aliases: " + ", ".join(aliases)) if examples: header_lines.append("Examples: " + " | ".join(examples)) if header_lines: table.set_header_lines(header_lines) args_meta = meta.get("args", []) or [] example_text = " | ".join(examples) # If we have an example, use it as the source command so @N runs that example if first_example_cmd: table.set_source_command(first_example_cmd, []) if not args_meta: row = table.add_row() row.add_column("Arg", "(none)") row.add_column("Type", "") row.add_column("Req", "") row.add_column("Description", "") row.add_column("Example", example_text) if first_example_tokens: table.set_row_selection_args(len(table.rows) - 1, first_example_tokens) else: for arg in args_meta: row = table.add_row() name = arg.get("name") or "" row.add_column("Arg", f"-{name}" if name else "") row.add_column("Type", arg.get("type", "")) row.add_column("Req", "yes" if arg.get("required") else "") desc = arg.get("description", "") or "" choices = arg.get("choices", []) or [] if choices: choice_text = f"choices: {', '.join(choices)}" desc = f"{desc} ({choice_text})" if desc else choice_text row.add_column("Description", desc) row.add_column("Example", example_text) if first_example_tokens: table.set_row_selection_args(len(table.rows) - 1, first_example_tokens) ctx.set_last_result_table_overlay(table, [meta]) ctx.set_current_stage_table(table) from rich_display import stdout_console stdout_console().print(table) def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: try: import cmdlet_catalog as _catalog CMDLET.arg[0].choices = _normalize_choice_list(_catalog.list_cmdlet_names()) metadata = _catalog.list_cmdlet_metadata() except Exception: CMDLET.arg[0].choices = [] metadata = {} parsed = parse_cmdlet_args(args, CMDLET) filter_text = parsed.get("filter") cmd_arg = parsed.get("cmd") if cmd_arg: target_meta = _find_cmd_metadata(str(cmd_arg), metadata) if not target_meta: log(f"Unknown command: {cmd_arg}", file=sys.stderr) return 1 _render_detail(target_meta, args) return 0 _render_list(metadata, filter_text, args) return 0 CMDLET = Cmdlet( name=".help", alias=["help", "?"], summary="Show cmdlet or detailed help", usage=".help [cmd] [-filter text]", arg=[ CmdletArg( name="cmd", type="string", description="Cmdlet name to show detailed help", required=False, choices=[], ), CmdletArg( name="-filter", type="string", description="Filter cmdlet by substring", required=False, ), ], ) CMDLET.exec = _run