#!/usr/bin/env python3 """List forms, templates, commands, and module files related to a 1C object.""" from __future__ import annotations import argparse import base64 import json import sys from pathlib import Path from typing import Any sys.path.insert(0, str(Path(__file__).resolve().parent)) from resolve_1c_object import resolve_object # noqa: E402 def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8-sig")) def decode_arg(value: str | None, encoded: str | None) -> str | None: if encoded: return base64.b64decode(encoded).decode("utf-8") return value def normalize(value: str) -> str: return value.casefold().replace(" ", "") def top_objects(item: dict[str, Any]) -> list[dict[str, Any]]: return item.get("xml_top_objects") or [] def find_owner(index: dict[str, Any], *, kind: str, name: str) -> tuple[str, dict[str, Any], dict[str, Any], dict[str, Any]]: resolution = resolve_object(index, kind=kind, name=name, limit=50) canonical = resolution.get("canonical") if not canonical: raise SystemExit(f"Object not found: {kind}.{name}") guid = canonical["guid"] item = (index.get("objects") or {}).get(guid) if not item: raise SystemExit(f"Resolved object is missing from index: {guid}") wanted_path = canonical.get("relative_path") owner_top = next((top for top in top_objects(item) if top.get("relative_path") == wanted_path), None) if not owner_top: owner_top = next((top for top in top_objects(item) if top.get("name") == canonical.get("name")), None) if not owner_top: raise SystemExit(f"Resolved object XML route is missing from index: {guid}") return guid, item, owner_top, resolution def owner_prefix(relative_path: str) -> str: if relative_path.lower().endswith(".xml"): return relative_path[:-4] return relative_path def classify_child(owner: str, top: dict[str, Any]) -> str | None: relative = str(top.get("relative_path") or "") if not relative.startswith(owner + "\\"): return None rest = relative[len(owner) + 1 :] first = rest.split("\\", 1)[0] if first in {"Forms", "Templates", "Commands"}: return first[:-1].lower() if first.endswith("s") else first.lower() return "other" def collect_xml_children(index: dict[str, Any], owner: str) -> dict[str, list[dict[str, Any]]]: result: dict[str, list[dict[str, Any]]] = {"form": [], "template": [], "command": [], "other": []} for guid, item in (index.get("objects") or {}).items(): for top in top_objects(item): category = classify_child(owner, top) if not category: continue result.setdefault(category, []).append( { "guid": guid, "xml_kind": top.get("xml_kind"), "name": top.get("name"), "synonym": top.get("synonym"), "relative_path": top.get("relative_path"), "path": top.get("path"), "route_kind": item.get("route_kind") or [], "dbnames": item.get("dbnames") or [], "extension_routes": item.get("extension_routes") or [], } ) for values in result.values(): values.sort(key=lambda row: row.get("relative_path") or "") return result def file_record(path: Path, *, root: Path) -> dict[str, Any]: try: relative = str(path.relative_to(root)) except ValueError: relative = str(path) return { "path": str(path), "relative_to_object_dir": relative, "size": path.stat().st_size, "suffix": path.suffix.lower(), } def collect_files(owner_top: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: owner_xml = Path(str(owner_top.get("path") or "")) owner_dir = owner_xml.with_suffix("") result = {"object_modules": [], "form_modules": [], "form_xml": [], "template_xml": [], "command_xml": [], "command_modules": [], "other": []} if not owner_dir.exists(): return result for path in sorted(item for item in owner_dir.rglob("*") if item.is_file()): relative = str(path.relative_to(owner_dir)) record = file_record(path, root=owner_dir) if relative in {"Ext\\ObjectModule.bsl", "Ext\\ManagerModule.bsl"}: result["object_modules"].append(record) elif relative.endswith("\\Ext\\Form\\Module.bsl"): result["form_modules"].append(record) elif relative.startswith("Forms\\") and path.suffix.lower() == ".xml": result["form_xml"].append(record) elif relative.startswith("Templates\\") and path.suffix.lower() == ".xml": result["template_xml"].append(record) elif relative.startswith("Commands\\") and path.suffix.lower() == ".xml": result["command_xml"].append(record) elif relative.startswith("Commands\\") and path.suffix.lower() == ".bsl": result["command_modules"].append(record) else: result["other"].append(record) return result def main() -> int: parser = argparse.ArgumentParser(description="Get related 1C object artifacts.") parser.add_argument("--index", type=Path, required=True) parser.add_argument("--kind") parser.add_argument("--name") parser.add_argument("--kind-b64") parser.add_argument("--name-b64") parser.add_argument("--output", type=Path) args = parser.parse_args() kind = decode_arg(args.kind, args.kind_b64) name = decode_arg(args.name, args.name_b64) if not kind or not name: raise SystemExit("Use --kind/--name or --kind-b64/--name-b64.") index = load_json(args.index) guid, item, owner_top, resolution = find_owner(index, kind=kind, name=name) owner = owner_prefix(str(owner_top.get("relative_path") or "")) children = collect_xml_children(index, owner) files = collect_files(owner_top) result = { "schema": "onec_object_artifacts.v1", "query": {"kind": kind, "name": name}, "resolution": { "schema": resolution.get("schema"), "canonical": resolution.get("canonical"), "summary": resolution.get("summary"), }, "owner": { "guid": guid, "xml_kind": owner_top.get("xml_kind"), "name": owner_top.get("name"), "synonym": owner_top.get("synonym"), "relative_path": owner_top.get("relative_path"), "path": owner_top.get("path"), "route_kind": item.get("route_kind") or [], "dbnames": item.get("dbnames") or [], "config_routes": item.get("config_routes") or [], "extension_routes": item.get("extension_routes") or [], }, "children": children, "files": files, "counts": { "forms": len(children.get("form") or []), "templates": len(children.get("template") or []), "commands": len(children.get("command") or []), "command_xml_files": len(files.get("command_xml") or []), "command_modules": len(files.get("command_modules") or []), "object_modules": len(files.get("object_modules") or []), "form_modules": len(files.get("form_modules") or []), }, } text = json.dumps(result, ensure_ascii=False, indent=2) + "\n" if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(text, encoding="utf-8") print(json.dumps({"output": str(args.output), "counts": result["counts"]}, ensure_ascii=True)) else: print(json.dumps(result, ensure_ascii=True, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())