#!/usr/bin/env python3 """Index exported 1C ConfigCAS files for GUID/string lookup. This is a mechanical content index: decompress, decode when possible, collect GUIDs and string hits. It does not infer semantic object kinds. """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any from inspect_1c_sql_files import GUID_RE, try_decode, try_decompress def inspect_cas_file(path: Path, target_guids: set[str], target_strings: list[str]) -> dict[str, Any]: data = path.read_bytes() payload, compression = try_decompress(data) text, encoding = try_decode(payload) item: dict[str, Any] = { "file_name": path.name, "bytes": len(data), "payload_bytes": len(payload), "compression": compression, "encoding": encoding, "guid_count": 0, "target_guid_hits": [], "target_string_hits": [], "guids_sample": [], "text_preview": "", } if text is None: return item clean = text.replace("\x00", "").replace("\ufeff", "") guids = sorted(set(match.lower() for match in GUID_RE.findall(clean))) item["guid_count"] = len(guids) item["guids_sample"] = guids[:50] item["target_guid_hits"] = sorted(target_guids.intersection(guids)) item["target_string_hits"] = [value for value in target_strings if value and value in clean] if item["target_guid_hits"] or item["target_string_hits"]: item["text_preview"] = clean[:2000] else: item["text_preview"] = clean[:300] return item def main() -> int: parser = argparse.ArgumentParser(description="Index exported ConfigCAS files.") parser.add_argument("cas_dir", type=Path) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--target-guid", action="append", default=[]) parser.add_argument("--target-string", action="append", default=[]) args = parser.parse_args() target_guids = {value.lower() for value in args.target_guid} files = sorted(path for path in args.cas_dir.iterdir() if path.is_file()) items = [inspect_cas_file(path, target_guids, args.target_string) for path in files] guid_hits = [item for item in items if item["target_guid_hits"]] string_hits = [item for item in items if item["target_string_hits"]] compression_counts: dict[str, int] = {} encoding_counts: dict[str, int] = {} for item in items: compression_counts[item["compression"]] = compression_counts.get(item["compression"], 0) + 1 encoding = item["encoding"] or "" encoding_counts[encoding] = encoding_counts.get(encoding, 0) + 1 report = { "schema": "onec_sql_cas_index.v1", "cas_dir": str(args.cas_dir), "file_count": len(items), "target_guids": sorted(target_guids), "target_strings": args.target_string, "compression_counts": dict(sorted(compression_counts.items())), "encoding_counts": dict(sorted(encoding_counts.items())), "target_guid_hit_count": len(guid_hits), "target_string_hit_count": len(string_hits), "target_guid_hits": guid_hits, "target_string_hits": string_hits, "files": items, } args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print( json.dumps( { "output": str(args.output), "files": len(items), "guid_hits": len(guid_hits), "string_hits": len(string_hits), "compression_counts": report["compression_counts"], }, ensure_ascii=False, ) ) return 0 if __name__ == "__main__": raise SystemExit(main())