from dataclasses import dataclass, field from typing import Literal, List, Dict, Any, Callable, Optional BlockType = Literal["plain","sequence","grouped","scheduled","alternate","alternate_distinct","numbered","and_rule","top_level"] @dataclass class FieldSpec: name: str kind: Literal["text","number","choice","list","boolean"] = "text" required: bool = True choices: Optional[List[str]] = None default: Any = None help: str = "" @dataclass class Constraint: check: Callable[[Dict[str, Any]], bool] message: str @dataclass class FormatBlueprint: id: str title: str block_type: BlockType fields: List[FieldSpec] = field(default_factory=list) assemble: Callable[[Dict[str, Any]], str] = lambda v: v.get("text","") constraints: List[Constraint] = field(default_factory=list) def _pairs_from_list(v) -> List[Dict[str, str]]: out = [] for x in v or []: if isinstance(x, dict) and "key" in x and "val" in x: out.append({"key": str(x["key"]), "val": str(x["val"])}) return out def bp_top_level(): def assemble(v): owner = (v.get("owner") or "").strip() pairs = _pairs_from_list(v.get("pairs")) body = " ".join([f":: {p['key'].strip()} :: {p['val'].strip()} ;" for p in pairs]) return f"{owner} {body} !!".strip() return FormatBlueprint( id="top_level_simple", title="Top-level (owner :: key :: val ; ... !!)", block_type="top_level", fields=[ FieldSpec("owner", help="Например: portrait"), FieldSpec("pairs", kind="list", help="Список пар key/val"), ], assemble=assemble, constraints=[ Constraint(lambda v: bool((v.get("owner") or "").strip()), "owner пуст"), ], ) def bp_sequence(): def assemble(v): items = [str(x).strip() for x in (v.get("items") or []) if str(x).strip()] if not items: return "" inner = " , ".join(items) return f"{{ {inner} }} ;" return FormatBlueprint( id="sequence_simple", title="Sequence { a , b , c } ;", block_type="sequence", fields=[FieldSpec("items", kind="list", help="Список элементов")], assemble=assemble, constraints=[Constraint(lambda v: len(v.get("items") or [])>=1, "Нужно >=1 элемента")], ) def bp_numbered(): def assemble(v): n = int(v.get("count") or 1) marker = str(v.get("marker") or "").strip() # "", "!", "_" options = [str(x).strip() for x in (v.get("options") or []) if str(x).strip()] inside = " | ".join(options) return f"{n}{marker} {{ {inside} }}" return FormatBlueprint( id="numbered_simple", title="Numbered (N[!|_] { a | b | c })", block_type="numbered", fields=[ FieldSpec("count", kind="number", default=3, help="Сколько выбрать"), FieldSpec("marker", kind="choice", choices=["", "!", "_"], default="!"), FieldSpec("options", kind="list", help="Опции в { }"), ], assemble=assemble, constraints=[ Constraint(lambda v: int(v.get("count") or 0)>=1, "count >= 1"), Constraint(lambda v: len(v.get("options") or [])>=1, "Требуется >=1 опции"), ], ) def bp_alternate(): def assemble(v): opts = [str(x).strip() for x in (v.get("options") or []) if str(x).strip()] body = " | ".join(opts) distinct = bool(v.get("distinct") or False) return f"[ {body} ]!" if distinct else f"[ {body} ]" return FormatBlueprint( id="alternate_simple", title="Alternate [ a | b | c ] (! для distinct)", block_type="alternate", fields=[ FieldSpec("options", kind="list", help="Опции в []"), FieldSpec("distinct", kind="boolean", default=False, help="Добавить !"), ], assemble=assemble, constraints=[Constraint(lambda v: len(v.get("options") or [])>=1, "Требуется >=1 опции")], ) def bp_scheduled(): # [ a : b : c ] : 10-50% def assemble(v): parts = [str(x).strip() for x in (v.get("parts") or []) if str(x).strip()] tail = (v.get("tail") or "").strip() return f"[ {' : '.join(parts)} ] : {tail}" return FormatBlueprint( id="scheduled_simple", title="Scheduled [ a : b : ... ] : 10-50%", block_type="scheduled", fields=[ FieldSpec("parts", kind="list", help="Секции внутри [] через ':'"), FieldSpec("tail", kind="text", default="50", help="Число/диапазон/проценты"), ], assemble=assemble, constraints=[Constraint(lambda v: len(v.get('parts') or [])>=1, "Нужна >=1 секция")], ) def get_blueprints(): return [ bp_top_level(), bp_sequence(), bp_numbered(), bp_alternate(), bp_scheduled(), ]