Files
llm/plugins/1c/parser/config_sections.py
T

85 lines
2.5 KiB
Python

"""Mechanical section summaries for Config brace trees.
This module intentionally does not name sections as attributes, tabular
sections, forms, etc. It only reports paths, shapes, strings, and GUIDs so a
higher-level validator can attach semantics using XML or other evidence.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
from typing import Any
from .payload import GUID_RE, collect_strings, scalar
@dataclass(frozen=True)
class SectionSummary:
path: str
node_type: str
list_len: int | None
first_scalars: list[str]
string_count_sampled: int
guid_count_sampled: int
strings_sample: list[str]
guids_sample: list[str]
def to_dict(self) -> dict[str, Any]:
return asdict(self)
def _children(node: Any) -> list[Any]:
if isinstance(node, dict) and node.get("type") in {"list", "sequence"}:
return node.get("items") or []
return []
def _atoms(node: Any, limit: int) -> list[str]:
values: list[str] = []
def walk(value: Any) -> None:
if len(values) >= limit:
return
if isinstance(value, dict) and value.get("type") in {"atom", "string"}:
text = scalar(value)
if text:
values.append(text)
return
for child in _children(value):
walk(child)
walk(node)
return values
def summarize_section(node: Any, path: str, *, limit: int = 200) -> SectionSummary:
children = _children(node)
atoms = _atoms(node, limit)
strings = collect_strings(node, limit=limit)
guids = sorted(set(value.lower() for value in atoms if GUID_RE.fullmatch(value)))
return SectionSummary(
path=path,
node_type=node.get("type") if isinstance(node, dict) else type(node).__name__,
list_len=len(children) if children else None,
first_scalars=[scalar(child) for child in children[:12]],
string_count_sampled=len(strings),
guid_count_sampled=len(guids),
strings_sample=strings[:50],
guids_sample=guids[:50],
)
def summarize_sections(tree: Any, *, max_depth: int = 2, limit: int = 200) -> list[SectionSummary]:
summaries: list[SectionSummary] = []
def walk(node: Any, path: list[int], depth: int) -> None:
if depth > max_depth:
return
if path:
summaries.append(summarize_section(node, ".".join(str(part) for part in path), limit=limit))
for index, child in enumerate(_children(node)):
walk(child, [*path, index], depth + 1)
walk(tree, [], 0)
return summaries