import argparse import pathlib import sqlite3 import sys def quoted_phrase(value: str) -> str: return '"' + value.replace('"', '""') + '"' def main() -> None: parser = argparse.ArgumentParser(description="Search the Xinje manual page index without scanning PDFs.") parser.add_argument("--index", required=True, type=pathlib.Path) parser.add_argument("--manual", help="Case-insensitive filename substring filter") parser.add_argument("--limit", type=int, default=12) parser.add_argument("query", nargs="+") args = parser.parse_args() query = " ".join(args.query).strip() if not query: raise SystemExit("query cannot be empty") if args.limit < 1: raise SystemExit("--limit must be positive") connection = sqlite3.connect(args.index) try: where = "" parameters = [] if args.manual: where = " AND lower(filename) LIKE ?" parameters.append(f"%{args.manual.lower()}%") try: rows = connection.execute( "SELECT filename, pdf_page, snippet(page_search, 3, '[', ']', ' ... ', 18) " "FROM page_search WHERE page_search MATCH ?" + where + " ORDER BY bm25(page_search) LIMIT ?", [quoted_phrase(query), *parameters, args.limit], ).fetchall() except sqlite3.OperationalError: rows = [] if not rows: rows = connection.execute( "SELECT filename, pdf_page, substr(text, max(1, instr(text, ?) - 120), 420) " "FROM page_search WHERE instr(text, ?) > 0" + where + " LIMIT ?", [query, query, *parameters, args.limit], ).fetchall() print(f"query: {query}") print(f"hits: {len(rows)}") for filename, page, excerpt in rows: print(f"\n[{filename}] PDF page {page}") print(excerpt) finally: connection.close() if __name__ == "__main__": if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") main()