first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user