101 lines
3.7 KiB
Python
101 lines
3.7 KiB
Python
#!/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 "<binary>"
|
|
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())
|