first commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# CaseTransformPlugin
|
||||
|
||||
## Purpose
|
||||
|
||||
Applies string case transformations to resolved context values. Supports eight case styles covering all common naming conventions. The plugin resolves the variable path first, then transforms the resulting string, making it composable with any data shape without pre-formatting the values in the context object.
|
||||
|
||||
## Features
|
||||
|
||||
- `upper` — ALL CAPS
|
||||
- `lower` — all lowercase
|
||||
- `title` — Title Case (each word capitalised)
|
||||
- `camel` — camelCase (first word lower)
|
||||
- `pascal` — PascalCase (each word upper)
|
||||
- `snake` — snake_case
|
||||
- `kebab` — kebab-case
|
||||
- `constant` — CONSTANT_CASE
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { TemplateEngine, createCaseTransformPlugin } from "@suaveplan/templates";
|
||||
|
||||
const engine = new TemplateEngine().use(createCaseTransformPlugin());
|
||||
engine.parse("{name:title}", { name: "john doe" }); // "John Doe"
|
||||
engine.parse("{key:kebab}", { key: "myVariableName" }); // "my-variable-name"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { TemplateEngine, createCaseTransformPlugin, createDotNotationPlugin } from "@suaveplan/templates";
|
||||
|
||||
const engine = new TemplateEngine()
|
||||
.use(createCaseTransformPlugin())
|
||||
.use(createDotNotationPlugin());
|
||||
|
||||
const ctx = { user: { fullName: "alice smith" } };
|
||||
engine.parse(
|
||||
"Slug: {user.fullName:kebab}, Class: {user.fullName:pascal}",
|
||||
ctx
|
||||
); // "Slug: alice-smith, Class: AliceSmith"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createCaseTransformPlugin<TContext>(): Plugin<TContext>`
|
||||
|
||||
Returns a `Plugin` with:
|
||||
|
||||
- `name: "case-transform"`
|
||||
- `priority: PluginPriority.MEDIUM` (50)
|
||||
- `pattern: /\{([a-zA-Z_$][\w.$]*(?:\[-?\d+\])?):(?:[^[\]]+\[[-\d]+\]:)?(upper|lower|title|camel|pascal|snake|kebab|constant)\}/g`
|
||||
|
||||
**transform(match, context)**: Re-extracts the path from the full match, resolves it via `getNestedValue`, and applies the named case transform. Returns `""` for nullish values, `SKIP_PLUGIN` for `_`-prefixed paths.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `snake`, `kebab`, and `constant` all follow the same prefix-on-uppercase approach: insert separator before each uppercase letter, replace other separators, normalise case, strip leading separator, collapse doubles.
|
||||
- `camel` and `pascal` split on `/[\s_-]+/` so they handle space-separated, snake_case, and kebab-case inputs uniformly.
|
||||
- Runs at MEDIUM priority so it runs before variable-resolution plugins but after dynamic and control-flow plugins.
|
||||
|
||||
### Transforms
|
||||
|
||||
`CaseTransformPlugin` registers the `upper`, `lower`, `title`, `camel`, `pascal`, `snake`, and `kebab` case-transform helpers inside the template engine. Each is invoked via the standard `{{ value | upper }}` pipe syntax. The plugin is pure — it does not read context or emit events — so it is safe to register more than once and cheap to add to any preset bundle.
|
||||
@@ -0,0 +1,81 @@
|
||||
# RegexPlugin
|
||||
|
||||
## Purpose
|
||||
|
||||
Applies inline regular-expression operations to resolved context values. The plugin enables match extraction, capture group access, and global search-and-replace — all expressed within the template syntax — so callers can reshape value strings without pre-processing the context object. By centralising regex logic here the main application code stays free of ad-hoc string manipulation.
|
||||
|
||||
## Features
|
||||
|
||||
- `{path:/pattern/flags}` — extract the first match of the regex against the resolved value.
|
||||
- `{path:/pattern/flags:N}` — extract capture group N (0 = full match, 1 = first group, etc.).
|
||||
- `{path:/pattern/flags:replace:replacement}` — replace all occurrences using `String.replace`.
|
||||
- Supports all standard regex flags: `g`, `i`, `m`, `s`, `u`, `y`.
|
||||
- Returns `""` for nullish resolved values or when no match is found.
|
||||
- Returns the original match string for invalid regex patterns (safe degradation).
|
||||
- Skips `_`-prefixed paths (reserved for dynamic plugins).
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { TemplateEngine, createRegexPlugin } from "@suaveplan/templates";
|
||||
|
||||
const engine = new TemplateEngine().use(createRegexPlugin());
|
||||
|
||||
engine.parse("{phone:/\\D/g:replace:}", { phone: "+1 (555) 867-5309" });
|
||||
// → "15558675309"
|
||||
|
||||
engine.parse("{email:/@.+$/}", { email: "alice@example.com" });
|
||||
// → "@example.com"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { TemplateEngine, createRegexPlugin } from "@suaveplan/templates";
|
||||
|
||||
const engine = new TemplateEngine().use(createRegexPlugin());
|
||||
|
||||
// Capture groups: extract year from ISO date
|
||||
engine.parse(
|
||||
"Year: {date:/^(\\d{4})-(\\d{2})-(\\d{2})$/:1}",
|
||||
{ date: "2024-06-15" }
|
||||
);
|
||||
// → "Year: 2024"
|
||||
|
||||
// Replace non-alphanumeric characters
|
||||
engine.parse("{code:/[a-z]/gi:replace:X}", { code: "abc123" });
|
||||
// → "XXX123"
|
||||
|
||||
// Out-of-range group returns empty string
|
||||
engine.parse("{value:/(a)(b)/:9}", { value: "ab" });
|
||||
// → ""
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createRegexPlugin<TContext>(): Plugin<TContext>`
|
||||
|
||||
Returns a `Plugin` with:
|
||||
|
||||
- `name: "regex"`
|
||||
- `priority: PluginPriority.MEDIUM` (50)
|
||||
- `pattern: /\{([a-zA-Z_$][\w.$]*):\/([^/]+)\/([gimsuy]*)?(?::(\w+)(?::([^}]*))?)?\\}/g`
|
||||
|
||||
The pattern captures five groups:
|
||||
|
||||
1. Variable path (e.g. `phone`)
|
||||
2. Regex pattern body (e.g. `\\D`)
|
||||
3. Flags string (e.g. `g`, may be empty)
|
||||
4. Operation keyword (`replace` or a decimal digit string)
|
||||
5. Replacement string when operation is `replace`
|
||||
|
||||
**transform(match, context)**: Resolves the path via `getNestedValue`, converts the result to a string, then applies the specified operation. Returns `SKIP_PLUGIN` when `path` or `pattern` capture groups are absent. Returns the original match text when the regex constructor throws (malformed pattern). Returns `""` for nullish values or when no match exists.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The regex engine used is the native JS `RegExp` constructor, so all platform-supported flags and syntax are available.
|
||||
- When no operation is specified, `String.match()` is used and only the zeroth element (the full match) is returned; this is equivalent to requesting group 0 explicitly.
|
||||
- Group extraction uses `String.match()` rather than `RegExp.exec()`, which means the `g` flag causes `match()` to return all full matches in an array with no group information. Callers wanting capture groups should omit the `g` flag.
|
||||
- The `replace` operation delegates directly to `String.replace()`, which respects the `g` flag: without it only the first occurrence is replaced; with it all occurrences are replaced.
|
||||
- Invalid regex patterns are caught silently and the original match text is returned, preventing template rendering from crashing due to user-supplied regex strings.
|
||||
- Runs at `MEDIUM` priority so it executes before variable-resolution plugins but after control-flow plugins.
|
||||
@@ -0,0 +1,80 @@
|
||||
# StringSplitPlugin
|
||||
|
||||
## Purpose
|
||||
|
||||
Splits a resolved string value on a configurable delimiter and returns a single element by index. This plugin solves the common template problem of extracting a specific word or segment from a delimited value — such as a first name from a full name, a domain from an email address, or a path component from a URL — without requiring the context to be pre-shaped before rendering.
|
||||
|
||||
## Features
|
||||
|
||||
- Syntax: `{path:delimiter[index]}` — split on delimiter, return element at index.
|
||||
- Default delimiter is a single space when the delimiter is omitted: `{name:[0]}`.
|
||||
- Supports negative indices counting from the end: `{path:,[-1]}` returns the last element.
|
||||
- Returns `""` for nullish resolved values, out-of-range indices, or missing paths.
|
||||
- Skips `_`-prefixed paths (reserved for dynamic plugins).
|
||||
- The delimiter may be any string, including multi-character strings.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { TemplateEngine, createStringSplitPlugin } from "@suaveplan/templates";
|
||||
|
||||
const engine = new TemplateEngine().use(createStringSplitPlugin());
|
||||
|
||||
engine.parse("{name:[0]}", { name: "Alice Smith" });
|
||||
// → "Alice"
|
||||
|
||||
engine.parse("{email:@[1]}", { email: "alice@example.com" });
|
||||
// → "example.com"
|
||||
|
||||
engine.parse("{path:/[-1]}", { path: "/home/user/file.txt" });
|
||||
// → "file.txt"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { TemplateEngine, createStringSplitPlugin, createDotNotationPlugin } from "@suaveplan/templates";
|
||||
|
||||
const engine = new TemplateEngine()
|
||||
.use(createStringSplitPlugin())
|
||||
.use(createDotNotationPlugin());
|
||||
|
||||
// Extract TLD from a domain string
|
||||
const ctx = { domain: "alice.example.co.uk" };
|
||||
engine.parse("TLD: {domain:.[-1]}", ctx);
|
||||
// → "TLD: uk"
|
||||
|
||||
// Extract CSV column
|
||||
const row = { line: "2024,Alice,admin" };
|
||||
engine.parse("User: {line:,[1]}", row);
|
||||
// → "User: Alice"
|
||||
|
||||
// Out-of-range index gracefully returns empty string
|
||||
engine.parse("{tags:,[-5]}", { tags: "a,b" });
|
||||
// → ""
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createStringSplitPlugin<TContext>(): Plugin<TContext>`
|
||||
|
||||
Returns a `Plugin` with:
|
||||
|
||||
- `name: "string-split"`
|
||||
- `priority: PluginPriority.MEDIUM` (50)
|
||||
- `pattern: /\{([a-zA-Z_$][\w.$]*):([^[\]]+)?\[(-?\d+)\]\}/g`
|
||||
|
||||
The pattern captures three groups:
|
||||
|
||||
1. Variable path
|
||||
2. Optional delimiter (defaults to `" "` when absent)
|
||||
3. Integer index (may be negative)
|
||||
|
||||
**transform(match, context)**: Resolves the path via `getNestedValue`, splits the resulting string on the delimiter, and returns the element at the specified index. Negative indices are resolved as `parts.length + index`. Returns `""` when the resolved value is nullish, the index is out of range, or any required group is missing.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The delimiter is captured as an optional group so `{name:[0]}` (delimiter omitted) defaults to a single space, matching the most common split use case.
|
||||
- Negative index arithmetic mirrors the approach used in `getNestedValue`: `actual = index < 0 ? parts.length + index : index`. An index that resolves below 0 after adjustment returns `""`.
|
||||
- `String.split()` with a string delimiter is used directly; the delimiter is not treated as a regex. If callers need regex-based splitting they should use the `RegexPlugin` instead.
|
||||
- Runs at `MEDIUM` priority alongside `CaseTransformPlugin` and `RegexPlugin` so all transforms are applied before variable-resolution plugins.
|
||||
Reference in New Issue
Block a user