File size: 5,088 Bytes
ede7fa8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
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(),
]
|