Copperlace Capabilities
Copperlace renders text from named rules and can render object-valued rules as
structured values. A text render starts from one rule name, expands any template
references it encounters, and returns a single string or a RenderError.
Structured rendering preserves an object tree while rendering its text leaves.
See Structured output for JSON and wrapper examples.
Named Rules
Every top-level key is a renderable rule, except for the special
top-level context object. Nested object leaves are available as dotted rule
names, so name.first = ["Mia"] can be rendered as {name.first}. Object
parents such as name are not directly renderable.
name = ["Mia"]
origin = "Hello {name}"
Rendering origin resolves {name} by rendering the top-level name rule.
Rendering name directly is also valid.
Templates
String values are parsed as templates. Literal text is copied into the output.
Expressions inside braces are resolved at render time and append text to the
output. Statements inside {% … %} are resolved at render time but never
append text directly.
adjective = ["bright"]
story = "A {adjective} path"
origin = "{story}"
Template expression whitespace is trimmed, so { name } resolves the same rule
as {name}.
Processors
Template expressions can pipe rendered output through processors. Processors run left-to-right and transform strings only.
name = [" mIA "]
origin = "Hello {name | trim | capitalize}"
Rendering origin produces Hello Mia.
Builtin processors are uppercase, lowercase, trim, capitalize,
titlecase, article, past_tense, pluralize, singularize, and
possessive, present_participle, ordinal, sentence, quote, and slug.
The article processor prefixes rendered text with a or an, as in
{item | article}. The past_tense and present_participle processors convert
one verb token to past tense or -ing form. The pluralize and singularize
processors convert one noun token between singular and plural forms,
possessive adds an English possessive suffix, ordinal adds an ordinal suffix
to one integer token, sentence capitalizes the first alphabetic character, and
quote wraps rendered text in escaped double quotes. The slug processor
normalizes rendered text into a lowercase hyphenated slug. Rust, C ABI, Python,
Java, and JS/WASM callers can register custom processors when constructing a
ruleset. Custom processors extend the builtin registry and override builtin
processors when names collide. The CLI uses the builtin processor registry only.
Random Choices
Array values are random choices. Rendering an array-backed rule chooses one child value and renders that value.
mood = [vexed, wistful, astute]
origin = "The path felt {mood}."
Choice elements may be strings, arrays, or scalar values. Nested object values
are not renderable outside the special top-level context object, except for
weighted choice entries inside arrays.
Weighted choices use object entries with value and weight. If any array
entry is weighted, plain entries in the same array use weight 1.0.
mood = [
{ value = common, weight = 9.5 },
{ value = rare, weight = 0.5 },
ordinary
]
Arrays inside structured object-valued rules are different from top-level list rules: they render as arrays and preserve their children in order. Top-level list rules remain random text choices in v1.
Arrays inside structured objects are preserved for structured rendering. Nested arrays that are valid structured data but not valid weighted text choices are accepted structurally and rejected only if callers try to render that nested array as a dotted text rule.
Bindings
Copperlace supports two binding forms. Both forms emit no text directly.
Bind If Missing
A statement of the form {% alias:rule %} ensures alias has a value for the
current render and emits no text directly. If alias is missing, Copperlace
renders rule and stores the result under alias. If alias already exists,
the statement leaves the existing value unchanged and does not render rule.
name = ["Mia"]
origin = "{% hero:name %}{hero}/{hero}"
Rendering origin produces Mia/Mia. The first statement binds hero; later
{hero} references reuse the same value.
Bindings do not overwrite an existing value:
first = ["Mia"]
second = ["Darcy"]
origin = "{% hero:first %}{% hero:second %}{hero}"
Rendering origin produces Mia. The second binding statement is a no-op
because hero already exists.
Binding statements store processed values when a pipeline is present:
name = ["mia"]
origin = "{% hero:name | uppercase %}{hero}"
Rendering origin produces MIA.
Overwrite Binding
A statement of the form {% alias:=rule %} always renders rule, stores the
result under alias, and replaces any existing value for alias.
first = ["Mia"]
second = ["Darcy"]
origin = "{% hero:first %}{% hero:=second %}{hero}"
Rendering origin produces Darcy. The overwrite binding replaces the
previous hero value.
Bindings are scoped to one call to render_rule. A later render starts with an
empty render context and may choose different random values.
Context Defaults
A top-level context object defines lazy defaults for names used inside
templates. When a template references {name}, resolution uses this order:
-
Existing value already bound in the current render context.
-
Lazy default from top-level
context. -
Top-level rule with the same name.
name = ["Mia"]
animal = ["owl"]
story = "{hero} and {heroPet}"
origin = "{story}"
context = {
hero = "{name}"
heroPet = "{animal}"
}
Rendering origin renders story; the first {hero} reference renders and
caches context.hero, and the first {heroPet} reference renders and caches
context.heroPet.
Rust callers can also provide initial render context values when rendering a
rule. Initial values use the same resolution slot as existing bindings: they
resolve before lazy context defaults and named rules, and they are scoped to
one render call.
Cycle Detection
Copperlace tracks the rule call stack during a render. If a rule attempts to
render a rule already on the stack, rendering fails with
CircularRuleReference.
a = "{b}"
b = "{a}"