# 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 ```ts 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 ```ts 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(): Plugin` Returns a `Plugin` with: - `name: "counter"` - `priority: PluginPriority.HIGH` (80) - `pattern: /\{_counter(?::([^:}]+)(?::(\d+)(?::(\d+))?)?)?\}/g` The pattern captures up to three optional groups: 1. Name-or-start: either a counter name (non-numeric string) or a numeric start value 2. Start-or-padding: a numeric start value (when group 1 is a name) or a pad length 3. 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 - `getCounter` from `ParseState` initialises a counter at `initialValue - 1` on first access so that the first call returns `initialValue` without a special branch. This detail is encapsulated in `ParseState`; 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 from `format.ts`, which zero-pads from the left by default. - Runs at `HIGH` priority (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.