Files
playbook/skills/tsl-api-reference/scripts/lookup.py
T
csh e1f7ce052c feat(skills): add tsl api reference skill
Bundle the TSL API index and markdown references under the skill, route TSL docs to use it, and update the codegen index generator to maintain the bundled data.
2026-07-07 16:36:15 +08:00

172 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Query bundled TSL API reference data."""
import argparse
import re
import sys
from pathlib import Path
ENTRY_RE = re.compile(r"^#{2,3}(?!#)\s+`(.+?)`\s*$")
SKILL_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_TSV = SKILL_ROOT / "data" / "function_index.tsv"
DEFAULT_CODEGEN_ROOT = SKILL_ROOT / "references" / "codegen"
def find_tsv(start):
"""Walk upward from start and find a bundled or legacy function_index.tsv."""
for directory in [start, *start.parents]:
candidates = [
directory / "data" / "function_index.tsv",
directory / "function_index.tsv",
directory / "docs" / "tsl" / "codegen" / "function_index.tsv",
]
for candidate in candidates:
if candidate.is_file():
return candidate
return None
def load_rows(tsv_path):
lines = tsv_path.read_text(encoding="utf-8").splitlines()
if not lines:
return []
header = lines[0].split("\t")
rows = []
for line in lines[1:]:
if not line.strip():
continue
values = line.split("\t")
values += [""] * (len(header) - len(values))
rows.append(dict(zip(header, values)))
return rows
def search_exact(rows, name):
key = name.lower()
return [row for row in rows if row.get("name", "").lower() == key]
def search_keyword(rows, terms):
lowered_terms = [term.lower() for term in terms]
matches = []
for row in rows:
haystack = " ".join(
[
row.get("name", ""),
row.get("signature", ""),
row.get("module", ""),
row.get("summary", ""),
]
).lower()
if all(term in haystack for term in lowered_terms):
matches.append(row)
return matches
def slice_entry(codegen_root, page, signature):
md = codegen_root / page
if not md.is_file():
return ""
lines = md.read_text(encoding="utf-8").splitlines()
start = None
for idx, line in enumerate(lines):
match = ENTRY_RE.match(line)
if match and match.group(1) == signature:
start = idx
break
if start is None:
return ""
end = len(lines)
for idx in range(start + 1, len(lines)):
if ENTRY_RE.match(lines[idx]):
end = idx
break
block = lines[start:end]
while block and not block[-1].strip():
block.pop()
return "\n".join(block)
def format_row(row):
return "\t".join(
[
row.get("name", ""),
row.get("signature", ""),
f"{row.get('page', '')}#{row.get('anchor', '')}",
row.get("summary", ""),
]
)
def codegen_root_for_tsv(tsv_path):
if tsv_path.parent.name == "data":
candidate = tsv_path.parent.parent / "references" / "codegen"
if candidate.is_dir():
return candidate
if tsv_path.parent.name == "codegen":
return tsv_path.parent
candidate = tsv_path.parent / "references" / "codegen"
if candidate.is_dir():
return candidate
return DEFAULT_CODEGEN_ROOT
def default_tsv_path():
if DEFAULT_TSV.is_file():
return DEFAULT_TSV
return find_tsv(Path.cwd())
def main(argv=None):
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description="Query TSL API reference facts.")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--name", help="exact API/function name")
group.add_argument("--kw", nargs="+", help="keyword terms (AND)")
parser.add_argument("--tsv", help="explicit path to function_index.tsv")
parser.add_argument("--limit", type=int, default=50)
args = parser.parse_args(argv)
tsv_path = Path(args.tsv) if args.tsv else default_tsv_path()
if not tsv_path or not tsv_path.is_file():
print(
"ERROR: function_index.tsv not found; reinstall tsl-api-reference "
"or pass --tsv PATH",
file=sys.stderr,
)
return 1
rows = load_rows(tsv_path)
codegen_root = codegen_root_for_tsv(tsv_path)
if args.name:
matches = search_exact(rows, args.name)
if not matches:
print(f"No TSL API named '{args.name}'. Try: --kw <中文关键词>")
return 0
for row in matches:
body = slice_entry(codegen_root, row["page"], row["signature"])
if body:
print(body)
else:
print(f"### `{row['signature']}`\n{row.get('summary', '')}")
print(
f"\n<!-- {row['scope']}/{row['module']} · "
f"{row['page']}#{row['anchor']} -->\n"
)
return 0
matches = search_keyword(rows, args.kw)
print(f"# {len(matches)} matches for: {' '.join(args.kw)}")
print("name\tsignature\tpage#anchor\tsummary")
for row in matches[: args.limit]:
print(format_row(row))
if len(matches) > args.limit:
print(f"... {len(matches) - args.limit} more; refine keywords or raise --limit")
return 0
if __name__ == "__main__":
raise SystemExit(main())