@funcstache/stache-stream 0.2.2 → 0.2.3
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/.eslintrc.json +30 -0
- package/.swcrc +29 -0
- package/DEV.md +84 -0
- package/README.md +145 -0
- package/TASKS.md +13 -0
- package/TODO.md +28 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +120 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1633 -0
- package/docs/classes/StacheTransformStream.html +13 -0
- package/docs/hierarchy.html +1 -0
- package/docs/index.html +73 -0
- package/docs/interfaces/Context.html +3 -0
- package/docs/interfaces/ContextProvider.html +10 -0
- package/docs/interfaces/PartialTagContextLambda.html +11 -0
- package/docs/interfaces/PartialTagContextLambdaResult.html +7 -0
- package/docs/interfaces/SectionTagCallback.html +12 -0
- package/docs/interfaces/SectionTagContextRecord.html +4 -0
- package/docs/interfaces/Tag.html +45 -0
- package/docs/interfaces/VariableTagContextLambda.html +4 -0
- package/docs/interfaces/VariableTagContextRecord.html +3 -0
- package/docs/media/StacheStream.ts +79 -0
- package/docs/modules.html +1 -0
- package/docs/types/ContextTypes.html +3 -0
- package/docs/types/JsonType.html +2 -0
- package/docs/types/PartialTagContext.html +4 -0
- package/docs/types/SectionTagContext.html +4 -0
- package/docs/types/TemplateName.html +9 -0
- package/docs/types/VariableTagContext.html +4 -0
- package/docs/types/VariableTagContextPrimitive.html +3 -0
- package/docs-assets/images/context-dotted-found.png +0 -0
- package/docs-assets/images/context-dotted-not-found.png +0 -0
- package/docs-assets/images/context-not-found.png +0 -0
- package/package.json +3 -6
- package/project.json +26 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +67 -0
- package/src/lib/parse/Parse.spec.ts +50 -0
- package/src/lib/parse/Parse.ts +92 -0
- package/src/lib/parse/README.md +62 -0
- package/src/lib/plan_base_v2.md +33 -0
- package/src/lib/plan_comment.md +53 -0
- package/src/lib/plan_implicit-iterator.md +213 -0
- package/src/lib/plan_inverted-sections.md +160 -0
- package/src/lib/plan_partials.md +237 -0
- package/src/lib/plan_sections.md +167 -0
- package/src/lib/plan_stache-stream.md +110 -0
- package/src/lib/plan_whitespace.md +98 -0
- package/src/lib/queue/Queue.spec.ts +275 -0
- package/src/lib/queue/Queue.ts +253 -0
- package/src/lib/queue/README.md +110 -0
- package/src/lib/stache-stream/README.md +45 -0
- package/src/lib/stache-stream/StacheStream.spec.ts +107 -0
- package/src/lib/stache-stream/StacheStream.ts +79 -0
- package/src/lib/tag/README.md +95 -0
- package/src/lib/tag/Tag.spec.ts +212 -0
- package/src/lib/tag/Tag.ts +295 -0
- package/src/lib/template/README.md +102 -0
- package/src/lib/template/Template-comment.spec.ts +76 -0
- package/src/lib/template/Template-inverted-section.spec.ts +85 -0
- package/src/lib/template/Template-partials.spec.ts +125 -0
- package/src/lib/template/Template-section.spec.ts +142 -0
- package/src/lib/template/Template.spec.ts +178 -0
- package/src/lib/template/Template.ts +614 -0
- package/src/lib/test/streams.ts +36 -0
- package/src/lib/tokenize/README.md +97 -0
- package/src/lib/tokenize/Tokenize.spec.ts +364 -0
- package/src/lib/tokenize/Tokenize.ts +374 -0
- package/src/lib/{types.d.ts → types.ts} +73 -25
- package/tsconfig.json +21 -0
- package/tsconfig.lib.json +16 -0
- package/tsconfig.spec.json +21 -0
- package/typedoc.mjs +15 -0
- package/vite.config.ts +27 -0
- package/vitest.setup.ts +6 -0
- package/src/global.d.js +0 -8
- package/src/global.d.js.map +0 -1
- package/src/index.d.ts +0 -7
- package/src/index.js +0 -24
- package/src/index.js.map +0 -1
- package/src/lib/parse/Parse.d.ts +0 -14
- package/src/lib/parse/Parse.js +0 -79
- package/src/lib/parse/Parse.js.map +0 -1
- package/src/lib/queue/Queue.d.ts +0 -32
- package/src/lib/queue/Queue.js +0 -181
- package/src/lib/queue/Queue.js.map +0 -1
- package/src/lib/stache-stream/StacheStream.d.ts +0 -22
- package/src/lib/stache-stream/StacheStream.js +0 -71
- package/src/lib/stache-stream/StacheStream.js.map +0 -1
- package/src/lib/tag/Tag.d.ts +0 -33
- package/src/lib/tag/Tag.js +0 -231
- package/src/lib/tag/Tag.js.map +0 -1
- package/src/lib/template/Template.d.ts +0 -18
- package/src/lib/template/Template.js +0 -428
- package/src/lib/template/Template.js.map +0 -1
- package/src/lib/test/streams.d.ts +0 -2
- package/src/lib/test/streams.js +0 -39
- package/src/lib/test/streams.js.map +0 -1
- package/src/lib/tokenize/Tokenize.d.ts +0 -22
- package/src/lib/tokenize/Tokenize.js +0 -268
- package/src/lib/tokenize/Tokenize.js.map +0 -1
- package/src/lib/types.js +0 -33
- package/src/lib/types.js.map +0 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { Log } from "@funcstache/logger";
|
|
3
|
+
import { Tag } from "../tag/Tag";
|
|
4
|
+
|
|
5
|
+
export class Tokenize extends EventEmitter<TokenizeEventMap> {
|
|
6
|
+
readonly #TOKEN_ENTER_CHAR = "{";
|
|
7
|
+
readonly #TOKEN_EXIT_CHAR = "}";
|
|
8
|
+
|
|
9
|
+
#logger = new Log({
|
|
10
|
+
category: "TKZ",
|
|
11
|
+
level: (process.env as any).LOG_LEVEL || "warn",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
#stateCurrent: StateNames = "outsideTag";
|
|
15
|
+
#statePrevious: StateNames | undefined;
|
|
16
|
+
#leftBuffer = "";
|
|
17
|
+
#leftTokenBuffer = "";
|
|
18
|
+
#middleBuffer = "";
|
|
19
|
+
#rightTokenBuffer = "";
|
|
20
|
+
|
|
21
|
+
#emitTagToken() {
|
|
22
|
+
if (this.#leftTokenBuffer.length === this.#rightTokenBuffer.length) {
|
|
23
|
+
const data = Tag.parse(
|
|
24
|
+
`${this.#leftTokenBuffer}${this.#middleBuffer}${this.#rightTokenBuffer}`
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
data &&
|
|
28
|
+
this.#emitToken({
|
|
29
|
+
data,
|
|
30
|
+
uid: performance.now(),
|
|
31
|
+
type: "tag",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.#leftBuffer = "";
|
|
36
|
+
this.#leftTokenBuffer = "";
|
|
37
|
+
this.#middleBuffer = "";
|
|
38
|
+
this.#rightTokenBuffer = "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#emitToken(event: TokenizeTagEvent | TokenizeTextEvent | null) {
|
|
42
|
+
this.#logger.debug(() => [`!!! #emitEvent: event=`, JSON.stringify(event)]);
|
|
43
|
+
this.emit("token", event);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async push(char: string | null): Promise<void> {
|
|
47
|
+
this.#logger.debug(() => "-".repeat(80));
|
|
48
|
+
|
|
49
|
+
char && this.#updateStatus(char);
|
|
50
|
+
await this.#processChar(char);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async #processChar(char: string | null): Promise<void> {
|
|
54
|
+
this.#logger.debug(
|
|
55
|
+
() =>
|
|
56
|
+
`=== #processChar: current=${this.#stateCurrent}, previous=${
|
|
57
|
+
this.#statePrevious
|
|
58
|
+
}`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (char === null) {
|
|
62
|
+
if (this.#leftBuffer) {
|
|
63
|
+
this.#emitToken({
|
|
64
|
+
data: this.#leftBuffer,
|
|
65
|
+
type: "text",
|
|
66
|
+
uid: performance.now(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
this.#emitTagToken();
|
|
70
|
+
this.#emitToken(null);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The only state changes that should happen here are resets that need to be done after `char`
|
|
75
|
+
// is processed.
|
|
76
|
+
switch (this.#stateCurrent) {
|
|
77
|
+
case "tagEnteringMaybe": {
|
|
78
|
+
// This state change happens in the case of nested tags, for example: `{{#a}}{{m}}{{/a}}`
|
|
79
|
+
// It's why we can't set `#leftTokenBuffer` in the `#updateStatus` method.
|
|
80
|
+
if (this.#statePrevious === "tagExitingMaybe") {
|
|
81
|
+
this.#emitTagToken();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.#leftTokenBuffer += char;
|
|
85
|
+
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// case "tagExitingMaybe": {
|
|
90
|
+
// break;
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
case "inTagValue": {
|
|
94
|
+
if (this.#leftBuffer) {
|
|
95
|
+
this.#emitToken({
|
|
96
|
+
data: this.#leftBuffer,
|
|
97
|
+
type: "text",
|
|
98
|
+
uid: performance.now(),
|
|
99
|
+
});
|
|
100
|
+
this.#leftBuffer = "";
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "outsideTag": {
|
|
106
|
+
if (this.#statePrevious === "tagEnteringMaybe") {
|
|
107
|
+
// Single `{` that was not the start of a mustache tag — treat as plain text.
|
|
108
|
+
// #updateStatus already appended the current char to leftBuffer, so insert
|
|
109
|
+
// leftTokenBuffer before that last char to preserve the correct order.
|
|
110
|
+
this.#leftBuffer =
|
|
111
|
+
this.#leftBuffer.slice(0, -1) +
|
|
112
|
+
this.#leftTokenBuffer +
|
|
113
|
+
this.#leftBuffer.slice(-1);
|
|
114
|
+
this.#leftTokenBuffer = "";
|
|
115
|
+
} else if (this.#leftTokenBuffer && this.#rightTokenBuffer) {
|
|
116
|
+
const data = Tag.parse(
|
|
117
|
+
`${this.#leftTokenBuffer}${this.#middleBuffer}${
|
|
118
|
+
this.#rightTokenBuffer
|
|
119
|
+
}`
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
data &&
|
|
123
|
+
this.#emitToken({
|
|
124
|
+
data,
|
|
125
|
+
type: "tag",
|
|
126
|
+
uid: performance.now(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.#leftTokenBuffer = "";
|
|
130
|
+
this.#middleBuffer = "";
|
|
131
|
+
this.#rightTokenBuffer = "";
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.#logger.debug(
|
|
138
|
+
() =>
|
|
139
|
+
`--- #processChar: '${char}' - ${this.#leftBuffer},${
|
|
140
|
+
this.#leftTokenBuffer ? ` _${this.#leftTokenBuffer}_` : " "
|
|
141
|
+
}, ${this.#middleBuffer}, ${
|
|
142
|
+
this.#rightTokenBuffer ? `_${this.#rightTokenBuffer}_` : ""
|
|
143
|
+
}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#updateStateFromModel(
|
|
148
|
+
current: StateNames,
|
|
149
|
+
options: UpdateStateModelOptions,
|
|
150
|
+
instantStateCallback?: (state: StateNames) => void
|
|
151
|
+
): StateNames {
|
|
152
|
+
const currentStateData = stateModel[current];
|
|
153
|
+
let nextState: StateNames = current;
|
|
154
|
+
for (const stateName of Object.keys(currentStateData) as StateNames[]) {
|
|
155
|
+
const dataArr = currentStateData[stateName];
|
|
156
|
+
if (!dataArr) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
dataArr.some((data) => {
|
|
162
|
+
const allFalse =
|
|
163
|
+
"isFalse" in data
|
|
164
|
+
? data.isFalse.every(
|
|
165
|
+
(optionName) => options[optionName] === false
|
|
166
|
+
)
|
|
167
|
+
: true;
|
|
168
|
+
|
|
169
|
+
if (!allFalse) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const allTrue =
|
|
174
|
+
"isTrue" in data
|
|
175
|
+
? data.isTrue.every((optionName) => options[optionName] === true)
|
|
176
|
+
: true;
|
|
177
|
+
|
|
178
|
+
if (allTrue) {
|
|
179
|
+
nextState = stateName;
|
|
180
|
+
|
|
181
|
+
// If the next state is "instant" invoke the callback then invoke
|
|
182
|
+
// `#updateStateFromModel` function again recursively.
|
|
183
|
+
if ("instant" in data && data.instant) {
|
|
184
|
+
instantStateCallback && instantStateCallback(nextState);
|
|
185
|
+
nextState = this.#updateStateFromModel(nextState, options);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
})
|
|
193
|
+
) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return nextState;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#updateStatus(char: string): void {
|
|
202
|
+
this.#logger.debug(
|
|
203
|
+
() =>
|
|
204
|
+
`--- #updateStatus: '${char}' - ${this.#leftBuffer},${
|
|
205
|
+
this.#leftTokenBuffer ? ` _${this.#leftTokenBuffer}_` : " "
|
|
206
|
+
}, ${this.#middleBuffer}, ${
|
|
207
|
+
this.#rightTokenBuffer ? `_${this.#rightTokenBuffer}_` : ""
|
|
208
|
+
}`
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
this.#statePrevious = this.#stateCurrent;
|
|
212
|
+
|
|
213
|
+
// This code sets the state for how `char` should be processed.
|
|
214
|
+
const isEnterToken = char === this.#TOKEN_ENTER_CHAR;
|
|
215
|
+
const isExitToken = char === this.#TOKEN_EXIT_CHAR;
|
|
216
|
+
|
|
217
|
+
const options: UpdateStateModelOptions = {
|
|
218
|
+
currentCharEnterToken: isEnterToken,
|
|
219
|
+
currentCharExitToken: isExitToken,
|
|
220
|
+
currentCharNotToken: !isEnterToken && !isExitToken,
|
|
221
|
+
enterTokenCountEqualsOne: 1 === this.#leftTokenBuffer.length,
|
|
222
|
+
enterTokenCountEqualsTwo: 2 === this.#leftTokenBuffer.length,
|
|
223
|
+
enterTokenCountEqualsThree: 3 === this.#leftTokenBuffer.length,
|
|
224
|
+
exitTokenCountEqualsEnterTokenCount:
|
|
225
|
+
this.#leftTokenBuffer.length === this.#rightTokenBuffer.length,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
this.#stateCurrent = this.#updateStateFromModel(
|
|
229
|
+
this.#stateCurrent,
|
|
230
|
+
options,
|
|
231
|
+
(state) => {
|
|
232
|
+
this.#logger.debug(
|
|
233
|
+
() =>
|
|
234
|
+
`>>> #updateStatus: instant;\n #statePrevious='${
|
|
235
|
+
this.#statePrevious
|
|
236
|
+
}',\n state='${state}'`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Manage most of the state changes here. a small number of them have to be done in
|
|
242
|
+
// `#processChar` because certain state values have to be reset after the char is processed.
|
|
243
|
+
switch (this.#stateCurrent) {
|
|
244
|
+
case "tagEnteringMaybe": {
|
|
245
|
+
//Changing to "tagEnteringMaybe" could happen because of nested tags, for example:
|
|
246
|
+
// `{{#a}}{{m}}{{/a}}`. It's why we can't set `#leftTokenBuffer` here, it has to be done as
|
|
247
|
+
// part of #processChar.
|
|
248
|
+
|
|
249
|
+
// this.#leftTokenBuffer += char;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case "tagExitingMaybe": {
|
|
254
|
+
this.#rightTokenBuffer += char;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "outsideTag": {
|
|
259
|
+
this.#leftBuffer += char;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "inTagValue": {
|
|
264
|
+
this.#middleBuffer += char;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
interface TokenizeEvent {
|
|
272
|
+
uid: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface TokenizeTagEvent extends TokenizeEvent {
|
|
276
|
+
data: Tag;
|
|
277
|
+
type: "tag";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface TokenizeTextEvent extends TokenizeEvent {
|
|
281
|
+
data: string;
|
|
282
|
+
type: "text";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export type TokenizeEventMap = {
|
|
286
|
+
token: [TokenizeTagEvent | TokenizeTextEvent | null];
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export type StateNames =
|
|
290
|
+
| "inTagValue"
|
|
291
|
+
| "outsideTag"
|
|
292
|
+
| "tagExitingMaybe"
|
|
293
|
+
| "tagEnteringMaybe";
|
|
294
|
+
|
|
295
|
+
const stateModel: StateModel = {
|
|
296
|
+
inTagValue: {
|
|
297
|
+
tagExitingMaybe: [
|
|
298
|
+
{
|
|
299
|
+
isTrue: ["currentCharExitToken"],
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
outsideTag: {
|
|
305
|
+
tagEnteringMaybe: [
|
|
306
|
+
{
|
|
307
|
+
isTrue: ["currentCharEnterToken"],
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
tagExitingMaybe: {
|
|
313
|
+
tagEnteringMaybe: [
|
|
314
|
+
{
|
|
315
|
+
isTrue: [
|
|
316
|
+
"currentCharEnterToken",
|
|
317
|
+
"exitTokenCountEqualsEnterTokenCount",
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
outsideTag: [
|
|
322
|
+
{
|
|
323
|
+
isTrue: ["currentCharNotToken", "exitTokenCountEqualsEnterTokenCount"],
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
tagEnteringMaybe: {
|
|
329
|
+
outsideTag: [
|
|
330
|
+
{
|
|
331
|
+
isFalse: ["currentCharEnterToken"],
|
|
332
|
+
isTrue: ["enterTokenCountEqualsOne"],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
inTagValue: [
|
|
336
|
+
{
|
|
337
|
+
isTrue: ["currentCharNotToken", "enterTokenCountEqualsTwo"],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
isTrue: ["enterTokenCountEqualsThree"],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
} as const;
|
|
345
|
+
|
|
346
|
+
type StateModel = {
|
|
347
|
+
[currentState in StateNames]: {
|
|
348
|
+
[moveToStateWhen in StateNames]?: (
|
|
349
|
+
| {
|
|
350
|
+
instant?: boolean;
|
|
351
|
+
isFalse: (keyof UpdateStateModelOptions)[];
|
|
352
|
+
isTrue: (keyof UpdateStateModelOptions)[];
|
|
353
|
+
}
|
|
354
|
+
| {
|
|
355
|
+
instant?: boolean;
|
|
356
|
+
isFalse: (keyof UpdateStateModelOptions)[];
|
|
357
|
+
}
|
|
358
|
+
| {
|
|
359
|
+
instant?: boolean;
|
|
360
|
+
isTrue: (keyof UpdateStateModelOptions)[];
|
|
361
|
+
}
|
|
362
|
+
)[];
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
interface UpdateStateModelOptions {
|
|
367
|
+
currentCharEnterToken: boolean;
|
|
368
|
+
currentCharExitToken: boolean;
|
|
369
|
+
currentCharNotToken: boolean;
|
|
370
|
+
enterTokenCountEqualsOne: boolean;
|
|
371
|
+
enterTokenCountEqualsTwo: boolean;
|
|
372
|
+
enterTokenCountEqualsThree: boolean;
|
|
373
|
+
exitTokenCountEqualsEnterTokenCount: boolean;
|
|
374
|
+
}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { ReadableStream } from "node:stream/web";
|
|
2
|
+
|
|
2
3
|
export type ContentTagTagTypes = (typeof ContentTagTypes)[number];
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
export const ContentTagTypes = [
|
|
6
|
+
"block", // `{{$tag}}{{/tag}}`
|
|
7
|
+
"end", // `{{/tag}}`
|
|
8
|
+
"inverted", // `{{^tag}}{{/tag}}`
|
|
9
|
+
"parent", // `{{<tag}}{{/tag}}`
|
|
10
|
+
"section", // `{{#tag}}{{/tag}}`
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* A JavaScript object that supplies replacements for tags in a Mustache template. The Mustache
|
|
6
15
|
* specification uses the term "hash." In stache-stream "hash" and "context" are synonyms.
|
|
7
16
|
* @category Context
|
|
8
17
|
*/
|
|
9
18
|
export interface Context {
|
|
10
|
-
|
|
19
|
+
readonly [key: string]: ContextTypes | undefined;
|
|
11
20
|
}
|
|
21
|
+
|
|
12
22
|
/**
|
|
13
23
|
* When a Mustache template is rendered data needs to be provided to replace tags in the template; a
|
|
14
24
|
* ContextProvider delivers that information. The ContextProvider includes context for a template as
|
|
@@ -20,19 +30,30 @@ export interface Context {
|
|
|
20
30
|
* look for a value in parent context.
|
|
21
31
|
*/
|
|
22
32
|
export interface ContextProvider {
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
readonly context: ContextTypes;
|
|
34
|
+
readonly getContextValue?: <CTX extends ContextTypes>(
|
|
35
|
+
tag: Tag
|
|
36
|
+
) => Promise<CTX | undefined>;
|
|
25
37
|
}
|
|
38
|
+
|
|
26
39
|
/**
|
|
27
40
|
* Properties in a { @link Context } object implement these types. The specific types to implement
|
|
28
41
|
* are determined by the tag type in a Mustache template.
|
|
29
42
|
* @category Context
|
|
30
43
|
*/
|
|
31
|
-
export type ContextTypes =
|
|
44
|
+
export type ContextTypes =
|
|
45
|
+
| Context
|
|
46
|
+
// | JsonType
|
|
47
|
+
| PartialTagContext
|
|
48
|
+
| SectionTagContext
|
|
49
|
+
| VariableTagContext;
|
|
50
|
+
// | (Context | JsonType | SectionTagContextList)[];
|
|
51
|
+
|
|
32
52
|
/**
|
|
33
53
|
* A primitive JavaScript type that can be serialized in a JSON object.
|
|
34
54
|
*/
|
|
35
55
|
export type JsonType = boolean | number | string;
|
|
56
|
+
|
|
36
57
|
/**
|
|
37
58
|
* Implement one of these interfaces to provide replacements for
|
|
38
59
|
* {@link https://mustache.github.io/mustache.5.html#Variables Partial tags}. See
|
|
@@ -41,6 +62,7 @@ export type JsonType = boolean | number | string;
|
|
|
41
62
|
* @category Partial
|
|
42
63
|
*/
|
|
43
64
|
export type PartialTagContext = PartialTagContextLambda;
|
|
65
|
+
|
|
44
66
|
/**
|
|
45
67
|
* Implement this function to asynchronously return a `ReadableStream` that contains a Mustache
|
|
46
68
|
* template and its associated context that replaces a
|
|
@@ -62,20 +84,22 @@ export type PartialTagContext = PartialTagContextLambda;
|
|
|
62
84
|
* @category Partial
|
|
63
85
|
*/
|
|
64
86
|
export interface PartialTagContextLambda {
|
|
65
|
-
|
|
87
|
+
(tag: Tag): Promise<PartialTagContextLambdaResult>;
|
|
66
88
|
}
|
|
89
|
+
|
|
67
90
|
/**
|
|
68
91
|
* The result of invoking a {@link PartialTagContextLambda}.
|
|
69
92
|
* @category Context
|
|
70
93
|
* @category Partial
|
|
71
94
|
*/
|
|
72
95
|
export interface PartialTagContextLambdaResult {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
/** The optional context associated with this template. */
|
|
97
|
+
context?: ContextTypes;
|
|
98
|
+
/** When invoked `input` resolves to a stream containing the Mustache template that replaces a
|
|
99
|
+
* partial tag. */
|
|
100
|
+
input: () => Promise<ReadableStream>;
|
|
78
101
|
}
|
|
102
|
+
|
|
79
103
|
/**
|
|
80
104
|
* Implement one of these interfaces to provide replacements for
|
|
81
105
|
* {@link https://mustache.github.io/mustache.5.html#Sections Section tags}. See
|
|
@@ -84,6 +108,7 @@ export interface PartialTagContextLambdaResult {
|
|
|
84
108
|
* @category Section
|
|
85
109
|
*/
|
|
86
110
|
export type SectionTagContext = SectionTagCallback | SectionTagContextRecord[];
|
|
111
|
+
|
|
87
112
|
/**
|
|
88
113
|
* Provides zero or more items that will replace a
|
|
89
114
|
* {@link https://mustache.github.io/mustache.5.html#Sections Section tag}. See
|
|
@@ -92,8 +117,9 @@ export type SectionTagContext = SectionTagCallback | SectionTagContextRecord[];
|
|
|
92
117
|
* @category Section
|
|
93
118
|
*/
|
|
94
119
|
export interface SectionTagContextRecord {
|
|
95
|
-
|
|
120
|
+
[key: string]: JsonType | SectionTagContextRecord;
|
|
96
121
|
}
|
|
122
|
+
|
|
97
123
|
/**
|
|
98
124
|
* When provided this callback is invoked when the
|
|
99
125
|
* {@link https://mustache.github.io/mustache.5.html#Sections Section tag} is encountered. The
|
|
@@ -117,8 +143,9 @@ export interface SectionTagContextRecord {
|
|
|
117
143
|
* @category Section
|
|
118
144
|
*/
|
|
119
145
|
export interface SectionTagCallback {
|
|
120
|
-
|
|
146
|
+
(tagContent: string): string;
|
|
121
147
|
}
|
|
148
|
+
|
|
122
149
|
/**
|
|
123
150
|
* Represents a tag in a Mustache template providing information about a tag and its
|
|
124
151
|
* characteristics.
|
|
@@ -165,12 +192,14 @@ export interface SectionTagCallback {
|
|
|
165
192
|
* "{{ #items }}" the `value` is "#items".
|
|
166
193
|
*/
|
|
167
194
|
export interface Tag {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
195
|
+
readonly content: string | null;
|
|
196
|
+
readonly key: string;
|
|
197
|
+
readonly raw: boolean;
|
|
198
|
+
readonly type: ContentTagTagTypes | ValueTagTagTypes;
|
|
199
|
+
readonly value: string;
|
|
200
|
+
// process: () => Promise<void>;
|
|
173
201
|
}
|
|
202
|
+
|
|
174
203
|
/**
|
|
175
204
|
* Defines how to access a template file.
|
|
176
205
|
* - string: The name of a template file in the same directory as this module file. The name must
|
|
@@ -179,9 +208,19 @@ export interface Tag {
|
|
|
179
208
|
* in the "string" type above. The second element is a relative path to the directory where the
|
|
180
209
|
* template file is located; the path is relative to the location of this module file.
|
|
181
210
|
*/
|
|
182
|
-
export type TemplateName =
|
|
211
|
+
export type TemplateName =
|
|
212
|
+
| string
|
|
213
|
+
| [templateName: string, templateFilename: string];
|
|
214
|
+
|
|
183
215
|
export type ValueTagTagTypes = (typeof ValueTagTypes)[number];
|
|
184
|
-
|
|
216
|
+
|
|
217
|
+
export const ValueTagTypes = [
|
|
218
|
+
"comment", // {{!text}}
|
|
219
|
+
"implicit", // `{{.}}`
|
|
220
|
+
"partial", // `{{>tag}}`
|
|
221
|
+
"variable",
|
|
222
|
+
] as const;
|
|
223
|
+
|
|
185
224
|
/**
|
|
186
225
|
* Interfaces that can supply replacements for
|
|
187
226
|
* {@link https://mustache.github.io/mustache.5.html#Variables variable tags}. See
|
|
@@ -189,7 +228,11 @@ export declare const ValueTagTypes: readonly ["comment", "implicit", "partial",
|
|
|
189
228
|
* @category Context
|
|
190
229
|
* @category Variable
|
|
191
230
|
*/
|
|
192
|
-
export type VariableTagContext =
|
|
231
|
+
export type VariableTagContext =
|
|
232
|
+
| VariableTagContextPrimitive
|
|
233
|
+
| VariableTagContextLambda
|
|
234
|
+
| VariableTagContextRecord;
|
|
235
|
+
|
|
193
236
|
/**
|
|
194
237
|
* A function that resolves to the value that replaces a
|
|
195
238
|
* {@link https://mustache.github.io/mustache.5.html#Variables variable tag}. See
|
|
@@ -198,15 +241,19 @@ export type VariableTagContext = VariableTagContextPrimitive | VariableTagContex
|
|
|
198
241
|
* @category Variable
|
|
199
242
|
*/
|
|
200
243
|
export interface VariableTagContextLambda {
|
|
201
|
-
|
|
244
|
+
(): Promise<VariableTagContextPrimitive | VariableTagContextRecord>;
|
|
202
245
|
}
|
|
246
|
+
|
|
203
247
|
/**
|
|
204
248
|
* A {@link https://mustache.github.io/mustache.5.html#Variables variable tag} can be replaced by
|
|
205
249
|
* these JavaScript primitives. See {@link VariableTagContext VariableTagContext}.
|
|
206
250
|
* @category Context
|
|
207
251
|
* @category Variable
|
|
208
252
|
*/
|
|
209
|
-
export type VariableTagContextPrimitive =
|
|
253
|
+
export type VariableTagContextPrimitive =
|
|
254
|
+
| JsonType
|
|
255
|
+
| Array<boolean | number | string>;
|
|
256
|
+
|
|
210
257
|
/**
|
|
211
258
|
* A {@link https://mustache.github.io/mustache.5.html#Variables variable tag} with a "dotted name"
|
|
212
259
|
* can be replaced by a JavaScript object. See {@link VariableTagContext VariableTagContext}.
|
|
@@ -214,11 +261,12 @@ export type VariableTagContextPrimitive = JsonType | Array<boolean | number | st
|
|
|
214
261
|
* @category Variable
|
|
215
262
|
*/
|
|
216
263
|
export interface VariableTagContextRecord {
|
|
217
|
-
|
|
264
|
+
[key: string]: unknown;
|
|
218
265
|
}
|
|
266
|
+
|
|
219
267
|
/**
|
|
220
268
|
* Invoke this function to write to the output `WritableStream`.
|
|
221
269
|
*/
|
|
222
270
|
export interface WriteToOutput {
|
|
223
|
-
|
|
271
|
+
(text: string): Promise<void>;
|
|
224
272
|
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"forceConsistentCasingInFileNames": true,
|
|
5
|
+
"strict": true,
|
|
6
|
+
"noImplicitOverride": true,
|
|
7
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
8
|
+
"noImplicitReturns": true,
|
|
9
|
+
"noFallthroughCasesInSwitch": true
|
|
10
|
+
},
|
|
11
|
+
"files": [],
|
|
12
|
+
"include": [],
|
|
13
|
+
"references": [
|
|
14
|
+
{
|
|
15
|
+
"path": "./tsconfig.lib.json"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "./tsconfig.spec.json"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"types": ["node"]
|
|
7
|
+
},
|
|
8
|
+
"include": ["src/**/*.ts"],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"vite.config.ts",
|
|
11
|
+
"vitest.config.ts",
|
|
12
|
+
"vitest.setup.ts",
|
|
13
|
+
"src/**/*.spec.ts",
|
|
14
|
+
"src/**/*.test.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"types": [
|
|
6
|
+
"vitest/globals",
|
|
7
|
+
"vitest/importMeta",
|
|
8
|
+
"vite/client",
|
|
9
|
+
"node",
|
|
10
|
+
"vitest"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"vite.config.ts",
|
|
15
|
+
"vitest.config.ts",
|
|
16
|
+
"vitest.setup.ts",
|
|
17
|
+
"src/**/*.d.ts",
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/**/*.spec.ts"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/typedoc.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// what?
|
|
2
|
+
const config = {
|
|
3
|
+
entryPoints: ["src/index.ts"],
|
|
4
|
+
includeVersion: true,
|
|
5
|
+
githubPages: true,
|
|
6
|
+
gitRevision: "main",
|
|
7
|
+
// navigation: {
|
|
8
|
+
// includeCategories: true,
|
|
9
|
+
// },
|
|
10
|
+
out: "docs",
|
|
11
|
+
readme: ["README.md"],
|
|
12
|
+
tsconfig: "tsconfig.lib.json",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default config;
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
root: __dirname,
|
|
6
|
+
cacheDir: "../../node_modules/.vite/libs/stache-stream",
|
|
7
|
+
|
|
8
|
+
plugins: [nxViteTsPaths()],
|
|
9
|
+
|
|
10
|
+
// Uncomment this if you are using workers.
|
|
11
|
+
// worker: {
|
|
12
|
+
// plugins: [ nxViteTsPaths() ],
|
|
13
|
+
// },
|
|
14
|
+
|
|
15
|
+
test: {
|
|
16
|
+
watch: false,
|
|
17
|
+
globals: true,
|
|
18
|
+
environment: "node",
|
|
19
|
+
include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
20
|
+
reporters: ["default"],
|
|
21
|
+
setupFiles: ["./vitest.setup.ts"],
|
|
22
|
+
coverage: {
|
|
23
|
+
reportsDirectory: "../../coverage/libs/stache-stream",
|
|
24
|
+
provider: "v8",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|