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.
172 lines
5.1 KiB
Python
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())
|