@stll/template-conditions 0.3.0 → 0.5.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/README.md +1 -1
- package/dist/index.d.ts +361 -93
- package/dist/index.js +958 -253
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ functions usable on both backend (Bun) and frontend (browser).
|
|
|
7
7
|
|
|
8
8
|
Boolean conditions are evaluated through the canonical
|
|
9
9
|
[`@stll/conditions`](https://github.com/stella/stella/tree/main/packages/conditions)
|
|
10
|
-
AST and its single evaluator, so template `{
|
|
10
|
+
AST and its single evaluator, so template `{% if ... %}` conditionals share one
|
|
11
11
|
set of operators and semantics with the rest of the system. This package owns
|
|
12
12
|
the template surface: the marker grammar, the string parser, the named-condition
|
|
13
13
|
resolver, the numeric expression evaluator, and the no-code builder serializer.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,22 @@ declare const resolvePath: (path: string, data: Record<string, unknown>) => unkn
|
|
|
6
6
|
//#region src/parse.d.ts
|
|
7
7
|
declare const parseCondition: (expression: string) => ConditionNode$1 | null;
|
|
8
8
|
//#endregion
|
|
9
|
+
//#region src/referenced-paths.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* What a `{% if %}` expression reads. Structural, not evaluative: the paths a
|
|
12
|
+
* condition depends on, for callers that decide something about the condition
|
|
13
|
+
* itself rather than about a document being filled.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Every field path an expression reads, in source order.
|
|
17
|
+
*
|
|
18
|
+
* `null` says the expression does not answer the question: it did not parse,
|
|
19
|
+
* or it reads an operand whose own references cannot be enumerated. A caller
|
|
20
|
+
* deciding what a condition depends on must not read that as "depends on
|
|
21
|
+
* nothing".
|
|
22
|
+
*/
|
|
23
|
+
declare const referencedConditionPaths: (expression: string) => string[] | null;
|
|
24
|
+
//#endregion
|
|
9
25
|
//#region src/compute.d.ts
|
|
10
26
|
/**
|
|
11
27
|
* Arithmetic evaluator for computed template fields.
|
|
@@ -31,8 +47,8 @@ declare const evaluateNumericExpression: (expression: string, data: Record<strin
|
|
|
31
47
|
//#region src/field-values.d.ts
|
|
32
48
|
/**
|
|
33
49
|
* Single source of truth for the DETERMINISTIC field-value transforms a
|
|
34
|
-
* template fill applies
|
|
35
|
-
*
|
|
50
|
+
* template fill applies: formula (arithmetic over the other values) and
|
|
51
|
+
* locale-aware date rendering.
|
|
36
52
|
*
|
|
37
53
|
* Both the API fill engine (apps/api/src/handlers/docx) and the web live
|
|
38
54
|
* preview (template-studio.tsx) MUST route their rendering through
|
|
@@ -67,18 +83,6 @@ type FieldDateFormat = {
|
|
|
67
83
|
* returns null.
|
|
68
84
|
*/
|
|
69
85
|
declare const formatDate: (value: string, dateFormat: FieldDateFormat) => string | null;
|
|
70
|
-
/** Minimal structural shape of one composite field part: only the `key`
|
|
71
|
-
* referenced by the join format is needed to render. Callers' richer part
|
|
72
|
-
* types (api FieldPart, web EditablePart) structurally satisfy this. */
|
|
73
|
-
type PartConfig = {
|
|
74
|
-
key: string;
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* Render a `{{key}}` format over part values. Markers whose key has no part
|
|
78
|
-
* value are left as-is (a visible authoring artifact, not a render error), so
|
|
79
|
-
* a partially filled composite previews exactly as the fill engine renders it.
|
|
80
|
-
*/
|
|
81
|
-
declare const renderComposite: (parts: readonly PartConfig[], format: string, partValues: Readonly<Record<string, string>>) => string;
|
|
82
86
|
/**
|
|
83
87
|
* Minimal STRUCTURAL config of a field, carrying only what the deterministic
|
|
84
88
|
* transforms read. Callers (api FieldMeta, web StudioField) structurally
|
|
@@ -88,8 +92,6 @@ declare const renderComposite: (parts: readonly PartConfig[], format: string, pa
|
|
|
88
92
|
type DeterministicFieldConfig = {
|
|
89
93
|
path: string;
|
|
90
94
|
inputType?: string | undefined;
|
|
91
|
-
parts?: readonly PartConfig[] | undefined;
|
|
92
|
-
format?: string | undefined;
|
|
93
95
|
formula?: string | undefined;
|
|
94
96
|
dateFormat?: FieldDateFormat | undefined;
|
|
95
97
|
};
|
|
@@ -99,16 +101,13 @@ type DeterministicFieldConfig = {
|
|
|
99
101
|
* no deterministic transform (a scalar, lookup, or AI field the CALLER renders
|
|
100
102
|
* itself).
|
|
101
103
|
*
|
|
102
|
-
* Dispatch order mirrors the API fill pipeline (
|
|
103
|
-
* -
|
|
104
|
-
* - else formula present → {@link evaluateNumericExpression}, stringified
|
|
104
|
+
* Dispatch order mirrors the API fill pipeline (formula → date):
|
|
105
|
+
* - formula present → {@link evaluateNumericExpression}, stringified
|
|
105
106
|
* - else date (inputType "date" + dateFormat) → {@link formatDate}
|
|
106
107
|
* - else null
|
|
107
108
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* scalar path handles it). Formula and date return null when the expression /
|
|
111
|
-
* value does not yield a value, so the caller leaves the field as-is.
|
|
109
|
+
* Formula and date return null when the expression or value does not yield a
|
|
110
|
+
* value, so the caller leaves the field as-is.
|
|
112
111
|
*/
|
|
113
112
|
declare const renderDeterministicFieldValue: (field: DeterministicFieldConfig, values: Record<string, unknown>) => string | null;
|
|
114
113
|
//#endregion
|
|
@@ -122,22 +121,47 @@ declare const serializeCondition: (node: ConditionNode$1) => string;
|
|
|
122
121
|
//#endregion
|
|
123
122
|
//#region src/markers.d.ts
|
|
124
123
|
/**
|
|
125
|
-
* Canonical grammar for
|
|
126
|
-
* shared by the fill pipeline (api), the editor highlighter (folio), and
|
|
127
|
-
* read-only preview (web).
|
|
124
|
+
* Canonical grammar for the docxtpl dialect of Jinja — the single source of
|
|
125
|
+
* truth shared by the fill pipeline (api), the editor highlighter (folio), and
|
|
126
|
+
* the read-only preview (web).
|
|
127
|
+
*
|
|
128
|
+
* Two spans carry meaning: an output marker `{{ ... }}` prints a value, and a
|
|
129
|
+
* tag `{% ... %}` opens or closes a block. A tag may carry a docxtpl placement
|
|
130
|
+
* prefix — `{%p ... %}` owns its paragraph, `{%tr ... %}` owns its table row —
|
|
131
|
+
* and a bare tag is placed by the leniency rules the consumer applies.
|
|
128
132
|
*
|
|
129
133
|
* Add a new directive to `MarkerMeta` and teach {@link classifyMarker} to
|
|
130
134
|
* recognize it. Compile-time totality checks then require its ordered runtime
|
|
131
135
|
* value and its block/inline disposition, while exhaustive consumer switches
|
|
132
136
|
* fail until they handle it.
|
|
133
137
|
*
|
|
134
|
-
* No consumer should define its own
|
|
138
|
+
* No consumer should define its own marker regex. The low-level pattern
|
|
135
139
|
* factories below exist so handlers that mutate raw XML/text reuse the exact
|
|
136
140
|
* same literals; recognizers should prefer the high-level scanner.
|
|
137
141
|
*/
|
|
142
|
+
/** A literal an author can write inside a filter or function call. */
|
|
143
|
+
type MarkerLiteral = string | number | boolean;
|
|
144
|
+
/** One argument of a filter call: positional, or `name=value`. */
|
|
145
|
+
type FilterArgument = {
|
|
146
|
+
kind: "positional";
|
|
147
|
+
value: MarkerLiteral;
|
|
148
|
+
} | {
|
|
149
|
+
kind: "keyword";
|
|
150
|
+
name: string;
|
|
151
|
+
value: MarkerLiteral;
|
|
152
|
+
};
|
|
153
|
+
/** One `| name` / `| name(args)` step of an output marker's filter chain. */
|
|
154
|
+
type FilterCall = {
|
|
155
|
+
name: FilterName;
|
|
156
|
+
args: readonly FilterArgument[];
|
|
157
|
+
};
|
|
158
|
+
/** The loop properties addressable inside a `{% for %}` body. */
|
|
159
|
+
declare const LOOP_PROPERTIES: readonly ["index", "index0", "first", "last", "length"];
|
|
160
|
+
type LoopProperty = (typeof LOOP_PROPERTIES)[number];
|
|
138
161
|
type MarkerMeta = {
|
|
139
162
|
kind: "placeholder";
|
|
140
163
|
expr: string;
|
|
164
|
+
filters: readonly FilterCall[];
|
|
141
165
|
} | {
|
|
142
166
|
kind: "clause";
|
|
143
167
|
name: string;
|
|
@@ -149,134 +173,377 @@ type MarkerMeta = {
|
|
|
149
173
|
kind: "ref";
|
|
150
174
|
key: string;
|
|
151
175
|
} | {
|
|
152
|
-
kind: "
|
|
153
|
-
|
|
154
|
-
kind: "count";
|
|
176
|
+
kind: "loop";
|
|
177
|
+
property: LoopProperty;
|
|
155
178
|
} | {
|
|
156
179
|
kind: "if";
|
|
157
180
|
expr: string;
|
|
158
181
|
} | {
|
|
159
|
-
kind: "
|
|
182
|
+
kind: "elif";
|
|
160
183
|
expr: string;
|
|
161
184
|
} | {
|
|
162
185
|
kind: "else";
|
|
163
186
|
} | {
|
|
164
187
|
kind: "endif";
|
|
165
188
|
} | {
|
|
166
|
-
kind: "
|
|
167
|
-
|
|
189
|
+
kind: "for";
|
|
190
|
+
alias: string;
|
|
191
|
+
path: string;
|
|
192
|
+
/** Filters on the loop path configure the ARRAY: how many rows it takes
|
|
193
|
+
* and what to call it. */
|
|
194
|
+
filters: readonly FilterCall[];
|
|
168
195
|
} | {
|
|
169
|
-
kind: "
|
|
196
|
+
kind: "endfor";
|
|
170
197
|
};
|
|
171
198
|
type DirectiveKind = MarkerMeta["kind"];
|
|
172
|
-
declare const DIRECTIVE_KINDS: readonly ["placeholder", "clause", "num", "ref", "
|
|
199
|
+
declare const DIRECTIVE_KINDS: readonly ["placeholder", "clause", "num", "ref", "loop", "if", "elif", "else", "endif", "for", "endfor"];
|
|
173
200
|
declare const DIRECTIVE_PLACEMENT: {
|
|
174
201
|
readonly placeholder: "inline";
|
|
175
202
|
readonly clause: "inline";
|
|
176
203
|
readonly num: "inline";
|
|
177
204
|
readonly ref: "inline";
|
|
178
|
-
readonly
|
|
179
|
-
readonly count: "inline";
|
|
205
|
+
readonly loop: "inline";
|
|
180
206
|
readonly if: "block";
|
|
181
|
-
readonly
|
|
207
|
+
readonly elif: "block";
|
|
182
208
|
readonly else: "block";
|
|
183
209
|
readonly endif: "block";
|
|
184
|
-
readonly
|
|
185
|
-
readonly
|
|
210
|
+
readonly for: "block";
|
|
211
|
+
readonly endfor: "block";
|
|
186
212
|
};
|
|
187
213
|
type BlockDirectiveKind = { [TKind in DirectiveKind]: (typeof DIRECTIVE_PLACEMENT)[TKind] extends "block" ? TKind : never; }[DirectiveKind];
|
|
188
214
|
/** Directives that occupy their own paragraph, in canonical grammar order. */
|
|
189
|
-
declare const BLOCK_DIRECTIVE_KINDS: readonly ["if", "
|
|
215
|
+
declare const BLOCK_DIRECTIVE_KINDS: readonly ["if", "elif", "else", "endif", "for", "endfor"];
|
|
190
216
|
declare const isBlockDirectiveKind: (kind: unknown) => kind is BlockDirectiveKind;
|
|
191
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Every filter the dialect accepts on a fillable value. The names are the
|
|
219
|
+
* grammar's; which manifest property each one writes is decided by the field
|
|
220
|
+
* catalogue in the api, which is total over BOTH this list and the manifest
|
|
221
|
+
* keys, so neither side can gain an entry the other has not decided.
|
|
222
|
+
*/
|
|
223
|
+
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"];
|
|
224
|
+
type FilterName = (typeof FILTER_NAMES)[number];
|
|
225
|
+
/** Normalise one marker's inner text: typographic quotes become straight
|
|
226
|
+
* quotes, fixed-width spaces become ordinary ones. */
|
|
227
|
+
declare const normalizeMarkerInner: (inner: string) => string;
|
|
228
|
+
/**
|
|
229
|
+
* What an output marker holds between its braces: anything but a brace or a
|
|
230
|
+
* double quote, plus whole double-quoted arguments, whose braces are content
|
|
231
|
+
* rather than the marker's own delimiters — `pattern("^[0-9]{5}$")` is a regex,
|
|
232
|
+
* not a nested marker. An apostrophe stays an ordinary character, so
|
|
233
|
+
* `label("Owner's")` reads as it always did.
|
|
234
|
+
*
|
|
235
|
+
* A quoted run honours `\`-escapes, the way the argument scanner reads them,
|
|
236
|
+
* so `label("she said \"yes\"")` is one span rather than three: the span and
|
|
237
|
+
* the scanner have to agree on where a literal ends or a marker the scanner
|
|
238
|
+
* would parse is one the recognizer never sees.
|
|
239
|
+
*
|
|
240
|
+
* The alternatives cannot overlap (the plain one excludes every character the
|
|
241
|
+
* quoted one may start with, and inside a quoted run the escape branch is the
|
|
242
|
+
* only one that starts with a backslash), so the scan stays linear on
|
|
243
|
+
* adversarial input: a `{{` with no closing `}}` costs one failed pass, not a
|
|
244
|
+
* polynomial retry.
|
|
245
|
+
*/
|
|
246
|
+
declare const MARKER_OUTPUT_BODY: string;
|
|
247
|
+
/** What a tag holds between `{%` and `%}`: anything but a brace, and a `%`
|
|
248
|
+
* only where it does not close the tag. */
|
|
249
|
+
declare const MARKER_STATEMENT_BODY: string;
|
|
250
|
+
/**
|
|
251
|
+
* Any marker span: an output `{{...}}` (group `output`) or a tag `{%...%}`
|
|
252
|
+
* (group `statement`, with the docxtpl placement in group `prefix`).
|
|
253
|
+
*/
|
|
192
254
|
declare const markerPattern: () => RegExp;
|
|
193
|
-
/** Inline
|
|
255
|
+
/** Inline value marker, including its filter chain; group `name` is the path
|
|
256
|
+
* the value comes from and group `filters` the untouched chain. */
|
|
194
257
|
declare const placeholderPattern: () => RegExp;
|
|
195
|
-
/** `{{
|
|
258
|
+
/** `{{ clause("Name") }}` / `{{ clause("Name", "v3") }}`. */
|
|
196
259
|
declare const clauseSlotPattern: () => RegExp;
|
|
197
|
-
/** `{{
|
|
260
|
+
/** `{{ num("key") }}` — the `key` group. */
|
|
198
261
|
declare const numPattern: () => RegExp;
|
|
199
|
-
/** `{{
|
|
262
|
+
/** `{{ ref("key") }}` — the `key` group. */
|
|
200
263
|
declare const refPattern: () => RegExp;
|
|
201
|
-
/** `{{
|
|
202
|
-
declare const
|
|
203
|
-
/** `{{@count}}` — the item count of the innermost enclosing loop. */
|
|
204
|
-
declare const countPattern: () => RegExp;
|
|
264
|
+
/** `{{ loop.index }}` and its siblings — the `property` group. */
|
|
265
|
+
declare const loopPattern: () => RegExp;
|
|
205
266
|
/** Cheap presence test for any numbering marker (no capture). */
|
|
206
267
|
declare const hasNumberingPattern: () => RegExp;
|
|
207
|
-
/** Cheap presence test for any
|
|
268
|
+
/** Cheap presence test for any tag (no capture). */
|
|
208
269
|
declare const hasBlockDirectivePattern: () => RegExp;
|
|
209
|
-
/** A whole line that is a single block
|
|
270
|
+
/** A whole line that is a single block tag — `prefix`, `tag` and `expr`. */
|
|
210
271
|
declare const blockDirectiveLinePattern: () => RegExp;
|
|
211
272
|
/** Whether `value` is a valid field path per the marker grammar (dotted
|
|
212
273
|
* segments of letters/digits/underscore/dash — no brackets or spaces). */
|
|
213
274
|
declare const isFieldPath: (value: string) => boolean;
|
|
214
275
|
/** Whether `value` is safe to use as a dotted object path. */
|
|
215
276
|
declare const isSafeFieldPath: (value: string) => boolean;
|
|
216
|
-
/** Whether `value` is a valid name for authoring a `
|
|
217
|
-
* the intersection of the clause-slot parse grammar and the fill pipeline's
|
|
218
|
-
* placeholder charset, so every accepted name round-trips through fill. */
|
|
277
|
+
/** Whether `value` is a valid name for authoring a `clause("NAME")` slot. */
|
|
219
278
|
declare const isClauseSlotName: (value: string) => boolean;
|
|
279
|
+
/** Which brace pair carried the marker text. */
|
|
280
|
+
type MarkerForm = "output" | "statement";
|
|
281
|
+
/** Where a tag's docxtpl prefix says the tag belongs. `"none"` is a bare tag,
|
|
282
|
+
* placed by the consumer's leniency rules, and every output marker. */
|
|
283
|
+
type MarkerPrefix = "none" | "paragraph" | "row";
|
|
220
284
|
/**
|
|
221
|
-
* Classify the inner text of
|
|
222
|
-
*
|
|
285
|
+
* Classify the inner text of one marker. Returns `null` when the text is not a
|
|
286
|
+
* recognized directive (so a recognizer can leave stray braces alone).
|
|
223
287
|
*/
|
|
224
|
-
declare const classifyMarker: (innerRaw: string) => MarkerMeta | null;
|
|
288
|
+
declare const classifyMarker: (innerRaw: string, form?: MarkerForm) => MarkerMeta | null;
|
|
225
289
|
type ScannedMarker = {
|
|
226
|
-
/** Offset of
|
|
290
|
+
/** Offset of the opening brace pair in the source text. */
|
|
227
291
|
start: number;
|
|
228
|
-
/** Offset just past
|
|
292
|
+
/** Offset just past the closing brace pair. */
|
|
229
293
|
end: number;
|
|
230
|
-
/** The full matched marker, e.g. `{
|
|
294
|
+
/** The full matched marker, e.g. `{% endfor %}`. */
|
|
231
295
|
raw: string;
|
|
232
|
-
/** The
|
|
296
|
+
/** The normalized, trimmed inner text. */
|
|
233
297
|
inner: string;
|
|
298
|
+
form: MarkerForm;
|
|
299
|
+
/** The docxtpl placement prefix the author wrote. */
|
|
300
|
+
prefix: MarkerPrefix;
|
|
234
301
|
meta: MarkerMeta;
|
|
235
302
|
};
|
|
236
303
|
/**
|
|
237
|
-
* Find every recognized
|
|
238
|
-
*
|
|
304
|
+
* Find every recognized marker in `text`, in document order, with offsets and
|
|
305
|
+
* classified metadata. Unrecognized spans are skipped.
|
|
239
306
|
*/
|
|
240
307
|
declare const scanMarkers: (text: string) => ScannedMarker[];
|
|
241
|
-
/**
|
|
308
|
+
/**
|
|
309
|
+
* Rewrite every `{{ ... }}` output marker in `text` through `replace`, which
|
|
310
|
+
* receives the whole marker and its raw inner text and returns the replacement.
|
|
311
|
+
* Tags are left untouched. The single scan is the same one the classifier uses,
|
|
312
|
+
* so a caller that rewrites marker text never needs its own regex.
|
|
313
|
+
*/
|
|
314
|
+
declare const replaceOutputMarkers: (text: string, replace: (raw: string, inner: string) => string) => string;
|
|
315
|
+
/**
|
|
316
|
+
* The values-map key one marker substitutes from, or `null` when the marker is
|
|
317
|
+
* not a substitution target (a tag, or a `loop.*` token the loop expander
|
|
318
|
+
* resolves first). One function so the key discovery writes is the key the
|
|
319
|
+
* patcher reads.
|
|
320
|
+
*/
|
|
321
|
+
declare const substitutionKey: (meta: MarkerMeta) => string | null;
|
|
322
|
+
/** The values-map key for one clause slot. */
|
|
323
|
+
declare const clauseSlotKey: (name: string, version?: string) => string;
|
|
324
|
+
/** A marker-shaped span that classifies to nothing. */
|
|
242
325
|
type InvalidMarker = {
|
|
243
|
-
/** Offset of
|
|
326
|
+
/** Offset of the opening brace pair in the source text. */
|
|
244
327
|
start: number;
|
|
245
|
-
/** Offset just past
|
|
328
|
+
/** Offset just past the closing brace pair. */
|
|
246
329
|
end: number;
|
|
247
330
|
/** The full matched span, e.g. `{{my field}}`. */
|
|
248
331
|
raw: string;
|
|
249
|
-
/** The
|
|
332
|
+
/** The normalized, trimmed inner text. */
|
|
250
333
|
inner: string;
|
|
334
|
+
form: MarkerForm;
|
|
251
335
|
};
|
|
252
336
|
/**
|
|
253
|
-
* Find every
|
|
254
|
-
* near-misses an author clearly meant as markers but that every
|
|
255
|
-
* skips, so they print literally at fill time
|
|
256
|
-
* space, `{{@clause:}}` with no name). Offsets and raw text are shaped like
|
|
257
|
-
* {@link scanMarkers} results, minus the classified `meta`. This is the exact
|
|
337
|
+
* Find every marker-shaped span whose inner text is NOT a recognized
|
|
338
|
+
* directive — near-misses an author clearly meant as markers but that every
|
|
339
|
+
* recognizer skips, so they print literally at fill time. This is the exact
|
|
258
340
|
* complement of {@link scanMarkers}: a span is in one list or the other.
|
|
259
341
|
*/
|
|
260
342
|
declare const scanInvalidMarkers: (text: string) => InvalidMarker[];
|
|
343
|
+
/** The loop alias the codemod (and the legacy-marker message) generates for a
|
|
344
|
+
* `{{#each path}}`: the singular of the last plain-word segment, else `item`. */
|
|
345
|
+
declare const legacyLoopAlias: (path: string) => string;
|
|
346
|
+
/** Translate an old-dialect condition expression to the Jinja surface:
|
|
347
|
+
* `!a` becomes `not a`, `a contains "x"` becomes `"x" in a`. */
|
|
348
|
+
declare const translateLegacyExpression: (expr: string) => string;
|
|
261
349
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
350
|
+
* The Jinja marker that replaces one old-dialect marker's inner text, or
|
|
351
|
+
* `null` when the text is not old-dialect. Shared by the rejection message and
|
|
352
|
+
* the codemod, so the fix an author is told to make is the fix the script
|
|
353
|
+
* would have made.
|
|
265
354
|
*/
|
|
266
|
-
|
|
267
|
-
declare const MARKER_DEFECT_KINDS: readonly ["unknown_directive", "bracket_index"];
|
|
355
|
+
declare const legacyMarkerReplacement: (innerRaw: string) => string | null;
|
|
268
356
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* authority on which `{{#...}}` tokens exist: adding a directive to the grammar
|
|
274
|
-
* stops it being reported here, with no list to update.
|
|
357
|
+
* Why a marker-shaped span misses the grammar in a way that names the
|
|
358
|
+
* authoring mistake rather than merely failing to classify. Consumers derive
|
|
359
|
+
* their own warning codes from {@link MARKER_DEFECT_KINDS} so the two never
|
|
360
|
+
* drift.
|
|
275
361
|
*/
|
|
276
|
-
|
|
362
|
+
type MarkerDefectKind = "legacy_marker" | "unsupported_tag" | "unknown_filter" | "python_expression" | "bracket_index";
|
|
363
|
+
declare const MARKER_DEFECT_KINDS: readonly ["legacy_marker", "unsupported_tag", "unknown_filter", "python_expression", "bracket_index"];
|
|
364
|
+
/** One diagnosis of a rejected span: the kind plus the construct to quote. */
|
|
365
|
+
type MarkerDefect = {
|
|
366
|
+
kind: MarkerDefectKind;
|
|
367
|
+
/** The construct the author wrote that the grammar refuses. */
|
|
368
|
+
construct: string;
|
|
369
|
+
/** The Jinja that replaces it, when the mistake has one exact fix. */
|
|
370
|
+
replacement?: string;
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Name the authoring mistake behind one marker's inner text, or `null` when
|
|
374
|
+
* the text is a recognized directive ({@link classifyMarker} accepts it) or an
|
|
375
|
+
* unrecognizable span with no specific diagnosis (`{{my field}}`).
|
|
376
|
+
*/
|
|
377
|
+
declare const classifyMarkerDefect: (innerRaw: string, form?: MarkerForm) => MarkerDefect | null;
|
|
277
378
|
/** Exhaustiveness guard — pass the discriminant in a `switch` default branch. */
|
|
278
379
|
declare const assertNever: (value: never) => never;
|
|
279
380
|
//#endregion
|
|
381
|
+
//#region src/loop-scope.d.ts
|
|
382
|
+
/**
|
|
383
|
+
* The path a marker names, once the loops around it are taken into account.
|
|
384
|
+
*
|
|
385
|
+
* `{% for attorney in attorneys %}{{ attorney.name }}{% endfor %}` writes one
|
|
386
|
+
* field, and the manifest calls it `attorneys.name`: the loop's alias is the
|
|
387
|
+
* body's name for the item, the array path is the manifest's. Discovery reads
|
|
388
|
+
* that mapping, and every writer that puts a configuration back into a marker
|
|
389
|
+
* has to reverse it, so the rule lives beside the grammar rather than in each
|
|
390
|
+
* of them; two readings of one loop are two different documents.
|
|
391
|
+
*/
|
|
392
|
+
/** One enclosing `{% for %}`, as the body sees it and as the manifest does. */
|
|
393
|
+
type RowScope = {
|
|
394
|
+
/** The loop variable the body addresses items through. */
|
|
395
|
+
alias: string;
|
|
396
|
+
/** The array path the opener was written with, before its own enclosing
|
|
397
|
+
* loops were applied. */
|
|
398
|
+
declaredPath: string;
|
|
399
|
+
/** The array path the manifest speaks, enclosing loops included. */
|
|
400
|
+
scopedPath: string;
|
|
401
|
+
};
|
|
402
|
+
declare const rowScopePaths: (rowScopes: readonly RowScope[]) => string[];
|
|
403
|
+
/** A path already under the innermost repeat is left alone; anything else is
|
|
404
|
+
* read as a name relative to it. */
|
|
405
|
+
declare const qualifyRowScopedPath: (path: string, rowPaths?: readonly string[]) => string;
|
|
406
|
+
/** The manifest path for a marker written inside these loops. */
|
|
407
|
+
declare const qualifyRowScopedPlaceholder: (path: string, rowScopes: readonly RowScope[]) => string;
|
|
408
|
+
/** The manifest path for an array a `{% for %}` opens inside these loops. */
|
|
409
|
+
declare const qualifyLoopPath: (declaredPath: string, rowScopes: readonly RowScope[]) => string;
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/field-config.d.ts
|
|
412
|
+
/** Where a field's value comes from, when a record supplies it. Only the shape
|
|
413
|
+
* the filters read: each caller's own union satisfies it. */
|
|
414
|
+
type MarkerFieldSource = {
|
|
415
|
+
kind: "matter";
|
|
416
|
+
field: string;
|
|
417
|
+
} | {
|
|
418
|
+
kind: "contact";
|
|
419
|
+
field: string;
|
|
420
|
+
} | {
|
|
421
|
+
kind: "firm";
|
|
422
|
+
field: string;
|
|
423
|
+
} | {
|
|
424
|
+
kind: "party";
|
|
425
|
+
role: string;
|
|
426
|
+
field: string;
|
|
427
|
+
} | {
|
|
428
|
+
kind: "attorney";
|
|
429
|
+
ref: string;
|
|
430
|
+
field: string;
|
|
431
|
+
};
|
|
432
|
+
/** The constraints a chain can express. */
|
|
433
|
+
type MarkerFieldValidation = {
|
|
434
|
+
required?: boolean | undefined;
|
|
435
|
+
pattern?: string | undefined;
|
|
436
|
+
min?: number | undefined;
|
|
437
|
+
max?: number | undefined;
|
|
438
|
+
minLength?: number | undefined;
|
|
439
|
+
maxLength?: number | undefined;
|
|
440
|
+
minItems?: number | undefined;
|
|
441
|
+
maxItems?: number | undefined;
|
|
442
|
+
};
|
|
443
|
+
/** A registry lookup and the renderings of its one resolved hit. */
|
|
444
|
+
type MarkerFieldLookup = {
|
|
445
|
+
registry: string;
|
|
446
|
+
formats: readonly {
|
|
447
|
+
key: string;
|
|
448
|
+
template: string;
|
|
449
|
+
}[];
|
|
450
|
+
};
|
|
451
|
+
/** The input controls a marker can name. */
|
|
452
|
+
type MarkerFieldInputType = "text" | "number" | "boolean" | "date" | "select";
|
|
453
|
+
/**
|
|
454
|
+
* Everything about a field that a marker's filter chain can carry. A caller's
|
|
455
|
+
* own field type may hold more (a derived condition AST, a discovery count);
|
|
456
|
+
* those keys are simply not read here.
|
|
457
|
+
*/
|
|
458
|
+
type MarkerFieldConfig = {
|
|
459
|
+
path: string;
|
|
460
|
+
label?: string | undefined;
|
|
461
|
+
hint?: string | undefined;
|
|
462
|
+
inputType?: MarkerFieldInputType | undefined;
|
|
463
|
+
options?: readonly string[] | undefined;
|
|
464
|
+
optionsFrom?: string | undefined;
|
|
465
|
+
validation?: MarkerFieldValidation | undefined;
|
|
466
|
+
required?: boolean | undefined;
|
|
467
|
+
aiPrompt?: string | undefined;
|
|
468
|
+
aiAdapt?: boolean | undefined;
|
|
469
|
+
aiSeesDocument?: boolean | undefined;
|
|
470
|
+
lookup?: MarkerFieldLookup | undefined;
|
|
471
|
+
source?: MarkerFieldSource | undefined;
|
|
472
|
+
formula?: string | undefined;
|
|
473
|
+
condition?: string | undefined;
|
|
474
|
+
dateFormat?: FieldDateFormat | undefined;
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* The filters that describe a REPEAT rather than a value: how many rows it
|
|
478
|
+
* takes and what to call it. These are the ones a `{% for %}` tag may carry,
|
|
479
|
+
* since the loop path names the array, not one of its values.
|
|
480
|
+
*/
|
|
481
|
+
declare const ARRAY_FILTER_NAMES: readonly ["label", "hint", "required", "min_items", "max_items"];
|
|
482
|
+
declare const isArrayFilterName: (name: FilterName) => boolean;
|
|
483
|
+
/**
|
|
484
|
+
* The filter chain that declares this field in a document, in the catalogue's
|
|
485
|
+
* own order so a chain the writer produces and one an author wrote read the
|
|
486
|
+
* same.
|
|
487
|
+
*/
|
|
488
|
+
declare const filtersFromFieldConfig: (field: MarkerFieldConfig) => FilterCall[];
|
|
489
|
+
/**
|
|
490
|
+
* The chain a `{% for %}` opener carries for this array: the repeat's own
|
|
491
|
+
* filters and nothing else, derived from {@link ARRAY_FILTER_NAMES}, so a
|
|
492
|
+
* value filter can never be written where the reader refuses it.
|
|
493
|
+
*/
|
|
494
|
+
declare const arrayFiltersFromFieldConfig: (field: MarkerFieldConfig) => FilterCall[];
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/render.d.ts
|
|
497
|
+
/** Why a value has no spelling the scanner reads back as itself. */
|
|
498
|
+
type UnwritableReason = "tag-delimiter" | "number-spelling";
|
|
499
|
+
/** The reason this literal cannot be written in a marker of this form, or
|
|
500
|
+
* `null` when it can. A boolean always can; a string can in a `{{ }}` marker,
|
|
501
|
+
* because the writer quotes and escapes it. */
|
|
502
|
+
declare const unwritableMarkerLiteral: (value: MarkerLiteral, form?: MarkerForm) => UnwritableReason | null;
|
|
503
|
+
/** Every value in a chain the grammar has no spelling for, with the filter it
|
|
504
|
+
* was written on and why, so a caller can refuse a configuration before it
|
|
505
|
+
* rewrites a document into something the scanner would read differently. A
|
|
506
|
+
* repeat's chain goes in a tag, so pass `"statement"` for one. */
|
|
507
|
+
declare const unwritableFilterValues: (filters: readonly FilterCall[], form?: MarkerForm) => {
|
|
508
|
+
filter: string;
|
|
509
|
+
value: MarkerLiteral;
|
|
510
|
+
reason: UnwritableReason;
|
|
511
|
+
}[];
|
|
512
|
+
/**
|
|
513
|
+
* One filter chain as marker text, without the leading pipe: `label("A") |
|
|
514
|
+
* required`. An empty chain renders the empty string, which is what a marker
|
|
515
|
+
* that configures nothing carries.
|
|
516
|
+
*/
|
|
517
|
+
declare const renderFilterChain: (filters: readonly FilterCall[]) => string;
|
|
518
|
+
/** One value marker: `{{ path }}` or `{{ path | filters }}`. */
|
|
519
|
+
declare const renderValueMarker: (path: string, filters: readonly FilterCall[]) => string;
|
|
520
|
+
type ConditionTagOptions = {
|
|
521
|
+
/** Which branch opener this is; `elif` keeps its place in the chain. */
|
|
522
|
+
kind: "if" | "elif";
|
|
523
|
+
/** The `{% if %}` expression, already serialized. */
|
|
524
|
+
expression: string;
|
|
525
|
+
/** The placement the tag already carried. */
|
|
526
|
+
prefix: MarkerPrefix;
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* One `{% if %}` / `{% elif %}` opener carrying its expression.
|
|
530
|
+
*
|
|
531
|
+
* A rule that decides whether a block shows belongs in the tag that shows it:
|
|
532
|
+
* the tag is the only place a document has for it, so a builder's rule is
|
|
533
|
+
* serialized here rather than stored beside the document under a name.
|
|
534
|
+
*/
|
|
535
|
+
declare const renderConditionTag: ({ expression, kind, prefix }: ConditionTagOptions) => string;
|
|
536
|
+
type ForOpenerOptions = {
|
|
537
|
+
alias: string;
|
|
538
|
+
path: string;
|
|
539
|
+
filters: readonly FilterCall[];
|
|
540
|
+
/** The placement the opener already carried, so rewriting its filters does
|
|
541
|
+
* not move a row-form loop off its table row. */
|
|
542
|
+
prefix: MarkerPrefix;
|
|
543
|
+
};
|
|
544
|
+
/** One `{% for %}` opener, keeping its placement prefix. */
|
|
545
|
+
declare const renderForOpener: ({ alias, filters, path, prefix }: ForOpenerOptions) => string;
|
|
546
|
+
//#endregion
|
|
280
547
|
//#region src/row-blocks.d.ts
|
|
281
548
|
/** One end of a row block, addressed within the row that was scanned. */
|
|
282
549
|
type RowBlockMarker = {
|
|
@@ -300,7 +567,7 @@ type RowBlockPair = {
|
|
|
300
567
|
* paragraph, they are a matching opener/closer pair, the closer sits in a LATER
|
|
301
568
|
* cell than the opener, the opener prefixes its cell and the closer suffixes
|
|
302
569
|
* its cell. Everything else — one half of a pair, two nested pairs, a stray
|
|
303
|
-
* `{
|
|
570
|
+
* `{% else %}`, a pair wrapping the paragraphs of a single cell — is left to the
|
|
304
571
|
* engine's existing structure errors rather than guessed at.
|
|
305
572
|
*/
|
|
306
573
|
declare const detectRowBlockPair: (cells: readonly (readonly string[])[]) => RowBlockPair | null;
|
|
@@ -308,11 +575,11 @@ declare const detectRowBlockPair: (cells: readonly (readonly string[])[]) => Row
|
|
|
308
575
|
//#region src/index.d.ts
|
|
309
576
|
type NamedCondition = {
|
|
310
577
|
name: string;
|
|
311
|
-
/** The `{
|
|
578
|
+
/** The `{% if %}` expression string. Authoritative unless `node` is set. */
|
|
312
579
|
expression: string;
|
|
313
580
|
/**
|
|
314
581
|
* The canonical AST, set when the condition cannot round-trip through the
|
|
315
|
-
* expression string — i.e. it uses a `formula` operand the `{
|
|
582
|
+
* expression string — i.e. it uses a `formula` operand the `{% if %}` grammar
|
|
316
583
|
* has no syntax for. When present it is evaluated directly and `expression`
|
|
317
584
|
* is ignored.
|
|
318
585
|
*/
|
|
@@ -326,16 +593,17 @@ type NamedCondition = {
|
|
|
326
593
|
*/
|
|
327
594
|
declare const MAX_CONDITION_DEPTH = 50;
|
|
328
595
|
/**
|
|
329
|
-
* Evaluate a template `{
|
|
596
|
+
* Evaluate a template `{% if ... %}` condition. The expression is parsed into
|
|
330
597
|
* the canonical `@stll/conditions` AST and evaluated by the shared evaluator.
|
|
331
598
|
*
|
|
332
|
-
* Supports comparisons (`==`, `!=`, `>`, `<`, `>=`, `<=`),
|
|
333
|
-
*
|
|
334
|
-
*
|
|
599
|
+
* Supports comparisons (`==`, `!=`, `>`, `<`, `>=`, `<=`), membership
|
|
600
|
+
* (`"x" in path`), presence (`is defined` / `is not defined`), truthiness,
|
|
601
|
+
* negation (`not`), logical operators (`and`, `or`), parentheses, dotted
|
|
602
|
+
* paths, numeric underscores, and named-condition references.
|
|
335
603
|
*
|
|
336
604
|
* An empty or unparseable expression is `false` ("no condition" never gates a
|
|
337
605
|
* branch true).
|
|
338
606
|
*/
|
|
339
607
|
declare const evaluateCondition: (expression: string, data: Record<string, unknown>, namedConditions?: readonly NamedCondition[], _resolved?: ReadonlySet<string>, _depth?: number) => boolean;
|
|
340
608
|
//#endregion
|
|
341
|
-
export { BLOCK_DIRECTIVE_KINDS, type BlockDirectiveKind, type CompareNode, type CompareOp, type Condition, type ConditionNode, DATE_FORMAT_EXAMPLE_ISO, DATE_FORMAT_STYLES, DIRECTIVE_KINDS, type DateFormatStyle, type DeterministicFieldConfig, type DirectiveKind, type FieldDateFormat, type GroupNode, type InvalidMarker, MARKER_DEFECT_KINDS, MAX_CONDITION_DEPTH, type MarkerDefectKind, type MarkerMeta, NUMERIC_FUNCTION_NAMES, NamedCondition, type NumericFunctionName, type Operand, type
|
|
609
|
+
export { ARRAY_FILTER_NAMES, BLOCK_DIRECTIVE_KINDS, type BlockDirectiveKind, type CompareNode, type CompareOp, type Condition, type ConditionNode, type ConditionTagOptions, DATE_FORMAT_EXAMPLE_ISO, DATE_FORMAT_STYLES, DIRECTIVE_KINDS, type DateFormatStyle, type DeterministicFieldConfig, type DirectiveKind, FILTER_NAMES, type FieldDateFormat, type FilterArgument, type FilterCall, type FilterName, type ForOpenerOptions, type GroupNode, type InvalidMarker, LOOP_PROPERTIES, type LoopProperty, MARKER_DEFECT_KINDS, MARKER_OUTPUT_BODY, MARKER_STATEMENT_BODY, MAX_CONDITION_DEPTH, type MarkerDefect, type MarkerDefectKind, type MarkerFieldConfig, type MarkerFieldInputType, type MarkerFieldLookup, type MarkerFieldSource, type MarkerFieldValidation, type MarkerForm, type MarkerLiteral, type MarkerMeta, type MarkerPrefix, NUMERIC_FUNCTION_NAMES, NamedCondition, type NumericFunctionName, type Operand, type PredicateNode, type PredicateOp, type RowBlockMarker, type RowBlockPair, type RowScope, type ScannedMarker, type UnwritableReason, arrayFiltersFromFieldConfig, assertNever, blockDirectiveLinePattern, classifyMarker, classifyMarkerDefect, clauseSlotKey, clauseSlotPattern, conditionHasFormula, conditionNodeSchema, conditionSchema, detectRowBlockPair, emptyCondition, evaluateCondition, evaluateNumericExpression, filtersFromFieldConfig, formatDate, hasBlockDirectivePattern, hasNumberingPattern, isArrayFilterName, isBlockDirectiveKind, isClauseSlotName, isFieldPath, isSafeFieldPath, legacyLoopAlias, legacyMarkerReplacement, loopPattern, markerPattern, normalizeMarkerInner, numPattern, parseCondition, placeholderPattern, qualifyLoopPath, qualifyRowScopedPath, qualifyRowScopedPlaceholder, refPattern, referencedConditionPaths, renderConditionTag, renderDeterministicFieldValue, renderFilterChain, renderForOpener, renderValueMarker, replaceOutputMarkers, resolvePath, rowScopePaths, scanInvalidMarkers, scanMarkers, serializeCondition, substitutionKey, translateLegacyExpression, unwritableFilterValues, unwritableMarkerLiteral };
|