#!/usr/bin/env python3 """Read a selected 1C object module from base/effective/extension context.""" from __future__ import annotations import argparse import base64 import json import re import sys from pathlib import Path from typing import Any sys.path.insert(0, str(Path(__file__).resolve().parent)) from get_1c_object_code_context import build_context # noqa: E402 from resolve_1c_object import load_json # noqa: E402 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 | None) -> str: return re.sub(r"[\s._-]+", "", str(value or "")).casefold() def module_aliases(name: str) -> set[str]: n = name.strip() result = {normalize(n)} aliases = { "object_module": "МодульОбъекта", "manager_module": "МодульМенеджера", "form_module": "МодульФормы", "command_module": "МодульКоманды", } if n in aliases: result.add(normalize(aliases[n])) return result def match_module(module: dict[str, Any], wanted: str) -> bool: aliases = module_aliases(wanted) values = { module.get("name"), module.get("kind"), module.get("relative_path"), Path(str(module.get("relative_path") or "")).stem, } return any(normalize(value) in aliases or normalize(wanted) in normalize(value) for value in values) def read_text(path: str) -> str: return Path(path).read_text(encoding="utf-8-sig", errors="replace") def routine_span(text: str, routine: str) -> tuple[int, int] | None: wanted = normalize(routine) lines = text.splitlines() start = None end_pattern = None for index, line in enumerate(lines): match = re.match(r"^\s*(Процедура|Функция)\s+([A-Za-zА-Яа-я0-9_]+)", line, re.IGNORECASE) if not match: continue if normalize(match.group(2)) != wanted: continue start = index end_pattern = "КонецПроцедуры" if match.group(1).casefold() == "процедура" else "КонецФункции" break if start is None or end_pattern is None: return None for index in range(start + 1, len(lines)): if re.match(rf"^\s*{end_pattern}\b", lines[index], re.IGNORECASE): return start, index + 1 return start, len(lines) def snippet(text: str, *, max_chars: int, routine: str | None) -> dict[str, Any]: source = text line_start = 1 found_routine = None if routine: span = routine_span(text, routine) if span: lines = text.splitlines() source = "\n".join(lines[span[0] : span[1]]) line_start = span[0] + 1 found_routine = routine else: source = "" truncated = len(source) > max_chars return { "routine": found_routine, "line_start": line_start if source else None, "text": source[:max_chars], "truncated": truncated, "char_count": len(source), } def materialize(module: dict[str, Any], *, max_chars: int, routine: str | None) -> dict[str, Any]: text = read_text(str(module["path"])) return { "name": module.get("name"), "kind": module.get("kind"), "origin": module.get("origin"), "effective_action": module.get("effective_action"), "relative_path": module.get("relative_path"), "path": module.get("path"), "size": module.get("size"), "content": snippet(text, max_chars=max_chars, routine=routine), } def build_module_result( index: dict[str, Any], *, kind: str, name: str, module_name: str, view: str, extension: str | None, max_chars: int, routine: str | None, ) -> dict[str, Any]: context = build_context(index, kind=kind, name=name, view=view, extension=extension) matches = [module for module in context.get("modules") or [] if match_module(module, module_name)] if not matches: raise SystemExit(f"Module not found: {module_name}") modules = [] for module in matches: modules.append(materialize(module, max_chars=max_chars, routine=routine)) for overlay in module.get("extension_overlays") or []: modules.append(materialize(overlay, max_chars=max_chars, routine=routine)) return { "schema": "onec_module_content.v1", "view": view, "extension": extension if view == "extension" else None, "object": context.get("object"), "query": {"module": module_name, "routine": routine, "max_chars": max_chars}, "modules": modules, "counts": {"modules": len(modules)}, } def main() -> int: parser = argparse.ArgumentParser(description="Get 1C module content.") 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("--module", required=True) parser.add_argument("--view", choices=["effective", "base", "extension"], default="effective") parser.add_argument("--extension") parser.add_argument("--routine") parser.add_argument("--max-chars", type=int, default=20000) 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.") result = build_module_result( load_json(args.index), kind=kind, name=name, module_name=args.module, view=args.view, extension=args.extension, max_chars=args.max_chars, routine=args.routine, ) 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"], "view": result["view"]}, ensure_ascii=False)) else: print(text) return 0 if __name__ == "__main__": raise SystemExit(main())