Documentation
¶
Overview ¶
Package stencil is StageFreight's audience-text composition engine — the one lever for every place SF emits text a human reads as the project's voice (scribe README regions, narrate notifications, release bodies, and later tag/ commit messages). A stencil is a reusable pattern SF STAMPS, filling it per run: freeform markdown you author, with {element} embeds where SF supplies text.
The design line it enforces: SF owns the FACTS (a resolver yields the changelog, the digest, the status — structured and trustworthy), the author owns the FRAMING (the prose, order, and voice around them). Defaults live underneath — omit a stencil and SF stamps its embedded default; author one and you've seized the pen.
Grammar — deliberately one embed form, so a badge, a fact, and a composed stencil all read identically and nothing is a special citizen:
{name} — an element; replaced by env.Resolve(name)
There are NO conditionals — conditional logic does not belong in a templating system. Alternation lives in status-carrying facts (an element that resolves differently on pass/fail); absence lives in elision (empty token → gone; a line whose every token resolved empty → the whole line gone); a different ARC (pass vs fail message) is a different stencil selected by routing. Those three mechanisms cover everything a conditional would, without the body ever branching.
The engine is PURE and deterministic: the same template + Env renders byte-identical output (no time, no map-iteration order, no ambient state) — the same reproducibility discipline the badge subset enforces. Graceful by design: a resolver that yields "" renders nothing, and a LINE whose every embed resolved empty renders nothing at all (line elision — authored labels vanish with their data, so "no data → the line disappears" needs no conditionals); an unknown token is left LITERAL — and keeps its line — so a typo is visible, never silently swallowed. A stencil never breaks and the engine never fabricates.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Expand ¶
Expand is Render WITHOUT the blank-line tidy: it resolves {element} embeds (with the same per-render memoization and line elision) but preserves the resolved text verbatim otherwise. Use it where element content must survive byte-for-byte — scribe file bodies, where a multi-line element (an included doc, a table) must not have its internal blank runs squeezed, and no terminating newline should be forced. Render layers collapseBlankLines on top for audience summaries, where elided lines would otherwise leave ragged gaps.
func Render ¶
Render substitutes {element} embeds line by line (with line elision), then tidies runs of blank lines. Deterministic for pure resolvers.
Each element resolves AT MOST ONCE per render (memoized by name): an impure element — an ollama transform is the motivating case — referenced twice must yield the SAME text both times, or one named element would mean two things in a single document. So an impure element is one value per run, stable wherever it appears (non-deterministic across runs, consistent within one). Expensive facts (the changelog) likewise aren't recomputed per reference.
Types ¶
type Env ¶
type Env struct {
// Resolve returns the text an element name expands to, and whether it is known.
// A composed stencil embedding other elements resolves them by calling Render
// itself (composition = resolver recursion), so the engine never re-scans
// expanded output — a fact whose text happens to contain braces is never
// re-interpreted.
Resolve func(name string) (string, bool)
}
Env resolves the elements a stencil references. A consumer (scribe, the narrate runner, release) supplies this closure over its own facts, so the engine stays vocabulary-agnostic — it knows the grammar, never the nouns.