@stll/template-conditions 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +76 -64
- package/dist/index.js +52 -11
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { CompareNode, CompareOp, Condition, ConditionNode, ConditionNode as ConditionNode$1, GroupNode, Operand, PredicateNode, PredicateOp, conditionHasFormula, conditionNodeSchema, conditionSchema, emptyCondition } from "@stll/conditions";
|
|
2
2
|
//#region src/path.d.ts
|
|
3
3
|
/** Resolve a dotted path like `company.name` against data. */
|
|
4
|
-
declare const resolvePath: (path: string, data: Record<string, unknown>) => unknown;
|
|
4
|
+
export declare const resolvePath: (path: string, data: Record<string, unknown>) => unknown;
|
|
5
5
|
//#endregion
|
|
6
6
|
//#region src/parse.d.ts
|
|
7
7
|
/** Bare words the tokenizer reads as operators or literals; a data path can
|
|
8
8
|
* never be one of them. */
|
|
9
|
-
declare const CONDITION_RESERVED_WORDS: readonly ["and", "or", "not", "in", "is", "defined", "true", "false"];
|
|
10
|
-
declare const parseCondition: (expression: string) => ConditionNode$1 | null;
|
|
9
|
+
export declare const CONDITION_RESERVED_WORDS: readonly ["and", "or", "not", "in", "is", "defined", "true", "false"];
|
|
10
|
+
export declare const parseCondition: (expression: string) => ConditionNode$1 | null;
|
|
11
11
|
//#endregion
|
|
12
12
|
//#region src/referenced-paths.d.ts
|
|
13
13
|
/**
|
|
@@ -23,7 +23,7 @@ declare const parseCondition: (expression: string) => ConditionNode$1 | null;
|
|
|
23
23
|
* deciding what a condition depends on must not read that as "depends on
|
|
24
24
|
* nothing".
|
|
25
25
|
*/
|
|
26
|
-
declare const referencedConditionPaths: (expression: string) => string[] | null;
|
|
26
|
+
export declare const referencedConditionPaths: (expression: string) => string[] | null;
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region src/compute.d.ts
|
|
29
29
|
/**
|
|
@@ -43,9 +43,9 @@ declare const referencedConditionPaths: (expression: string) => string[] | null;
|
|
|
43
43
|
* non-numeric variable, division producing a non-finite result) so callers
|
|
44
44
|
* can fall back to leaving the field unfilled rather than emitting `NaN`.
|
|
45
45
|
*/
|
|
46
|
-
declare const NUMERIC_FUNCTION_NAMES: readonly ["min", "max", "round", "abs", "floor", "ceil"];
|
|
46
|
+
export declare const NUMERIC_FUNCTION_NAMES: readonly ["min", "max", "round", "abs", "floor", "ceil"];
|
|
47
47
|
type NumericFunctionName = (typeof NUMERIC_FUNCTION_NAMES)[number];
|
|
48
|
-
declare const evaluateNumericExpression: (expression: string, data: Record<string, unknown>) => number | undefined;
|
|
48
|
+
export declare const evaluateNumericExpression: (expression: string, data: Record<string, unknown>) => number | undefined;
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/field-values.d.ts
|
|
51
51
|
/**
|
|
@@ -67,10 +67,10 @@ declare const evaluateNumericExpression: (expression: string, data: Record<strin
|
|
|
67
67
|
* STRUCTURAL shapes so the package stays free of api/web imports; callers pass
|
|
68
68
|
* their own field objects, which structurally satisfy these.
|
|
69
69
|
*/
|
|
70
|
-
declare const DATE_FORMAT_STYLES: readonly ["long", "medium", "short", "iso"];
|
|
70
|
+
export declare const DATE_FORMAT_STYLES: readonly ["long", "medium", "short", "iso"];
|
|
71
71
|
type DateFormatStyle = (typeof DATE_FORMAT_STYLES)[number];
|
|
72
72
|
/** Stable exemplar for date-format configuration previews. */
|
|
73
|
-
declare const DATE_FORMAT_EXAMPLE_ISO = "2028-06-13";
|
|
73
|
+
export declare const DATE_FORMAT_EXAMPLE_ISO = "2028-06-13";
|
|
74
74
|
/** Locale-aware date rendering config. "iso" leaves the submitted value as-is;
|
|
75
75
|
* the other styles map to `Intl.DateTimeFormat` `dateStyle`. */
|
|
76
76
|
type FieldDateFormat = {
|
|
@@ -85,7 +85,7 @@ type FieldDateFormat = {
|
|
|
85
85
|
* 2028". The "iso" style returns the value unchanged; an invalid calendar date
|
|
86
86
|
* returns null.
|
|
87
87
|
*/
|
|
88
|
-
declare const formatDate: (value: string, dateFormat: FieldDateFormat) => string | null;
|
|
88
|
+
export declare const formatDate: (value: string, dateFormat: FieldDateFormat) => string | null;
|
|
89
89
|
/**
|
|
90
90
|
* Minimal STRUCTURAL config of a field, carrying only what the deterministic
|
|
91
91
|
* transforms read. Callers (api FieldMeta, web StudioField) structurally
|
|
@@ -112,7 +112,7 @@ type DeterministicFieldConfig = {
|
|
|
112
112
|
* Formula and date return null when the expression or value does not yield a
|
|
113
113
|
* value, so the caller leaves the field as-is.
|
|
114
114
|
*/
|
|
115
|
-
declare const renderDeterministicFieldValue: (field: DeterministicFieldConfig, values: Record<string, unknown>) => string | null;
|
|
115
|
+
export declare const renderDeterministicFieldValue: (field: DeterministicFieldConfig, values: Record<string, unknown>) => string | null;
|
|
116
116
|
//#endregion
|
|
117
117
|
//#region src/condition-builder.d.ts
|
|
118
118
|
/**
|
|
@@ -120,7 +120,7 @@ declare const renderDeterministicFieldValue: (field: DeterministicFieldConfig, v
|
|
|
120
120
|
* Returns an empty string for an empty tree (caller treats that as "no
|
|
121
121
|
* condition" / always-visible).
|
|
122
122
|
*/
|
|
123
|
-
declare const serializeCondition: (node: ConditionNode$1) => string;
|
|
123
|
+
export declare const serializeCondition: (node: ConditionNode$1) => string;
|
|
124
124
|
//#endregion
|
|
125
125
|
//#region src/markers.d.ts
|
|
126
126
|
/**
|
|
@@ -159,7 +159,7 @@ type FilterCall = {
|
|
|
159
159
|
args: readonly FilterArgument[];
|
|
160
160
|
};
|
|
161
161
|
/** The loop properties addressable inside a `{% for %}` body. */
|
|
162
|
-
declare const LOOP_PROPERTIES: readonly ["index", "index0", "first", "last", "length"];
|
|
162
|
+
export declare const LOOP_PROPERTIES: readonly ["index", "index0", "first", "last", "length"];
|
|
163
163
|
type LoopProperty = (typeof LOOP_PROPERTIES)[number];
|
|
164
164
|
type MarkerMeta = {
|
|
165
165
|
kind: "placeholder";
|
|
@@ -181,9 +181,14 @@ type MarkerMeta = {
|
|
|
181
181
|
} | {
|
|
182
182
|
kind: "if";
|
|
183
183
|
expr: string;
|
|
184
|
+
/** Filters on a tag whose whole body is one field path configure THAT
|
|
185
|
+
* boolean: a question the document only asks has no value marker to
|
|
186
|
+
* carry a chain. A tag holding a real expression carries none. */
|
|
187
|
+
filters: readonly FilterCall[];
|
|
184
188
|
} | {
|
|
185
189
|
kind: "elif";
|
|
186
190
|
expr: string;
|
|
191
|
+
filters: readonly FilterCall[];
|
|
187
192
|
} | {
|
|
188
193
|
kind: "else";
|
|
189
194
|
} | {
|
|
@@ -199,7 +204,7 @@ type MarkerMeta = {
|
|
|
199
204
|
kind: "endfor";
|
|
200
205
|
};
|
|
201
206
|
type DirectiveKind = MarkerMeta["kind"];
|
|
202
|
-
declare const DIRECTIVE_KINDS: readonly ["placeholder", "clause", "num", "ref", "loop", "if", "elif", "else", "endif", "for", "endfor"];
|
|
207
|
+
export declare const DIRECTIVE_KINDS: readonly ["placeholder", "clause", "num", "ref", "loop", "if", "elif", "else", "endif", "for", "endfor"];
|
|
203
208
|
declare const DIRECTIVE_PLACEMENT: {
|
|
204
209
|
readonly placeholder: "inline";
|
|
205
210
|
readonly clause: "inline";
|
|
@@ -215,19 +220,19 @@ declare const DIRECTIVE_PLACEMENT: {
|
|
|
215
220
|
};
|
|
216
221
|
type BlockDirectiveKind = { [TKind in DirectiveKind]: (typeof DIRECTIVE_PLACEMENT)[TKind] extends "block" ? TKind : never; }[DirectiveKind];
|
|
217
222
|
/** Directives that occupy their own paragraph, in canonical grammar order. */
|
|
218
|
-
declare const BLOCK_DIRECTIVE_KINDS: readonly ["if", "elif", "else", "endif", "for", "endfor"];
|
|
219
|
-
declare const isBlockDirectiveKind: (kind: unknown) => kind is BlockDirectiveKind;
|
|
223
|
+
export declare const BLOCK_DIRECTIVE_KINDS: readonly ["if", "elif", "else", "endif", "for", "endfor"];
|
|
224
|
+
export declare const isBlockDirectiveKind: (kind: unknown) => kind is BlockDirectiveKind;
|
|
220
225
|
/**
|
|
221
226
|
* Every filter the dialect accepts on a fillable value. The names are the
|
|
222
227
|
* grammar's; which manifest property each one writes is decided by the field
|
|
223
228
|
* catalogue in the api, which is total over BOTH this list and the manifest
|
|
224
229
|
* keys, so neither side can gain an entry the other has not decided.
|
|
225
230
|
*/
|
|
226
|
-
declare const FILTER_NAMES: readonly ["text", "number", "date", "checkbox", "select", "options_from", "label", "hint", "required", "pattern", "min", "max", "min_length", "max_length", "min_items", "max_items", "ai", "lookup", "matter", "contact", "party", "attorney", "firm", "formula", "condition"];
|
|
231
|
+
export declare const FILTER_NAMES: readonly ["text", "number", "date", "checkbox", "select", "options_from", "label", "hint", "required", "pattern", "min", "max", "min_length", "max_length", "min_items", "max_items", "ai", "lookup", "matter", "contact", "party", "attorney", "firm", "formula", "condition"];
|
|
227
232
|
type FilterName = (typeof FILTER_NAMES)[number];
|
|
228
233
|
/** Normalise one marker's inner text: typographic quotes become straight
|
|
229
234
|
* quotes, fixed-width spaces become ordinary ones. */
|
|
230
|
-
declare const normalizeMarkerInner: (inner: string) => string;
|
|
235
|
+
export declare const normalizeMarkerInner: (inner: string) => string;
|
|
231
236
|
/**
|
|
232
237
|
* What an output marker holds between its braces: anything but a brace or a
|
|
233
238
|
* double quote, plus whole double-quoted arguments, whose braces are content
|
|
@@ -246,39 +251,39 @@ declare const normalizeMarkerInner: (inner: string) => string;
|
|
|
246
251
|
* adversarial input: a `{{` with no closing `}}` costs one failed pass, not a
|
|
247
252
|
* polynomial retry.
|
|
248
253
|
*/
|
|
249
|
-
declare const MARKER_OUTPUT_BODY: string;
|
|
254
|
+
export declare const MARKER_OUTPUT_BODY: string;
|
|
250
255
|
/** What a tag holds between `{%` and `%}`: anything but a brace, and a `%`
|
|
251
256
|
* only where it does not close the tag. */
|
|
252
|
-
declare const MARKER_STATEMENT_BODY: string;
|
|
257
|
+
export declare const MARKER_STATEMENT_BODY: string;
|
|
253
258
|
/**
|
|
254
259
|
* Any marker span: an output `{{...}}` (group `output`) or a tag `{%...%}`
|
|
255
260
|
* (group `statement`, with the docxtpl placement in group `prefix`).
|
|
256
261
|
*/
|
|
257
|
-
declare const markerPattern: () => RegExp;
|
|
262
|
+
export declare const markerPattern: () => RegExp;
|
|
258
263
|
/** Inline value marker, including its filter chain; group `name` is the path
|
|
259
264
|
* the value comes from and group `filters` the untouched chain. */
|
|
260
|
-
declare const placeholderPattern: () => RegExp;
|
|
265
|
+
export declare const placeholderPattern: () => RegExp;
|
|
261
266
|
/** `{{ clause("Name") }}` / `{{ clause("Name", "v3") }}`. */
|
|
262
|
-
declare const clauseSlotPattern: () => RegExp;
|
|
267
|
+
export declare const clauseSlotPattern: () => RegExp;
|
|
263
268
|
/** `{{ num("key") }}` — the `key` group. */
|
|
264
|
-
declare const numPattern: () => RegExp;
|
|
269
|
+
export declare const numPattern: () => RegExp;
|
|
265
270
|
/** `{{ ref("key") }}` — the `key` group. */
|
|
266
|
-
declare const refPattern: () => RegExp;
|
|
271
|
+
export declare const refPattern: () => RegExp;
|
|
267
272
|
/** `{{ loop.index }}` and its siblings — the `property` group. */
|
|
268
|
-
declare const loopPattern: () => RegExp;
|
|
273
|
+
export declare const loopPattern: () => RegExp;
|
|
269
274
|
/** Cheap presence test for any numbering marker (no capture). */
|
|
270
|
-
declare const hasNumberingPattern: () => RegExp;
|
|
275
|
+
export declare const hasNumberingPattern: () => RegExp;
|
|
271
276
|
/** Cheap presence test for any tag (no capture). */
|
|
272
|
-
declare const hasBlockDirectivePattern: () => RegExp;
|
|
277
|
+
export declare const hasBlockDirectivePattern: () => RegExp;
|
|
273
278
|
/** A whole line that is a single block tag — `prefix`, `tag` and `expr`. */
|
|
274
|
-
declare const blockDirectiveLinePattern: () => RegExp;
|
|
279
|
+
export declare const blockDirectiveLinePattern: () => RegExp;
|
|
275
280
|
/** Whether `value` is a valid field path per the marker grammar (dotted
|
|
276
281
|
* segments of letters/digits/underscore/dash — no brackets or spaces). */
|
|
277
|
-
declare const isFieldPath: (value: string) => boolean;
|
|
282
|
+
export declare const isFieldPath: (value: string) => boolean;
|
|
278
283
|
/** Whether `value` is safe to use as a dotted object path. */
|
|
279
|
-
declare const isSafeFieldPath: (value: string) => boolean;
|
|
284
|
+
export declare const isSafeFieldPath: (value: string) => boolean;
|
|
280
285
|
/** Whether `value` is a valid name for authoring a `clause("NAME")` slot. */
|
|
281
|
-
declare const isClauseSlotName: (value: string) => boolean;
|
|
286
|
+
export declare const isClauseSlotName: (value: string) => boolean;
|
|
282
287
|
/** Which brace pair carried the marker text. */
|
|
283
288
|
type MarkerForm = "output" | "statement";
|
|
284
289
|
/** Where a tag's docxtpl prefix says the tag belongs. `"none"` is a bare tag,
|
|
@@ -288,7 +293,7 @@ type MarkerPrefix = "none" | "paragraph" | "row";
|
|
|
288
293
|
* Classify the inner text of one marker. Returns `null` when the text is not a
|
|
289
294
|
* recognized directive (so a recognizer can leave stray braces alone).
|
|
290
295
|
*/
|
|
291
|
-
declare const classifyMarker: (innerRaw: string, form?: MarkerForm) => MarkerMeta | null;
|
|
296
|
+
export declare const classifyMarker: (innerRaw: string, form?: MarkerForm) => MarkerMeta | null;
|
|
292
297
|
type ScannedMarker = {
|
|
293
298
|
/** Offset of the opening brace pair in the source text. */
|
|
294
299
|
start: number;
|
|
@@ -307,23 +312,23 @@ type ScannedMarker = {
|
|
|
307
312
|
* Find every recognized marker in `text`, in document order, with offsets and
|
|
308
313
|
* classified metadata. Unrecognized spans are skipped.
|
|
309
314
|
*/
|
|
310
|
-
declare const scanMarkers: (text: string) => ScannedMarker[];
|
|
315
|
+
export declare const scanMarkers: (text: string) => ScannedMarker[];
|
|
311
316
|
/**
|
|
312
317
|
* Rewrite every `{{ ... }}` output marker in `text` through `replace`, which
|
|
313
318
|
* receives the whole marker and its raw inner text and returns the replacement.
|
|
314
319
|
* Tags are left untouched. The single scan is the same one the classifier uses,
|
|
315
320
|
* so a caller that rewrites marker text never needs its own regex.
|
|
316
321
|
*/
|
|
317
|
-
declare const replaceOutputMarkers: (text: string, replace: (raw: string, inner: string) => string) => string;
|
|
322
|
+
export declare const replaceOutputMarkers: (text: string, replace: (raw: string, inner: string) => string) => string;
|
|
318
323
|
/**
|
|
319
324
|
* The values-map key one marker substitutes from, or `null` when the marker is
|
|
320
325
|
* not a substitution target (a tag, or a `loop.*` token the loop expander
|
|
321
326
|
* resolves first). One function so the key discovery writes is the key the
|
|
322
327
|
* patcher reads.
|
|
323
328
|
*/
|
|
324
|
-
declare const substitutionKey: (meta: MarkerMeta) => string | null;
|
|
329
|
+
export declare const substitutionKey: (meta: MarkerMeta) => string | null;
|
|
325
330
|
/** The values-map key for one clause slot. */
|
|
326
|
-
declare const clauseSlotKey: (name: string, version?: string) => string;
|
|
331
|
+
export declare const clauseSlotKey: (name: string, version?: string) => string;
|
|
327
332
|
/** A marker-shaped span that classifies to nothing. */
|
|
328
333
|
type InvalidMarker = {
|
|
329
334
|
/** Offset of the opening brace pair in the source text. */
|
|
@@ -342,20 +347,20 @@ type InvalidMarker = {
|
|
|
342
347
|
* recognizer skips, so they print literally at fill time. This is the exact
|
|
343
348
|
* complement of {@link scanMarkers}: a span is in one list or the other.
|
|
344
349
|
*/
|
|
345
|
-
declare const scanInvalidMarkers: (text: string) => InvalidMarker[];
|
|
350
|
+
export declare const scanInvalidMarkers: (text: string) => InvalidMarker[];
|
|
346
351
|
/** The loop alias the codemod (and the legacy-marker message) generates for a
|
|
347
352
|
* `{{#each path}}`: the singular of the last plain-word segment, else `item`. */
|
|
348
|
-
declare const legacyLoopAlias: (path: string) => string;
|
|
353
|
+
export declare const legacyLoopAlias: (path: string) => string;
|
|
349
354
|
/** Translate an old-dialect condition expression to the Jinja surface:
|
|
350
355
|
* `!a` becomes `not a`, `a contains "x"` becomes `"x" in a`. */
|
|
351
|
-
declare const translateLegacyExpression: (expr: string) => string;
|
|
356
|
+
export declare const translateLegacyExpression: (expr: string) => string;
|
|
352
357
|
/**
|
|
353
358
|
* The Jinja marker that replaces one old-dialect marker's inner text, or
|
|
354
359
|
* `null` when the text is not old-dialect. Shared by the rejection message and
|
|
355
360
|
* the codemod, so the fix an author is told to make is the fix the script
|
|
356
361
|
* would have made.
|
|
357
362
|
*/
|
|
358
|
-
declare const legacyMarkerReplacement: (innerRaw: string) => string | null;
|
|
363
|
+
export declare const legacyMarkerReplacement: (innerRaw: string) => string | null;
|
|
359
364
|
/**
|
|
360
365
|
* Why a marker-shaped span misses the grammar in a way that names the
|
|
361
366
|
* authoring mistake rather than merely failing to classify. Consumers derive
|
|
@@ -363,7 +368,7 @@ declare const legacyMarkerReplacement: (innerRaw: string) => string | null;
|
|
|
363
368
|
* drift.
|
|
364
369
|
*/
|
|
365
370
|
type MarkerDefectKind = "legacy_marker" | "unsupported_tag" | "unknown_filter" | "python_expression" | "bracket_index";
|
|
366
|
-
declare const MARKER_DEFECT_KINDS: readonly ["legacy_marker", "unsupported_tag", "unknown_filter", "python_expression", "bracket_index"];
|
|
371
|
+
export declare const MARKER_DEFECT_KINDS: readonly ["legacy_marker", "unsupported_tag", "unknown_filter", "python_expression", "bracket_index"];
|
|
367
372
|
/** One diagnosis of a rejected span: the kind plus the construct to quote. */
|
|
368
373
|
type MarkerDefect = {
|
|
369
374
|
kind: MarkerDefectKind;
|
|
@@ -377,9 +382,9 @@ type MarkerDefect = {
|
|
|
377
382
|
* the text is a recognized directive ({@link classifyMarker} accepts it) or an
|
|
378
383
|
* unrecognizable span with no specific diagnosis (`{{my field}}`).
|
|
379
384
|
*/
|
|
380
|
-
declare const classifyMarkerDefect: (innerRaw: string, form?: MarkerForm) => MarkerDefect | null;
|
|
385
|
+
export declare const classifyMarkerDefect: (innerRaw: string, form?: MarkerForm) => MarkerDefect | null;
|
|
381
386
|
/** Exhaustiveness guard — pass the discriminant in a `switch` default branch. */
|
|
382
|
-
declare const assertNever: (value: never) => never;
|
|
387
|
+
export declare const assertNever: (value: never) => never;
|
|
383
388
|
//#endregion
|
|
384
389
|
//#region src/loop-scope.d.ts
|
|
385
390
|
/**
|
|
@@ -402,14 +407,14 @@ type RowScope = {
|
|
|
402
407
|
/** The array path the manifest speaks, enclosing loops included. */
|
|
403
408
|
scopedPath: string;
|
|
404
409
|
};
|
|
405
|
-
declare const rowScopePaths: (rowScopes: readonly RowScope[]) => string[];
|
|
410
|
+
export declare const rowScopePaths: (rowScopes: readonly RowScope[]) => string[];
|
|
406
411
|
/** A path already under the innermost repeat is left alone; anything else is
|
|
407
412
|
* read as a name relative to it. */
|
|
408
|
-
declare const qualifyRowScopedPath: (path: string, rowPaths?: readonly string[]) => string;
|
|
413
|
+
export declare const qualifyRowScopedPath: (path: string, rowPaths?: readonly string[]) => string;
|
|
409
414
|
/** The manifest path for a marker written inside these loops. */
|
|
410
|
-
declare const qualifyRowScopedPlaceholder: (path: string, rowScopes: readonly RowScope[]) => string;
|
|
415
|
+
export declare const qualifyRowScopedPlaceholder: (path: string, rowScopes: readonly RowScope[]) => string;
|
|
411
416
|
/** The manifest path for an array a `{% for %}` opens inside these loops. */
|
|
412
|
-
declare const qualifyLoopPath: (declaredPath: string, rowScopes: readonly RowScope[]) => string;
|
|
417
|
+
export declare const qualifyLoopPath: (declaredPath: string, rowScopes: readonly RowScope[]) => string;
|
|
413
418
|
//#endregion
|
|
414
419
|
//#region src/field-config.d.ts
|
|
415
420
|
/** Where a field's value comes from, when a record supplies it. Only the shape
|
|
@@ -481,20 +486,20 @@ type MarkerFieldConfig = {
|
|
|
481
486
|
* takes and what to call it. These are the ones a `{% for %}` tag may carry,
|
|
482
487
|
* since the loop path names the array, not one of its values.
|
|
483
488
|
*/
|
|
484
|
-
declare const ARRAY_FILTER_NAMES: readonly ["label", "hint", "required", "min_items", "max_items"];
|
|
485
|
-
declare const isArrayFilterName: (name: FilterName) => boolean;
|
|
489
|
+
export declare const ARRAY_FILTER_NAMES: readonly ["label", "hint", "required", "min_items", "max_items"];
|
|
490
|
+
export declare const isArrayFilterName: (name: FilterName) => boolean;
|
|
486
491
|
/**
|
|
487
492
|
* The filter chain that declares this field in a document, in the catalogue's
|
|
488
493
|
* own order so a chain the writer produces and one an author wrote read the
|
|
489
494
|
* same.
|
|
490
495
|
*/
|
|
491
|
-
declare const filtersFromFieldConfig: (field: MarkerFieldConfig) => FilterCall[];
|
|
496
|
+
export declare const filtersFromFieldConfig: (field: MarkerFieldConfig) => FilterCall[];
|
|
492
497
|
/**
|
|
493
498
|
* The chain a `{% for %}` opener carries for this array: the repeat's own
|
|
494
499
|
* filters and nothing else, derived from {@link ARRAY_FILTER_NAMES}, so a
|
|
495
500
|
* value filter can never be written where the reader refuses it.
|
|
496
501
|
*/
|
|
497
|
-
declare const arrayFiltersFromFieldConfig: (field: MarkerFieldConfig) => FilterCall[];
|
|
502
|
+
export declare const arrayFiltersFromFieldConfig: (field: MarkerFieldConfig) => FilterCall[];
|
|
498
503
|
//#endregion
|
|
499
504
|
//#region src/render.d.ts
|
|
500
505
|
/** Why a value has no spelling the scanner reads back as itself. */
|
|
@@ -502,12 +507,12 @@ type UnwritableReason = "tag-delimiter" | "number-spelling";
|
|
|
502
507
|
/** The reason this literal cannot be written in a marker of this form, or
|
|
503
508
|
* `null` when it can. A boolean always can; a string can in a `{{ }}` marker,
|
|
504
509
|
* because the writer quotes and escapes it. */
|
|
505
|
-
declare const unwritableMarkerLiteral: (value: MarkerLiteral, form?: MarkerForm) => UnwritableReason | null;
|
|
510
|
+
export declare const unwritableMarkerLiteral: (value: MarkerLiteral, form?: MarkerForm) => UnwritableReason | null;
|
|
506
511
|
/** Every value in a chain the grammar has no spelling for, with the filter it
|
|
507
512
|
* was written on and why, so a caller can refuse a configuration before it
|
|
508
513
|
* rewrites a document into something the scanner would read differently. A
|
|
509
514
|
* repeat's chain goes in a tag, so pass `"statement"` for one. */
|
|
510
|
-
declare const unwritableFilterValues: (filters: readonly FilterCall[], form?: MarkerForm) => {
|
|
515
|
+
export declare const unwritableFilterValues: (filters: readonly FilterCall[], form?: MarkerForm) => {
|
|
511
516
|
filter: string;
|
|
512
517
|
value: MarkerLiteral;
|
|
513
518
|
reason: UnwritableReason;
|
|
@@ -517,25 +522,32 @@ declare const unwritableFilterValues: (filters: readonly FilterCall[], form?: Ma
|
|
|
517
522
|
* required`. An empty chain renders the empty string, which is what a marker
|
|
518
523
|
* that configures nothing carries.
|
|
519
524
|
*/
|
|
520
|
-
declare const renderFilterChain: (filters: readonly FilterCall[]) => string;
|
|
525
|
+
export declare const renderFilterChain: (filters: readonly FilterCall[]) => string;
|
|
521
526
|
/** One value marker: `{{ path }}` or `{{ path | filters }}`. */
|
|
522
|
-
declare const renderValueMarker: (path: string, filters: readonly FilterCall[]) => string;
|
|
527
|
+
export declare const renderValueMarker: (path: string, filters: readonly FilterCall[]) => string;
|
|
523
528
|
type ConditionTagOptions = {
|
|
524
529
|
/** Which branch opener this is; `elif` keeps its place in the chain. */
|
|
525
530
|
kind: "if" | "elif";
|
|
526
531
|
/** The `{% if %}` expression, already serialized. */
|
|
527
532
|
expression: string;
|
|
533
|
+
/** The chain the boolean this tag names carries. Only a tag whose whole
|
|
534
|
+
* expression is one field path reads one back, so an expression tag writes
|
|
535
|
+
* none. */
|
|
536
|
+
filters: readonly FilterCall[];
|
|
528
537
|
/** The placement the tag already carried. */
|
|
529
538
|
prefix: MarkerPrefix;
|
|
530
539
|
};
|
|
531
540
|
/**
|
|
532
|
-
* One `{% if %}` / `{% elif %}` opener carrying its expression
|
|
541
|
+
* One `{% if %}` / `{% elif %}` opener carrying its expression, and the field
|
|
542
|
+
* configuration of the boolean it asks for.
|
|
533
543
|
*
|
|
534
544
|
* A rule that decides whether a block shows belongs in the tag that shows it:
|
|
535
545
|
* the tag is the only place a document has for it, so a builder's rule is
|
|
536
|
-
* serialized here rather than stored beside the document under a name.
|
|
546
|
+
* serialized here rather than stored beside the document under a name. The
|
|
547
|
+
* same is true of what to call the question and how to decide it: a condition
|
|
548
|
+
* the document never prints has no value marker to hold that chain either.
|
|
537
549
|
*/
|
|
538
|
-
declare const renderConditionTag: ({ expression, kind, prefix }: ConditionTagOptions) => string;
|
|
550
|
+
export declare const renderConditionTag: ({ expression, filters, kind, prefix }: ConditionTagOptions) => string;
|
|
539
551
|
type ForOpenerOptions = {
|
|
540
552
|
alias: string;
|
|
541
553
|
path: string;
|
|
@@ -545,7 +557,7 @@ type ForOpenerOptions = {
|
|
|
545
557
|
prefix: MarkerPrefix;
|
|
546
558
|
};
|
|
547
559
|
/** One `{% for %}` opener, keeping its placement prefix. */
|
|
548
|
-
declare const renderForOpener: ({ alias, filters, path, prefix }: ForOpenerOptions) => string;
|
|
560
|
+
export declare const renderForOpener: ({ alias, filters, path, prefix }: ForOpenerOptions) => string;
|
|
549
561
|
//#endregion
|
|
550
562
|
//#region src/row-blocks.d.ts
|
|
551
563
|
/** One end of a row block, addressed within the row that was scanned. */
|
|
@@ -573,10 +585,10 @@ type RowBlockPair = {
|
|
|
573
585
|
* `{% else %}`, a pair wrapping the paragraphs of a single cell — is left to the
|
|
574
586
|
* engine's existing structure errors rather than guessed at.
|
|
575
587
|
*/
|
|
576
|
-
declare const detectRowBlockPair: (cells: readonly (readonly string[])[]) => RowBlockPair | null;
|
|
588
|
+
export declare const detectRowBlockPair: (cells: readonly (readonly string[])[]) => RowBlockPair | null;
|
|
577
589
|
//#endregion
|
|
578
590
|
//#region src/index.d.ts
|
|
579
|
-
type NamedCondition = {
|
|
591
|
+
export type NamedCondition = {
|
|
580
592
|
name: string;
|
|
581
593
|
/** The `{% if %}` expression string. Authoritative unless `node` is set. */
|
|
582
594
|
expression: string;
|
|
@@ -594,7 +606,7 @@ type NamedCondition = {
|
|
|
594
606
|
* Prevents stack overflow when a long chain of named
|
|
595
607
|
* conditions (A→B→C→...→Z) is evaluated without cycles.
|
|
596
608
|
*/
|
|
597
|
-
declare const MAX_CONDITION_DEPTH = 50;
|
|
609
|
+
export declare const MAX_CONDITION_DEPTH = 50;
|
|
598
610
|
/**
|
|
599
611
|
* Evaluate a template `{% if ... %}` condition. The expression is parsed into
|
|
600
612
|
* the canonical `@stll/conditions` AST and evaluated by the shared evaluator.
|
|
@@ -607,6 +619,6 @@ declare const MAX_CONDITION_DEPTH = 50;
|
|
|
607
619
|
* An empty or unparseable expression is `false` ("no condition" never gates a
|
|
608
620
|
* branch true).
|
|
609
621
|
*/
|
|
610
|
-
declare const evaluateCondition: (expression: string, data: Record<string, unknown>, namedConditions?: readonly NamedCondition[], _resolved?: ReadonlySet<string>, _depth?: number) => boolean;
|
|
622
|
+
export declare const evaluateCondition: (expression: string, data: Record<string, unknown>, namedConditions?: readonly NamedCondition[], _resolved?: ReadonlySet<string>, _depth?: number) => boolean;
|
|
611
623
|
//#endregion
|
|
612
|
-
export {
|
|
624
|
+
export { type BlockDirectiveKind, type CompareNode, type CompareOp, type Condition, type ConditionNode, type ConditionTagOptions, type DateFormatStyle, type DeterministicFieldConfig, type DirectiveKind, type FieldDateFormat, type FilterArgument, type FilterCall, type FilterName, type ForOpenerOptions, type GroupNode, type InvalidMarker, type LoopProperty, type MarkerDefect, type MarkerDefectKind, type MarkerFieldConfig, type MarkerFieldInputType, type MarkerFieldLookup, type MarkerFieldSource, type MarkerFieldValidation, type MarkerForm, type MarkerLiteral, type MarkerMeta, type MarkerPrefix, type NumericFunctionName, type Operand, type PredicateNode, type PredicateOp, type RowBlockMarker, type RowBlockPair, type RowScope, type ScannedMarker, type UnwritableReason, conditionHasFormula, conditionNodeSchema, conditionSchema, emptyCondition };
|
package/dist/index.js
CHANGED
|
@@ -945,6 +945,32 @@ const readPrefix = (token) => token === "p" || token === "tr" ? PREFIX_BY_TOKEN[
|
|
|
945
945
|
const TAG_RE = /^(?<tag>[\p{L}_][\p{L}\p{N}_]*)\b(?<rest>[\s\S]*)$/u;
|
|
946
946
|
const FOR_RE = /^(?<alias>[\p{L}_][\p{L}\p{N}_-]*)\s+in\s+(?<path>[\p{L}\p{N}_.-]+)\s*(?<filters>\|[\s\S]*)?$/u;
|
|
947
947
|
const CALL_RE = /^(?<name>[\p{L}_][\p{L}\p{N}_]*)\s*\(/u;
|
|
948
|
+
const CONDITION_RE = /^(?<path>[\p{L}\p{N}_.-]+)\s*(?<filters>\|[\s\S]*)$/u;
|
|
949
|
+
/**
|
|
950
|
+
* Read an `{% if %}` / `{% elif %}` body: one field path plus the chain that
|
|
951
|
+
* configures the boolean it names, or an expression the author wrote.
|
|
952
|
+
*
|
|
953
|
+
* A tail the filter catalogue does not read is part of that expression
|
|
954
|
+
* (`items|length > 0`), never a configuration, so the body stays verbatim
|
|
955
|
+
* rather than being rejected: a tag the grammar refuses would take its whole
|
|
956
|
+
* block with it.
|
|
957
|
+
*/
|
|
958
|
+
const readConditionBody = (rest) => {
|
|
959
|
+
const match = CONDITION_RE.exec(rest);
|
|
960
|
+
if (!match) return {
|
|
961
|
+
expr: rest,
|
|
962
|
+
filters: []
|
|
963
|
+
};
|
|
964
|
+
const chain = scanFilterChain(match.groups?.["filters"] ?? "");
|
|
965
|
+
if (chain === null || "unknownFilter" in chain || chain.filters.length === 0) return {
|
|
966
|
+
expr: rest,
|
|
967
|
+
filters: []
|
|
968
|
+
};
|
|
969
|
+
return {
|
|
970
|
+
expr: match.groups?.["path"] ?? rest,
|
|
971
|
+
filters: chain.filters
|
|
972
|
+
};
|
|
973
|
+
};
|
|
948
974
|
/** Classify a `{% ... %}` tag. */
|
|
949
975
|
const classifyStatement = (inner) => {
|
|
950
976
|
const match = TAG_RE.exec(inner);
|
|
@@ -952,14 +978,22 @@ const classifyStatement = (inner) => {
|
|
|
952
978
|
const tag = match.groups?.["tag"] ?? "";
|
|
953
979
|
const rest = (match.groups?.["rest"] ?? "").trim();
|
|
954
980
|
switch (tag) {
|
|
955
|
-
case "if":
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
}
|
|
981
|
+
case "if": {
|
|
982
|
+
const { expr, filters } = readConditionBody(rest);
|
|
983
|
+
return {
|
|
984
|
+
kind: "if",
|
|
985
|
+
expr,
|
|
986
|
+
filters
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
case "elif": {
|
|
990
|
+
const { expr, filters } = readConditionBody(rest);
|
|
991
|
+
return {
|
|
992
|
+
kind: "elif",
|
|
993
|
+
expr,
|
|
994
|
+
filters
|
|
995
|
+
};
|
|
996
|
+
}
|
|
963
997
|
case "else": return rest === "" ? { kind: "else" } : null;
|
|
964
998
|
case "endif": return rest === "" ? { kind: "endif" } : null;
|
|
965
999
|
case "endfor": return rest === "" ? { kind: "endfor" } : null;
|
|
@@ -1466,13 +1500,20 @@ const PREFIX_TOKEN = {
|
|
|
1466
1500
|
row: "tr"
|
|
1467
1501
|
};
|
|
1468
1502
|
/**
|
|
1469
|
-
* One `{% if %}` / `{% elif %}` opener carrying its expression
|
|
1503
|
+
* One `{% if %}` / `{% elif %}` opener carrying its expression, and the field
|
|
1504
|
+
* configuration of the boolean it asks for.
|
|
1470
1505
|
*
|
|
1471
1506
|
* A rule that decides whether a block shows belongs in the tag that shows it:
|
|
1472
1507
|
* the tag is the only place a document has for it, so a builder's rule is
|
|
1473
|
-
* serialized here rather than stored beside the document under a name.
|
|
1508
|
+
* serialized here rather than stored beside the document under a name. The
|
|
1509
|
+
* same is true of what to call the question and how to decide it: a condition
|
|
1510
|
+
* the document never prints has no value marker to hold that chain either.
|
|
1474
1511
|
*/
|
|
1475
|
-
const renderConditionTag = ({ expression, kind, prefix }) =>
|
|
1512
|
+
const renderConditionTag = ({ expression, filters, kind, prefix }) => {
|
|
1513
|
+
const chain = renderFilterChain(filters);
|
|
1514
|
+
const head = `{%${PREFIX_TOKEN[prefix]} ${kind} ${expression}`;
|
|
1515
|
+
return chain === "" ? `${head} %}` : `${head} | ${chain} %}`;
|
|
1516
|
+
};
|
|
1476
1517
|
/** One `{% for %}` opener, keeping its placement prefix. */
|
|
1477
1518
|
const renderForOpener = ({ alias, filters, path, prefix }) => {
|
|
1478
1519
|
const chain = renderFilterChain(filters);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/template-conditions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A document-template marker and condition engine: {{...}} marker grammar, parse, compute, serialize, field values.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"condition",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"prepack": "bun run build"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@stll/conditions": "^0.
|
|
52
|
+
"@stll/conditions": "^0.4.0",
|
|
53
53
|
"better-result": "3.0.1",
|
|
54
54
|
"temporal-polyfill": "1.0.4"
|
|
55
55
|
},
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@stll/typescript-config": "0.0.0",
|
|
59
59
|
"bun-types": "1.4.2",
|
|
60
60
|
"fast-check": "^4.9.0",
|
|
61
|
-
"tsdown": "0.
|
|
61
|
+
"tsdown": "0.23.0"
|
|
62
62
|
},
|
|
63
63
|
"main": "./dist/index.js",
|
|
64
64
|
"types": "./dist/index.d.ts"
|