"""Индексация BSL-модулей конфигурации.""" from __future__ import annotations import re from dataclasses import dataclass from pathlib import Path from .config import OBJECT_TYPE_FOLDERS # Процедура/Функция Имя(...) RE_SYMBOL = re.compile( r"(?im)^\s*(?:&[А-Яа-яA-Za-z_]+)*\s*" r"(?:Процедура|Функция|Procedure|Function)\s+" r"([А-Яа-яA-Za-z_][А-Яа-яA-Za-z0-9_]*)" ) # Лимит тела для FTS (символы) — полные гигантские модули не кладём целиком. MAX_BODY_CHARS = 200_000 @dataclass class ModuleRec: config: str rel_path: str abs_path: str owner: str module_kind: str symbols: list[str] body: str line_count: int size: int mtime_ns: int def _module_kind(rel: Path) -> str: name = rel.name if name == "Module.bsl": parent = rel.parent.name if parent == "Form": return "FormModule" if parent == "Ext": # CommonModules/.../Ext/Module.bsl или HTTPServices/.../Ext/Module.bsl return "Module" return "Module" if name.endswith("Module.bsl"): return name[: -len(".bsl")] # ObjectModule, ManagerModule, ... return name def _owner_from_rel(rel: Path) -> str: """Documents/ЗаказКлиента/Ext/ObjectModule.bsl → Document.ЗаказКлиента.""" parts = rel.parts if not parts: return "" # Внешняя обработка: <Обработка>/Ext/ObjectModule.bsl if len(parts) >= 3 and parts[1] == "Ext": return f"ExternalDataProcessor.{parts[0]}" # Форма внешней обработки: <Обработка>/Forms/<Форма>/Ext/Form/Module.bsl if len(parts) >= 6 and parts[1] == "Forms": return f"ExternalDataProcessor.{parts[0]}.Form.{parts[2]}" folder = parts[0] otype = OBJECT_TYPE_FOLDERS.get(folder) if otype and len(parts) >= 2: return f"{otype}.{parts[1]}" return folder def parse_bsl_file(path: Path, *, config: str, src_root: Path) -> ModuleRec | None: try: st = path.stat() text = path.read_text(encoding="utf-8-sig", errors="replace") except OSError: return None rel = path.relative_to(src_root).as_posix() symbols = sorted(set(RE_SYMBOL.findall(text))) body = text if len(text) <= MAX_BODY_CHARS else text[:MAX_BODY_CHARS] return ModuleRec( config=config, rel_path=rel, abs_path=str(path), owner=_owner_from_rel(Path(rel)), module_kind=_module_kind(Path(rel)), symbols=symbols, body=body, line_count=text.count("\n") + (1 if text and not text.endswith("\n") else 0), size=st.st_size, mtime_ns=getattr(st, "st_mtime_ns", int(st.st_mtime * 1e9)), ) def iter_bsl_files(src: Path) -> list[Path]: if not src.is_dir(): return [] return sorted(src.rglob("*.bsl"))