3.6 KiB
CounterPlugin
Purpose
Provides stateful auto-incrementing counters within a single template rendering pass. Counters are scoped to the ParseState object created for each parse() call, meaning counter values always start fresh on every render. Multiple independent named counters can exist simultaneously, and each counter can be configured with a custom starting value and optional zero-padding, making the plugin suitable for generating numbered lists, pagination markers, or padded identifiers.
Features
{_counter}— default counter, increments from 1.{_counter:10}— start counting from a custom initial value.{_counter:1:3}— start at 1 and zero-pad output to 3 digits (001,002, …).{_counter:name}— independent named counter starting at 1.{_counter:name:5}— named counter starting at 5.{_counter:name:1:4}— named counter, start 1, pad to 4 digits.- Multiple counters in the same template are each incremented in order of appearance.
- Counters reset to their initial value on every new
parse()call.
Usage: Basic Example
import { TemplateEngine, createCounterPlugin } from "@suaveplan/templates";
const engine = new TemplateEngine().use(createCounterPlugin());
engine.parse("{_counter}. {_counter}. {_counter}.", {});
// → "1. 2. 3."
// Zero-padded counter
engine.parse("{_counter:1:3} {_counter:1:3} {_counter:1:3}", {});
// → "001 002 003"
Usage: Advanced Example
import { TemplateEngine, createCounterPlugin, createDotNotationPlugin } from "@suaveplan/templates";
const engine = new TemplateEngine()
.use(createCounterPlugin())
.use(createDotNotationPlugin());
// Two independent named counters in the same template
const tpl = "Row {_counter:row}: Col {_counter:col} | Row {_counter:row}: Col {_counter:col}";
engine.parse(tpl, {});
// → "Row 1: Col 1 | Row 2: Col 2"
// Counters reset on every parse call
engine.parse("{_counter}", {}); // "1"
engine.parse("{_counter}", {}); // "1" (fresh state)
API Reference
createCounterPlugin<TContext>(): Plugin<TContext>
Returns a Plugin with:
name: "counter"priority: PluginPriority.HIGH(80)pattern: /\{_counter(?::([^:}]+)(?::(\d+)(?::(\d+))?)?)?\}/g
The pattern captures up to three optional groups:
- Name-or-start: either a counter name (non-numeric string) or a numeric start value
- Start-or-padding: a numeric start value (when group 1 is a name) or a pad length
- Padding: pad length (only when group 1 is a name and group 2 is a start value)
transform(match, _context, state): Parses the three optional groups to determine counter name, start value, and pad length. Calls getCounter(state, name, start) to advance the counter and get its current value. Returns pad(value, padLength) when pad length > 0, otherwise String(value).
Implementation Notes
getCounterfromParseStateinitialises a counter atinitialValue - 1on first access so that the first call returnsinitialValuewithout a special branch. This detail is encapsulated inParseState; the plugin only sees the returned current value.- The disambiguation between a numeric first argument and a named counter is done with
/^\d+$/.test(nameOrStart). A counter named"42"is therefore interpreted as a start value, not a name — name strings must contain at least one non-digit character. - Padding uses the
pad(value, length)utility fromformat.ts, which zero-pads from the left by default. - Runs at
HIGHpriority (80) so counters resolve before MEDIUM-priority transforms but after CRITICAL control-flow plugins. This ensures loop bodies can contain counter tokens that are resolved during recursive rendering.