Files
.pouch/skills/pouch/scripts/catalog.py
T
2026-09-08 15:25:58 +08:00

169 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Print a human-facing builtin skill list: one-liner + copy-paste example.
Reads each skill's README.md. Do not substitute SKILL.md descriptions.
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
_HEADING_RE = re.compile(r"^#{1,6}\s+")
_FENCE_RE = re.compile(r"^```(\w+)?\s*$")
def skills_dir() -> Path:
here = Path(__file__).resolve()
repo_skills = here.parents[2]
if (repo_skills / "pouch" / "SKILL.md").is_file():
return repo_skills
return Path.home() / ".pouch" / "skills"
def _strip_md(text: str) -> str:
text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
text = re.sub(r"`([^`]+)`", r"\1", text)
return " ".join(text.split())
def _first_sentence(text: str) -> str:
text = _strip_md(text).strip()
for sep in ("。", "!", "?"):
idx = text.find(sep)
if idx != -1:
return text[: idx + 1]
return text.rstrip("::") + ("。" if text else "")
def _lede(readme: str) -> str:
lines = readme.splitlines()
i = 0
while i < len(lines) and (
not lines[i].strip() or _HEADING_RE.match(lines[i])
):
i += 1
chunk: list[str] = []
while i < len(lines):
raw = lines[i]
stripped = raw.strip()
if not stripped:
break
if _HEADING_RE.match(raw) or stripped.startswith(("- ", "* ", "> ")):
break
chunk.append(stripped)
i += 1
return _first_sentence(" ".join(chunk))
def _section_body(readme: str, titles: tuple[str, ...]) -> str | None:
lines = readme.splitlines()
start = None
for i, line in enumerate(lines):
if line.startswith("## ") and line[3:].strip() in titles:
start = i + 1
break
if start is None:
return None
end = len(lines)
for j in range(start, len(lines)):
if lines[j].startswith("## "):
end = j
break
return "\n".join(lines[start:end])
def _first_example(readme: str) -> str:
body = _section_body(readme, ("使用示例", "常见用法"))
search = body if body is not None else readme
lines = search.splitlines()
i = 0
while i < len(lines):
fence = _FENCE_RE.match(lines[i].strip())
if not fence:
i += 1
continue
i += 1
block: list[str] = []
while i < len(lines) and not lines[i].strip().startswith("```"):
block.append(lines[i])
i += 1
for raw in block:
stripped = raw.strip()
if not stripped or stripped.startswith("#"):
continue
return stripped
i += 1
return ""
def iter_skills(root: Path) -> list[Path]:
skills: list[Path] = []
if not root.is_dir():
return skills
for entry in sorted(root.iterdir()):
if not entry.is_dir() or entry.name.startswith("_"):
continue
if (entry / "SKILL.md").is_file():
skills.append(entry)
return skills
def catalog_rows(root: Path) -> list[tuple[str, str, str]]:
rows: list[tuple[str, str, str]] = []
errors: list[str] = []
for skill in iter_skills(root):
readme_path = skill / "README.md"
if not readme_path.is_file():
errors.append(f"{skill.name}: 缺少 README.md")
continue
text = readme_path.read_text(encoding="utf-8")
lede = _lede(text)
example = _first_example(text)
if not lede:
errors.append(f"{skill.name}: README 缺少一句话说明")
if not example:
errors.append(f"{skill.name}: README 缺少可复制的使用示例")
if lede and example:
rows.append((skill.name, lede, example))
if errors:
raise SystemExit("catalog 失败:\n " + "\n ".join(errors))
return rows
def format_table(rows: list[tuple[str, str, str]]) -> str:
lines = [
"| Skill | 做什么 | 可以这么说 |",
"| --- | --- | --- |",
]
for name, lede, example in rows:
example = example.replace("|", "\\|")
lede = lede.replace("|", "\\|")
lines.append(f"| {name} | {lede} | `{example}` |")
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(
description="列出 builtin skill 的一句话说明和可复制示例"
)
parser.add_argument(
"--skills-dir",
type=Path,
default=None,
help="skill 根目录(默认本仓库 skills/ 或 ~/.pouch/skills)",
)
args = parser.parse_args()
root = args.skills_dir.resolve() if args.skills_dir else skills_dir()
if not root.is_dir():
print(f"找不到 skills 目录: {root}", file=sys.stderr)
return 2
sys.stdout.write(format_table(catalog_rows(root)))
return 0
if __name__ == "__main__":
raise SystemExit(main())