(** A deliberately logic-less template engine (spec section 5). A template is DATA, never a program: placeholders, sections, inverted sections and comments, and nothing else. There are no partials, no lambdas, no expression evaluation, no arithmetic, and no filesystem or process access. There is deliberately no "raw" or triple-brace form -- a template cannot opt out of its flavour's escaping. Anything a calendar needs that this cannot express (a month grid's leading blanks, week bucketing) is computed in {!View} and handed in as data. That division is the design, not a workaround. *) (** What a template can be rendered against. *) type value = | Str of string | Bool of bool | List of value list | Obj of (string * value) list type node = | Text of string | Var of string list (** dotted path, e.g. ["name"; "la"] *) | Section of string list * node list | Inverted of string list * node list (** Never raises; a malformed template is an [Error] with a human-readable reason, because the template is user input. *) val parse : string -> (node list, string) result (** Render against a value. Escaping is applied to every interpolated value and NEVER to the template's own literal text, which is the author's markup. A missing key renders as the empty string -- the one deliberate silence, so that a template survives a rite that does not set every optional field. *) val render : flavour:Escape.flavour -> node list -> value -> string (** [parse] then [render]. *) val render_string : flavour:Escape.flavour -> string -> value -> (string, string) result