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
|
(** 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
|