first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# 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<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:
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.