#!/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*$") HEADING_RE = re.compile(r"^(#{1,6})(?!#)\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("tags", ""), 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(2) == signature: start = idx start_level = len(match.group(1)) break if start is None: return "" end = len(lines) for idx in range(start + 1, len(lines)): entry_match = ENTRY_RE.match(lines[idx]) if entry_match: end = idx break heading_match = HEADING_RE.match(lines[idx]) if heading_match and len(heading_match.group(1)) <= start_level: 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\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())