@dogsbay/minja 0.2.0-beta.48
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/LICENSE +21 -0
- package/README.md +603 -0
- package/bin/minja.js +1062 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +10 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -0
- package/dist/cli.js.map +1 -0
- package/dist/context.d.ts +47 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +112 -0
- package/dist/context.js.map +1 -0
- package/dist/evaluator.d.ts +20 -0
- package/dist/evaluator.d.ts.map +1 -0
- package/dist/evaluator.js +207 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +1026 -0
- package/dist/index.umd.js.map +7 -0
- package/dist/index.umd.min.js +9 -0
- package/dist/index.umd.min.js.map +7 -0
- package/dist/loader-fetch.d.ts +8 -0
- package/dist/loader-fetch.d.ts.map +1 -0
- package/dist/loader-fetch.js +15 -0
- package/dist/loader-fetch.js.map +1 -0
- package/dist/loader-memory.d.ts +11 -0
- package/dist/loader-memory.d.ts.map +1 -0
- package/dist/loader-memory.js +36 -0
- package/dist/loader-memory.js.map +1 -0
- package/dist/loader.d.ts +54 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +91 -0
- package/dist/loader.js.map +1 -0
- package/dist/parser.d.ts +12 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +501 -0
- package/dist/parser.js.map +1 -0
- package/dist/renderer.d.ts +13 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +415 -0
- package/dist/renderer.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,1026 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* minja v0.2.0-beta.48
|
|
3
|
+
* Minimal, secure Jinja2/Nunjucks subset for documentation preprocessing
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
var minja = (() => {
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// src/browser.ts
|
|
27
|
+
var browser_exports = {};
|
|
28
|
+
__export(browser_exports, {
|
|
29
|
+
Context: () => Context,
|
|
30
|
+
FetchLoader: () => FetchLoader,
|
|
31
|
+
MemoryLoader: () => MemoryLoader,
|
|
32
|
+
evaluateExpression: () => evaluateExpression,
|
|
33
|
+
parse: () => parse,
|
|
34
|
+
parseExpression: () => parseExpression,
|
|
35
|
+
render: () => render
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// src/evaluator.ts
|
|
39
|
+
function parseExpression(expr) {
|
|
40
|
+
expr = expr.trim();
|
|
41
|
+
if (expr === "true") {
|
|
42
|
+
return { type: "literal", value: true };
|
|
43
|
+
}
|
|
44
|
+
if (expr === "false") {
|
|
45
|
+
return { type: "literal", value: false };
|
|
46
|
+
}
|
|
47
|
+
if (expr === "null") {
|
|
48
|
+
return { type: "literal", value: null };
|
|
49
|
+
}
|
|
50
|
+
if (expr.startsWith('"') && expr.endsWith('"')) {
|
|
51
|
+
return { type: "literal", value: expr.slice(1, -1) };
|
|
52
|
+
}
|
|
53
|
+
if (expr.startsWith("'") && expr.endsWith("'")) {
|
|
54
|
+
return { type: "literal", value: expr.slice(1, -1) };
|
|
55
|
+
}
|
|
56
|
+
if (/^-?\d+(\.\d+)?$/.test(expr)) {
|
|
57
|
+
return { type: "literal", value: parseFloat(expr) };
|
|
58
|
+
}
|
|
59
|
+
if (expr.startsWith("(") && expr.endsWith(")")) {
|
|
60
|
+
let depth = 0;
|
|
61
|
+
let balanced = true;
|
|
62
|
+
for (let i = 0; i < expr.length; i++) {
|
|
63
|
+
if (expr[i] === "(") {
|
|
64
|
+
depth++;
|
|
65
|
+
} else if (expr[i] === ")") {
|
|
66
|
+
depth--;
|
|
67
|
+
}
|
|
68
|
+
if (depth === 0 && i < expr.length - 1) {
|
|
69
|
+
balanced = false;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (balanced && depth === 0) {
|
|
74
|
+
return parseExpression(expr.slice(1, -1));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (expr.startsWith("not ")) {
|
|
78
|
+
return {
|
|
79
|
+
type: "unary",
|
|
80
|
+
operator: "not",
|
|
81
|
+
operand: parseExpression(expr.substring(4))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const orMatch = findOperator(expr, " or ");
|
|
85
|
+
if (orMatch !== -1) {
|
|
86
|
+
return {
|
|
87
|
+
type: "binary",
|
|
88
|
+
operator: "or",
|
|
89
|
+
left: parseExpression(expr.substring(0, orMatch)),
|
|
90
|
+
right: parseExpression(expr.substring(orMatch + 4))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const andMatch = findOperator(expr, " and ");
|
|
94
|
+
if (andMatch !== -1) {
|
|
95
|
+
return {
|
|
96
|
+
type: "binary",
|
|
97
|
+
operator: "and",
|
|
98
|
+
left: parseExpression(expr.substring(0, andMatch)),
|
|
99
|
+
right: parseExpression(expr.substring(andMatch + 5))
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const comparisonOps = ["==", "!=", "<=", ">=", "<", ">"];
|
|
103
|
+
for (const op of comparisonOps) {
|
|
104
|
+
const opMatch = findOperator(expr, ` ${op} `);
|
|
105
|
+
if (opMatch !== -1) {
|
|
106
|
+
return {
|
|
107
|
+
type: "binary",
|
|
108
|
+
operator: op,
|
|
109
|
+
left: parseExpression(expr.substring(0, opMatch)),
|
|
110
|
+
right: parseExpression(expr.substring(opMatch + op.length + 2))
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (/^[\w.]+$/.test(expr)) {
|
|
115
|
+
return { type: "variable", name: expr };
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Invalid expression: ${expr}`);
|
|
118
|
+
}
|
|
119
|
+
function findOperator(expr, operator) {
|
|
120
|
+
let inString = null;
|
|
121
|
+
let depth = 0;
|
|
122
|
+
for (let i = 0; i < expr.length; i++) {
|
|
123
|
+
const char = expr[i];
|
|
124
|
+
if ((char === '"' || char === "'") && (i === 0 || expr[i - 1] !== "\\")) {
|
|
125
|
+
if (inString === char) {
|
|
126
|
+
inString = null;
|
|
127
|
+
} else if (inString === null) {
|
|
128
|
+
inString = char;
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!inString) {
|
|
133
|
+
if (char === "(") {
|
|
134
|
+
depth++;
|
|
135
|
+
} else if (char === ")") {
|
|
136
|
+
depth--;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!inString && depth === 0) {
|
|
140
|
+
if (expr.substring(i, i + operator.length) === operator) {
|
|
141
|
+
return i;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return -1;
|
|
146
|
+
}
|
|
147
|
+
function evaluateExpression(expr, context) {
|
|
148
|
+
switch (expr.type) {
|
|
149
|
+
case "literal":
|
|
150
|
+
return expr.value;
|
|
151
|
+
case "variable":
|
|
152
|
+
return context.get(expr.name);
|
|
153
|
+
case "binary": {
|
|
154
|
+
const left = evaluateExpression(expr.left, context);
|
|
155
|
+
const right = evaluateExpression(expr.right, context);
|
|
156
|
+
switch (expr.operator) {
|
|
157
|
+
case "==":
|
|
158
|
+
return left == right;
|
|
159
|
+
case "!=":
|
|
160
|
+
return left != right;
|
|
161
|
+
case "<":
|
|
162
|
+
return left < right;
|
|
163
|
+
case ">":
|
|
164
|
+
return left > right;
|
|
165
|
+
case "<=":
|
|
166
|
+
return left <= right;
|
|
167
|
+
case ">=":
|
|
168
|
+
return left >= right;
|
|
169
|
+
case "and":
|
|
170
|
+
return isTruthy(left) && isTruthy(right);
|
|
171
|
+
case "or":
|
|
172
|
+
return isTruthy(left) || isTruthy(right);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "unary": {
|
|
177
|
+
const operand = evaluateExpression(expr.operand, context);
|
|
178
|
+
switch (expr.operator) {
|
|
179
|
+
case "not":
|
|
180
|
+
return !isTruthy(operand);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
function isTruthy(value) {
|
|
188
|
+
if (value === void 0 || value === null || value === false) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (value === 0 || value === "") {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/parser.ts
|
|
198
|
+
function parse(template) {
|
|
199
|
+
const errors = [];
|
|
200
|
+
const ast = [];
|
|
201
|
+
let position = 0;
|
|
202
|
+
let line = 1;
|
|
203
|
+
let column = 1;
|
|
204
|
+
function createError(message) {
|
|
205
|
+
return { message, position, line, column };
|
|
206
|
+
}
|
|
207
|
+
function advance(count) {
|
|
208
|
+
for (let i = 0; i < count; i++) {
|
|
209
|
+
if (template[position + i] === "\n") {
|
|
210
|
+
line++;
|
|
211
|
+
column = 1;
|
|
212
|
+
} else {
|
|
213
|
+
column++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
position += count;
|
|
217
|
+
}
|
|
218
|
+
function findNext(str, from = position) {
|
|
219
|
+
return template.indexOf(str, from);
|
|
220
|
+
}
|
|
221
|
+
function extractTo(target) {
|
|
222
|
+
const text = template.substring(position, target);
|
|
223
|
+
advance(target - position);
|
|
224
|
+
return text;
|
|
225
|
+
}
|
|
226
|
+
while (position < template.length) {
|
|
227
|
+
const varStart = findNext("{{", position);
|
|
228
|
+
const tagStart = findNext("{%", position);
|
|
229
|
+
const commentStart = findNext("{#", position);
|
|
230
|
+
const candidates = [
|
|
231
|
+
{ pos: varStart, type: "var" },
|
|
232
|
+
{ pos: tagStart, type: "tag" },
|
|
233
|
+
{ pos: commentStart, type: "comment" }
|
|
234
|
+
].filter((c) => c.pos !== -1);
|
|
235
|
+
if (candidates.length === 0) {
|
|
236
|
+
const text = template.substring(position);
|
|
237
|
+
if (text) {
|
|
238
|
+
ast.push({ type: "text", value: text });
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
candidates.sort((a, b) => a.pos - b.pos);
|
|
243
|
+
const nearest = candidates[0];
|
|
244
|
+
if (nearest.pos > position) {
|
|
245
|
+
ast.push({ type: "text", value: extractTo(nearest.pos) });
|
|
246
|
+
}
|
|
247
|
+
if (nearest.type === "var") {
|
|
248
|
+
advance(2);
|
|
249
|
+
const endPos = findNext("}}", position);
|
|
250
|
+
if (endPos === -1) {
|
|
251
|
+
errors.push(createError("Unclosed variable tag"));
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
const varName = extractTo(endPos).trim();
|
|
255
|
+
advance(2);
|
|
256
|
+
ast.push({ type: "variable", name: varName });
|
|
257
|
+
} else if (nearest.type === "comment") {
|
|
258
|
+
if (ast.length > 0 && ast[ast.length - 1].type === "text") {
|
|
259
|
+
const lastNode = ast[ast.length - 1];
|
|
260
|
+
if (/\n[ \t]*$/.test(lastNode.value)) {
|
|
261
|
+
lastNode.value = lastNode.value.replace(/[ \t]*\n[ \t]*$/, "");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
advance(2);
|
|
265
|
+
const endPos = findNext("#}", position);
|
|
266
|
+
if (endPos === -1) {
|
|
267
|
+
errors.push(createError("Unclosed comment tag"));
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
const commentText = extractTo(endPos);
|
|
271
|
+
advance(2);
|
|
272
|
+
ast.push({ type: "comment", value: commentText });
|
|
273
|
+
if (position < template.length && template[position] === "\n") {
|
|
274
|
+
advance(1);
|
|
275
|
+
while (position < template.length && /[ \t]/.test(template[position])) {
|
|
276
|
+
advance(1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else if (nearest.type === "tag") {
|
|
280
|
+
advance(2);
|
|
281
|
+
const endPos = findNext("%}", position);
|
|
282
|
+
if (endPos === -1) {
|
|
283
|
+
errors.push(createError("Unclosed statement tag"));
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
let statement = extractTo(endPos).trim();
|
|
287
|
+
const hasLeftStrip = statement.startsWith("-");
|
|
288
|
+
const hasRightStrip = statement.endsWith("-");
|
|
289
|
+
if (hasLeftStrip) {
|
|
290
|
+
statement = statement.substring(1).trim();
|
|
291
|
+
if (ast.length > 0 && ast[ast.length - 1].type === "text") {
|
|
292
|
+
const lastNode = ast[ast.length - 1];
|
|
293
|
+
lastNode.value = lastNode.value.replace(/[ \t]*\n[ \t\n]*$/, "");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (hasRightStrip) {
|
|
297
|
+
statement = statement.substring(0, statement.length - 1).trim();
|
|
298
|
+
}
|
|
299
|
+
advance(2);
|
|
300
|
+
if (hasRightStrip) {
|
|
301
|
+
while (position < template.length && /[ \t\n]/.test(template[position])) {
|
|
302
|
+
advance(1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (statement.startsWith("set ")) {
|
|
306
|
+
const setMatch = /^set\s+(\w+)\s*=\s*(.+)$/.exec(statement);
|
|
307
|
+
if (setMatch) {
|
|
308
|
+
try {
|
|
309
|
+
const expr = parseExpression(setMatch[2]);
|
|
310
|
+
ast.push({
|
|
311
|
+
type: "set",
|
|
312
|
+
name: setMatch[1],
|
|
313
|
+
value: expr
|
|
314
|
+
});
|
|
315
|
+
} catch (error) {
|
|
316
|
+
errors.push(createError(`Invalid set expression: ${error.message}`));
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
errors.push(createError("Invalid set syntax"));
|
|
320
|
+
}
|
|
321
|
+
} else if (statement.startsWith("if ")) {
|
|
322
|
+
const condition = statement.substring(3).trim();
|
|
323
|
+
try {
|
|
324
|
+
const expr = parseExpression(condition);
|
|
325
|
+
const { trueBranch, elifBranches, elseBranch, parseErrors } = parseIfBlock();
|
|
326
|
+
errors.push(...parseErrors);
|
|
327
|
+
ast.push({
|
|
328
|
+
type: "if",
|
|
329
|
+
condition: expr,
|
|
330
|
+
trueBranch,
|
|
331
|
+
elifBranches: elifBranches.length > 0 ? elifBranches : void 0,
|
|
332
|
+
elseBranch: elseBranch.length > 0 ? elseBranch : void 0
|
|
333
|
+
});
|
|
334
|
+
} catch (error) {
|
|
335
|
+
errors.push(createError(`Invalid if expression: ${error.message}`));
|
|
336
|
+
}
|
|
337
|
+
} else if (statement.startsWith("include ")) {
|
|
338
|
+
const includeMatch = /^include\s+["']([^"']+)["']/.exec(statement);
|
|
339
|
+
if (includeMatch) {
|
|
340
|
+
ast.push({
|
|
341
|
+
type: "include",
|
|
342
|
+
path: includeMatch[1]
|
|
343
|
+
});
|
|
344
|
+
} else {
|
|
345
|
+
errors.push(createError("Invalid include syntax"));
|
|
346
|
+
}
|
|
347
|
+
} else if (statement.startsWith("switch ")) {
|
|
348
|
+
const switchExpr = statement.substring(7).trim();
|
|
349
|
+
try {
|
|
350
|
+
const expr = parseExpression(switchExpr);
|
|
351
|
+
const { cases, parseErrors } = parseSwitchBlock();
|
|
352
|
+
errors.push(...parseErrors);
|
|
353
|
+
ast.push({
|
|
354
|
+
type: "switch",
|
|
355
|
+
expression: expr,
|
|
356
|
+
cases
|
|
357
|
+
});
|
|
358
|
+
} catch (error) {
|
|
359
|
+
errors.push(createError(`Invalid switch expression: ${error.message}`));
|
|
360
|
+
}
|
|
361
|
+
} else if (statement.startsWith("leveloffset ")) {
|
|
362
|
+
const offsetMatch = /^leveloffset\s+([-+]?\d+)$/.exec(statement);
|
|
363
|
+
if (!offsetMatch) {
|
|
364
|
+
errors.push(createError("Invalid leveloffset syntax"));
|
|
365
|
+
} else {
|
|
366
|
+
const offsetStr = offsetMatch[1];
|
|
367
|
+
const isRelative = offsetStr.startsWith("+") || offsetStr.startsWith("-");
|
|
368
|
+
const offset = parseInt(offsetStr, 10);
|
|
369
|
+
if (isNaN(offset)) {
|
|
370
|
+
errors.push(createError("Invalid leveloffset value"));
|
|
371
|
+
} else {
|
|
372
|
+
let depth = 0;
|
|
373
|
+
let searchPos = position;
|
|
374
|
+
let endPos2 = -1;
|
|
375
|
+
while (searchPos < template.length) {
|
|
376
|
+
const leveloffsetMatch = /{%-?\s*leveloffset\s+[-+]?\d+\s*-?%}/.exec(template.substring(searchPos));
|
|
377
|
+
const endleveloffsetMatch = /{%-?\s*endleveloffset\s*-?%}/.exec(template.substring(searchPos));
|
|
378
|
+
const leveloffsetPos = leveloffsetMatch ? searchPos + leveloffsetMatch.index : Infinity;
|
|
379
|
+
const endleveloffsetPos = endleveloffsetMatch ? searchPos + endleveloffsetMatch.index : Infinity;
|
|
380
|
+
if (leveloffsetPos < endleveloffsetPos) {
|
|
381
|
+
depth++;
|
|
382
|
+
searchPos = leveloffsetPos + (leveloffsetMatch?.[0].length || 0);
|
|
383
|
+
} else if (endleveloffsetPos < Infinity) {
|
|
384
|
+
if (depth === 0) {
|
|
385
|
+
endPos2 = endleveloffsetPos;
|
|
386
|
+
break;
|
|
387
|
+
} else {
|
|
388
|
+
depth--;
|
|
389
|
+
searchPos = endleveloffsetPos + (endleveloffsetMatch?.[0].length || 0);
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (endPos2 === -1) {
|
|
396
|
+
errors.push(createError("Missing endleveloffset"));
|
|
397
|
+
} else {
|
|
398
|
+
const bodyTemplate = extractTo(endPos2);
|
|
399
|
+
const endMatch = /{%-?\s*endleveloffset\s*-?%}/.exec(template.substring(position));
|
|
400
|
+
if (endMatch) {
|
|
401
|
+
advance(endMatch[0].length);
|
|
402
|
+
}
|
|
403
|
+
const bodyResult = parse(bodyTemplate);
|
|
404
|
+
errors.push(...bodyResult.errors);
|
|
405
|
+
ast.push({
|
|
406
|
+
type: "leveloffset",
|
|
407
|
+
offset,
|
|
408
|
+
isRelative,
|
|
409
|
+
body: bodyResult.ast
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return { ast, errors };
|
|
418
|
+
function findEndTag(tagName) {
|
|
419
|
+
const pattern = new RegExp(`{%\\s*${tagName}\\s*%}`);
|
|
420
|
+
const match = pattern.exec(template.substring(position));
|
|
421
|
+
if (match) {
|
|
422
|
+
return {
|
|
423
|
+
start: position + match.index,
|
|
424
|
+
length: match[0].length
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
function parseIfBlock() {
|
|
430
|
+
const elifBranches = [];
|
|
431
|
+
const parseErrors = [];
|
|
432
|
+
let elseBranch = [];
|
|
433
|
+
let depth = 0;
|
|
434
|
+
let searchPos = position;
|
|
435
|
+
while (searchPos < template.length) {
|
|
436
|
+
const ifMatch = /{%-?\s*if\s+/.exec(template.substring(searchPos));
|
|
437
|
+
const elifMatch = /{%-?\s*elif\s+/.exec(template.substring(searchPos));
|
|
438
|
+
const elseMatch = /{%-?\s*else\s*-?%}/.exec(template.substring(searchPos));
|
|
439
|
+
const endifMatch = /{%-?\s*endif\s*-?%}/.exec(template.substring(searchPos));
|
|
440
|
+
const matches = [
|
|
441
|
+
{ type: "if", match: ifMatch, pos: ifMatch ? searchPos + ifMatch.index : Infinity },
|
|
442
|
+
{ type: "elif", match: elifMatch, pos: elifMatch ? searchPos + elifMatch.index : Infinity },
|
|
443
|
+
{ type: "else", match: elseMatch, pos: elseMatch ? searchPos + elseMatch.index : Infinity },
|
|
444
|
+
{ type: "endif", match: endifMatch, pos: endifMatch ? searchPos + endifMatch.index : Infinity }
|
|
445
|
+
].sort((a, b) => a.pos - b.pos);
|
|
446
|
+
const nearest = matches[0];
|
|
447
|
+
if (!nearest.match || nearest.pos === Infinity) {
|
|
448
|
+
parseErrors.push(createError("Missing endif"));
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
if (nearest.type === "if") {
|
|
452
|
+
depth++;
|
|
453
|
+
searchPos = nearest.pos + nearest.match[0].length;
|
|
454
|
+
} else if (nearest.type === "endif") {
|
|
455
|
+
if (depth === 0) {
|
|
456
|
+
const bodyTemplate = extractTo(nearest.pos);
|
|
457
|
+
advance(nearest.match[0].length);
|
|
458
|
+
const bodyResult = parse(bodyTemplate);
|
|
459
|
+
parseErrors.push(...bodyResult.errors);
|
|
460
|
+
return {
|
|
461
|
+
trueBranch: bodyResult.ast,
|
|
462
|
+
elifBranches,
|
|
463
|
+
elseBranch,
|
|
464
|
+
parseErrors
|
|
465
|
+
};
|
|
466
|
+
} else {
|
|
467
|
+
depth--;
|
|
468
|
+
searchPos = nearest.pos + nearest.match[0].length;
|
|
469
|
+
}
|
|
470
|
+
} else if (depth === 0 && (nearest.type === "elif" || nearest.type === "else")) {
|
|
471
|
+
const bodyTemplate = extractTo(nearest.pos);
|
|
472
|
+
const bodyResult = parse(bodyTemplate);
|
|
473
|
+
if (elifBranches.length === 0 && elseBranch.length === 0) {
|
|
474
|
+
parseErrors.push(...bodyResult.errors);
|
|
475
|
+
const trueBranch = bodyResult.ast;
|
|
476
|
+
if (nearest.type === "elif") {
|
|
477
|
+
advance(nearest.match[0].length);
|
|
478
|
+
const condMatch = /^([^%]+)%}/.exec(template.substring(position));
|
|
479
|
+
if (condMatch) {
|
|
480
|
+
const condStr = condMatch[1].trim();
|
|
481
|
+
advance(condMatch[0].length);
|
|
482
|
+
try {
|
|
483
|
+
const elifCondition = parseExpression(condStr);
|
|
484
|
+
const elifResult = parseIfBlock();
|
|
485
|
+
parseErrors.push(...elifResult.parseErrors);
|
|
486
|
+
elifBranches.push({
|
|
487
|
+
condition: elifCondition,
|
|
488
|
+
body: elifResult.trueBranch
|
|
489
|
+
});
|
|
490
|
+
elifBranches.push(...elifResult.elifBranches || []);
|
|
491
|
+
elseBranch = elifResult.elseBranch;
|
|
492
|
+
return {
|
|
493
|
+
trueBranch,
|
|
494
|
+
elifBranches,
|
|
495
|
+
elseBranch,
|
|
496
|
+
parseErrors
|
|
497
|
+
};
|
|
498
|
+
} catch (error) {
|
|
499
|
+
parseErrors.push(createError(`Invalid elif expression: ${error.message}`));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
advance(nearest.match[0].length);
|
|
504
|
+
const endifMatch2 = findEndTag("endif");
|
|
505
|
+
if (!endifMatch2) {
|
|
506
|
+
parseErrors.push(createError("Missing endif after else"));
|
|
507
|
+
} else {
|
|
508
|
+
const elseBodyTemplate = extractTo(endifMatch2.start);
|
|
509
|
+
advance(endifMatch2.length);
|
|
510
|
+
const elseBodyResult = parse(elseBodyTemplate);
|
|
511
|
+
parseErrors.push(...elseBodyResult.errors);
|
|
512
|
+
elseBranch = elseBodyResult.ast;
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
trueBranch,
|
|
516
|
+
elifBranches,
|
|
517
|
+
elseBranch,
|
|
518
|
+
parseErrors
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
searchPos = nearest.pos + nearest.match[0].length;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
trueBranch: [],
|
|
528
|
+
elifBranches,
|
|
529
|
+
elseBranch,
|
|
530
|
+
parseErrors
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function parseSwitchBlock() {
|
|
534
|
+
const cases = [];
|
|
535
|
+
const parseErrors = [];
|
|
536
|
+
while (position < template.length) {
|
|
537
|
+
const caseMatch = /{%-?\s*case\s+/.exec(template.substring(position));
|
|
538
|
+
const endswitchMatch = /{%-?\s*endswitch\s*-?%}/.exec(template.substring(position));
|
|
539
|
+
const casePos = caseMatch ? position + caseMatch.index : Infinity;
|
|
540
|
+
const endswitchPos = endswitchMatch ? position + endswitchMatch.index : Infinity;
|
|
541
|
+
if (endswitchPos < casePos) {
|
|
542
|
+
if (cases.length > 0 && position < endswitchPos) {
|
|
543
|
+
const bodyTemplate = extractTo(endswitchPos);
|
|
544
|
+
const bodyResult = parse(bodyTemplate);
|
|
545
|
+
parseErrors.push(...bodyResult.errors);
|
|
546
|
+
cases[cases.length - 1].body = bodyResult.ast;
|
|
547
|
+
}
|
|
548
|
+
advance(endswitchPos - position + (endswitchMatch?.[0].length || 0));
|
|
549
|
+
break;
|
|
550
|
+
} else if (casePos < Infinity) {
|
|
551
|
+
if (cases.length > 0 && position < casePos) {
|
|
552
|
+
const bodyTemplate = extractTo(casePos);
|
|
553
|
+
const bodyResult = parse(bodyTemplate);
|
|
554
|
+
parseErrors.push(...bodyResult.errors);
|
|
555
|
+
cases[cases.length - 1].body = bodyResult.ast;
|
|
556
|
+
} else if (position < casePos) {
|
|
557
|
+
extractTo(casePos);
|
|
558
|
+
}
|
|
559
|
+
advance(caseMatch[0].length);
|
|
560
|
+
const valueMatch = /([^%]+)%}/.exec(template.substring(position));
|
|
561
|
+
if (valueMatch) {
|
|
562
|
+
const valueStr = valueMatch[1].trim();
|
|
563
|
+
advance(valueMatch[0].length);
|
|
564
|
+
try {
|
|
565
|
+
const caseValue = parseExpression(valueStr);
|
|
566
|
+
cases.push({
|
|
567
|
+
value: caseValue,
|
|
568
|
+
body: []
|
|
569
|
+
// Will be filled in next iteration or at endswitch
|
|
570
|
+
});
|
|
571
|
+
} catch (error) {
|
|
572
|
+
parseErrors.push(createError(`Invalid case value: ${error.message}`));
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
parseErrors.push(createError("Invalid case syntax"));
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
parseErrors.push(createError("Missing endswitch"));
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return { cases, parseErrors };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/context.ts
|
|
588
|
+
var Context = class _Context {
|
|
589
|
+
constructor(parent, options) {
|
|
590
|
+
this.variables = /* @__PURE__ */ new Map();
|
|
591
|
+
this.parent = parent || null;
|
|
592
|
+
this.options = options || parent?.options || {};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Get a variable from the context (supports dot notation)
|
|
596
|
+
* @param name - Variable name (can use dot notation like "obj.prop.subprop")
|
|
597
|
+
* @returns The variable value or undefined
|
|
598
|
+
*/
|
|
599
|
+
get(name) {
|
|
600
|
+
const normalizedName = this.options.hyphenToUnderscore ? name.replace(/-/g, "_") : name;
|
|
601
|
+
const parts = normalizedName.split(".");
|
|
602
|
+
const rootName = parts[0];
|
|
603
|
+
let value;
|
|
604
|
+
if (this.variables.has(rootName)) {
|
|
605
|
+
value = this.variables.get(rootName);
|
|
606
|
+
} else if (this.parent) {
|
|
607
|
+
value = this.parent.get(rootName);
|
|
608
|
+
} else {
|
|
609
|
+
return void 0;
|
|
610
|
+
}
|
|
611
|
+
for (let i = 1; i < parts.length; i++) {
|
|
612
|
+
if (value === null || value === void 0) {
|
|
613
|
+
return void 0;
|
|
614
|
+
}
|
|
615
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
616
|
+
return void 0;
|
|
617
|
+
}
|
|
618
|
+
if (!Object.prototype.hasOwnProperty.call(value, parts[i])) {
|
|
619
|
+
return void 0;
|
|
620
|
+
}
|
|
621
|
+
value = value[parts[i]];
|
|
622
|
+
}
|
|
623
|
+
return value;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Set a variable in the current context
|
|
627
|
+
* @param name - Variable name (can use dot notation)
|
|
628
|
+
* @param value - Value to set
|
|
629
|
+
*/
|
|
630
|
+
set(name, value) {
|
|
631
|
+
const parts = name.split(".");
|
|
632
|
+
if (parts.length === 1) {
|
|
633
|
+
this.variables.set(name, value);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const rootName = parts[0];
|
|
637
|
+
let target = this.variables.get(rootName);
|
|
638
|
+
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
639
|
+
target = {};
|
|
640
|
+
this.variables.set(rootName, target);
|
|
641
|
+
}
|
|
642
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
643
|
+
const key = parts[i];
|
|
644
|
+
if (!target[key] || typeof target[key] !== "object" || Array.isArray(target[key])) {
|
|
645
|
+
target[key] = {};
|
|
646
|
+
}
|
|
647
|
+
target = target[key];
|
|
648
|
+
}
|
|
649
|
+
target[parts[parts.length - 1]] = value;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Create a child scope
|
|
653
|
+
* @returns A new Context with this context as parent
|
|
654
|
+
*/
|
|
655
|
+
push() {
|
|
656
|
+
return new _Context(this, this.options);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Return to parent scope
|
|
660
|
+
* @returns The parent context or null if at root
|
|
661
|
+
*/
|
|
662
|
+
pop() {
|
|
663
|
+
return this.parent;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Create a context from a plain object
|
|
667
|
+
* @param data - Plain object to convert to context
|
|
668
|
+
* @param options - Context options
|
|
669
|
+
* @returns New Context instance
|
|
670
|
+
*/
|
|
671
|
+
static from(data, options) {
|
|
672
|
+
const ctx = new _Context(void 0, options);
|
|
673
|
+
for (const [key, value] of Object.entries(data)) {
|
|
674
|
+
ctx.set(key, value);
|
|
675
|
+
}
|
|
676
|
+
return ctx;
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// src/loader-fetch.ts
|
|
681
|
+
var FetchLoader = class {
|
|
682
|
+
async load(path, basePath) {
|
|
683
|
+
const url = basePath ? new URL(path, basePath).href : path;
|
|
684
|
+
const fetchUrl = `${url}?preventCache=${Date.now()}`;
|
|
685
|
+
const response = await fetch(fetchUrl);
|
|
686
|
+
if (!response.ok) {
|
|
687
|
+
throw new Error(`Failed to load ${url}: ${response.status} ${response.statusText}`);
|
|
688
|
+
}
|
|
689
|
+
return await response.text();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/renderer.ts
|
|
694
|
+
function transformHeadings(text, offset) {
|
|
695
|
+
if (offset === 0) {
|
|
696
|
+
return text;
|
|
697
|
+
}
|
|
698
|
+
return text.replace(/^(#{1,6})(\s+)/gm, (_match, hashes, space) => {
|
|
699
|
+
const currentLevel = hashes.length;
|
|
700
|
+
const newLevel = Math.max(1, Math.min(6, currentLevel + offset));
|
|
701
|
+
return "#".repeat(newLevel) + space;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function resolvePath(path, basePath) {
|
|
705
|
+
if (path.startsWith("/")) {
|
|
706
|
+
return path;
|
|
707
|
+
}
|
|
708
|
+
const base = basePath.endsWith("/") ? basePath : basePath + "/";
|
|
709
|
+
const combined = base + path;
|
|
710
|
+
const parts = combined.split("/");
|
|
711
|
+
const resolved = [];
|
|
712
|
+
for (const part of parts) {
|
|
713
|
+
if (part === "." || part === "") {
|
|
714
|
+
continue;
|
|
715
|
+
} else if (part === "..") {
|
|
716
|
+
resolved.pop();
|
|
717
|
+
} else {
|
|
718
|
+
resolved.push(part);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const prefix = combined.startsWith("/") ? "/" : "";
|
|
722
|
+
return prefix + resolved.join("/");
|
|
723
|
+
}
|
|
724
|
+
async function render(template, options = {}) {
|
|
725
|
+
const {
|
|
726
|
+
loader = new FetchLoader(),
|
|
727
|
+
context: initialContext = {},
|
|
728
|
+
basePath = "",
|
|
729
|
+
maxIncludeDepth = 10,
|
|
730
|
+
timeout = 5e3,
|
|
731
|
+
hyphenToUnderscore = false,
|
|
732
|
+
undefinedBehavior = "empty"
|
|
733
|
+
} = options;
|
|
734
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
735
|
+
setTimeout(() => reject(new Error("Template rendering timeout")), timeout);
|
|
736
|
+
});
|
|
737
|
+
const renderPromise = renderInternal(template, {
|
|
738
|
+
loader,
|
|
739
|
+
context: Context.from(
|
|
740
|
+
{
|
|
741
|
+
...initialContext,
|
|
742
|
+
// Built-in variables
|
|
743
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
744
|
+
timestamp: Date.now(),
|
|
745
|
+
_levelOffset: 0
|
|
746
|
+
},
|
|
747
|
+
{ hyphenToUnderscore }
|
|
748
|
+
),
|
|
749
|
+
includeDepth: 0,
|
|
750
|
+
maxIncludeDepth,
|
|
751
|
+
basePath,
|
|
752
|
+
undefinedBehavior
|
|
753
|
+
});
|
|
754
|
+
return await Promise.race([renderPromise, timeoutPromise]);
|
|
755
|
+
}
|
|
756
|
+
async function renderInternal(template, options) {
|
|
757
|
+
const { includeDepth, maxIncludeDepth } = options;
|
|
758
|
+
if (includeDepth > maxIncludeDepth) {
|
|
759
|
+
throw new Error(`Maximum include depth (${maxIncludeDepth}) exceeded`);
|
|
760
|
+
}
|
|
761
|
+
const parseResult = parse(template);
|
|
762
|
+
if (parseResult.errors.length > 0) {
|
|
763
|
+
const errorMessages = parseResult.errors.map((e) => e.message).join(", ");
|
|
764
|
+
throw new Error(`Parse errors: ${errorMessages}`);
|
|
765
|
+
}
|
|
766
|
+
return await renderNodes(parseResult.ast, options);
|
|
767
|
+
}
|
|
768
|
+
async function renderNodes(nodes, options) {
|
|
769
|
+
const parts = [];
|
|
770
|
+
for (const node of nodes) {
|
|
771
|
+
parts.push(await renderNode(node, options));
|
|
772
|
+
}
|
|
773
|
+
return parts.join("");
|
|
774
|
+
}
|
|
775
|
+
async function renderNode(node, options) {
|
|
776
|
+
const { loader, context, includeDepth, maxIncludeDepth, basePath } = options;
|
|
777
|
+
switch (node.type) {
|
|
778
|
+
case "text": {
|
|
779
|
+
const currentOffset = context.get("_levelOffset") || 0;
|
|
780
|
+
if (currentOffset === 0) {
|
|
781
|
+
return node.value;
|
|
782
|
+
}
|
|
783
|
+
return transformHeadings(node.value, currentOffset);
|
|
784
|
+
}
|
|
785
|
+
case "variable": {
|
|
786
|
+
const value = context.get(node.name);
|
|
787
|
+
if (value === void 0 || value === null) {
|
|
788
|
+
switch (options.undefinedBehavior) {
|
|
789
|
+
case "throw":
|
|
790
|
+
throw new Error(`Undefined variable: ${node.name}`);
|
|
791
|
+
case "preserve":
|
|
792
|
+
return `{{ ${node.name} }}`;
|
|
793
|
+
case "empty":
|
|
794
|
+
default:
|
|
795
|
+
return "";
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const str = String(value);
|
|
799
|
+
if (str.includes("{{") || str.includes("{%")) {
|
|
800
|
+
return await renderInternal(str, options);
|
|
801
|
+
}
|
|
802
|
+
return str;
|
|
803
|
+
}
|
|
804
|
+
case "set": {
|
|
805
|
+
const value = evaluateExpression(node.value, context);
|
|
806
|
+
context.set(node.name, value);
|
|
807
|
+
return "";
|
|
808
|
+
}
|
|
809
|
+
case "comment":
|
|
810
|
+
return "";
|
|
811
|
+
case "if": {
|
|
812
|
+
if (options.undefinedBehavior === "preserve") {
|
|
813
|
+
const undefinedRefs = findUndefinedRefs(node.condition, context);
|
|
814
|
+
if (undefinedRefs.length > 0) {
|
|
815
|
+
return ifNodeToSource(node);
|
|
816
|
+
}
|
|
817
|
+
if (node.elifBranches) {
|
|
818
|
+
for (const elif of node.elifBranches) {
|
|
819
|
+
if (findUndefinedRefs(elif.condition, context).length > 0) {
|
|
820
|
+
return ifNodeToSource(node);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const condition = evaluateExpression(node.condition, context);
|
|
826
|
+
if (isTruthy2(condition)) {
|
|
827
|
+
return await renderNodes(node.trueBranch, options);
|
|
828
|
+
}
|
|
829
|
+
if (node.elifBranches) {
|
|
830
|
+
for (const elifBranch of node.elifBranches) {
|
|
831
|
+
const elifCondition = evaluateExpression(elifBranch.condition, context);
|
|
832
|
+
if (isTruthy2(elifCondition)) {
|
|
833
|
+
return await renderNodes(elifBranch.body, options);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (node.elseBranch) {
|
|
838
|
+
return await renderNodes(node.elseBranch, options);
|
|
839
|
+
}
|
|
840
|
+
return "";
|
|
841
|
+
}
|
|
842
|
+
case "include": {
|
|
843
|
+
try {
|
|
844
|
+
const includedContent = await loader.load(node.path, basePath);
|
|
845
|
+
let newBasePath = basePath;
|
|
846
|
+
if (node.path.includes("://")) {
|
|
847
|
+
const url = new URL(node.path);
|
|
848
|
+
newBasePath = url.href.substring(0, url.href.lastIndexOf("/") + 1);
|
|
849
|
+
} else if (basePath) {
|
|
850
|
+
if (basePath.includes("://")) {
|
|
851
|
+
const url = new URL(node.path, basePath);
|
|
852
|
+
newBasePath = url.href.substring(0, url.href.lastIndexOf("/") + 1);
|
|
853
|
+
} else {
|
|
854
|
+
const resolvedPath = resolvePath(node.path, basePath);
|
|
855
|
+
const lastSlash = resolvedPath.lastIndexOf("/");
|
|
856
|
+
newBasePath = lastSlash >= 0 ? resolvedPath.substring(0, lastSlash + 1) : basePath;
|
|
857
|
+
}
|
|
858
|
+
} else if (node.path.includes("/")) {
|
|
859
|
+
const lastSlash = node.path.lastIndexOf("/");
|
|
860
|
+
newBasePath = node.path.substring(0, lastSlash + 1);
|
|
861
|
+
}
|
|
862
|
+
return await renderInternal(includedContent, {
|
|
863
|
+
loader,
|
|
864
|
+
context,
|
|
865
|
+
includeDepth: includeDepth + 1,
|
|
866
|
+
maxIncludeDepth,
|
|
867
|
+
basePath: newBasePath,
|
|
868
|
+
undefinedBehavior: options.undefinedBehavior
|
|
869
|
+
});
|
|
870
|
+
} catch (error) {
|
|
871
|
+
if (options.undefinedBehavior === "preserve") {
|
|
872
|
+
return `{% include "${node.path}" %}`;
|
|
873
|
+
}
|
|
874
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
875
|
+
console.error(`Failed to include ${node.path}:`, message);
|
|
876
|
+
return `<!-- Include error: ${node.path} -->`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
case "switch": {
|
|
880
|
+
const switchValue = evaluateExpression(node.expression, context);
|
|
881
|
+
for (const caseItem of node.cases) {
|
|
882
|
+
const caseValue = evaluateExpression(caseItem.value, context);
|
|
883
|
+
if (switchValue == caseValue) {
|
|
884
|
+
return await renderNodes(caseItem.body, options);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return "";
|
|
888
|
+
}
|
|
889
|
+
case "leveloffset": {
|
|
890
|
+
const parentOffset = context.get("_levelOffset") || 0;
|
|
891
|
+
const newOffset = node.isRelative ? parentOffset + node.offset : node.offset;
|
|
892
|
+
const previousOffset = parentOffset;
|
|
893
|
+
context.set("_levelOffset", newOffset);
|
|
894
|
+
try {
|
|
895
|
+
const result = await renderNodes(node.body, options);
|
|
896
|
+
return result;
|
|
897
|
+
} finally {
|
|
898
|
+
context.set("_levelOffset", previousOffset);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
default:
|
|
902
|
+
const _exhaustive = node;
|
|
903
|
+
return _exhaustive;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function isTruthy2(value) {
|
|
907
|
+
if (value === void 0 || value === null || value === false) {
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
if (value === 0 || value === "") {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
function findUndefinedRefs(expr, context) {
|
|
916
|
+
const out = [];
|
|
917
|
+
function walk(e) {
|
|
918
|
+
switch (e.type) {
|
|
919
|
+
case "variable":
|
|
920
|
+
if (context.get(e.name) === void 0)
|
|
921
|
+
out.push(e.name);
|
|
922
|
+
break;
|
|
923
|
+
case "binary":
|
|
924
|
+
walk(e.left);
|
|
925
|
+
walk(e.right);
|
|
926
|
+
break;
|
|
927
|
+
case "unary":
|
|
928
|
+
walk(e.operand);
|
|
929
|
+
break;
|
|
930
|
+
case "literal":
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
walk(expr);
|
|
935
|
+
return out;
|
|
936
|
+
}
|
|
937
|
+
function expressionToSource(expr) {
|
|
938
|
+
switch (expr.type) {
|
|
939
|
+
case "literal":
|
|
940
|
+
if (typeof expr.value === "string")
|
|
941
|
+
return JSON.stringify(expr.value);
|
|
942
|
+
if (expr.value === null)
|
|
943
|
+
return "null";
|
|
944
|
+
return String(expr.value);
|
|
945
|
+
case "variable":
|
|
946
|
+
return expr.name;
|
|
947
|
+
case "unary":
|
|
948
|
+
return `not ${expressionToSource(expr.operand)}`;
|
|
949
|
+
case "binary":
|
|
950
|
+
return `(${expressionToSource(expr.left)} ${expr.operator} ${expressionToSource(expr.right)})`;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function nodesToSource(nodes) {
|
|
954
|
+
return nodes.map(nodeToSource).join("");
|
|
955
|
+
}
|
|
956
|
+
function nodeToSource(node) {
|
|
957
|
+
switch (node.type) {
|
|
958
|
+
case "text":
|
|
959
|
+
return node.value;
|
|
960
|
+
case "variable":
|
|
961
|
+
return `{{ ${node.name} }}`;
|
|
962
|
+
case "set":
|
|
963
|
+
return `{% set ${node.name} = ${expressionToSource(node.value)} %}`;
|
|
964
|
+
case "comment":
|
|
965
|
+
return "{# \u2026 #}";
|
|
966
|
+
case "if":
|
|
967
|
+
return ifNodeToSource(node);
|
|
968
|
+
case "include":
|
|
969
|
+
return `{% include "${node.path}" %}`;
|
|
970
|
+
case "leveloffset": {
|
|
971
|
+
const sign = node.isRelative && node.offset >= 0 ? "+" : "";
|
|
972
|
+
return `{% leveloffset ${sign}${node.offset} %}${nodesToSource(node.body)}{% endleveloffset %}`;
|
|
973
|
+
}
|
|
974
|
+
case "switch": {
|
|
975
|
+
const cases = node.cases.map((c) => `{% case ${expressionToSource(c.value)} %}${nodesToSource(c.body)}`).join("");
|
|
976
|
+
return `{% switch ${expressionToSource(node.expression)} %}${cases}{% endswitch %}`;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function ifNodeToSource(node) {
|
|
981
|
+
const parts = [`{% if ${expressionToSource(node.condition)} %}`, nodesToSource(node.trueBranch)];
|
|
982
|
+
if (node.elifBranches) {
|
|
983
|
+
for (const elif of node.elifBranches) {
|
|
984
|
+
parts.push(`{% elif ${expressionToSource(elif.condition)} %}`, nodesToSource(elif.body));
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (node.elseBranch) {
|
|
988
|
+
parts.push(`{% else %}`, nodesToSource(node.elseBranch));
|
|
989
|
+
}
|
|
990
|
+
parts.push(`{% endif %}`);
|
|
991
|
+
return parts.join("");
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/loader-memory.ts
|
|
995
|
+
var MemoryLoader = class {
|
|
996
|
+
constructor(files = {}) {
|
|
997
|
+
this.files = new Map(Object.entries(files));
|
|
998
|
+
}
|
|
999
|
+
addFile(path, content) {
|
|
1000
|
+
this.files.set(path, content);
|
|
1001
|
+
}
|
|
1002
|
+
async load(path, basePath) {
|
|
1003
|
+
const combined = basePath && !path.startsWith("/") ? `${basePath}/${path}` : path;
|
|
1004
|
+
const parts = combined.split("/");
|
|
1005
|
+
const normalized = [];
|
|
1006
|
+
for (const part of parts) {
|
|
1007
|
+
if (part === "." || part === "")
|
|
1008
|
+
continue;
|
|
1009
|
+
if (part === "..") {
|
|
1010
|
+
normalized.pop();
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
normalized.push(part);
|
|
1014
|
+
}
|
|
1015
|
+
const prefix = combined.startsWith("/") ? "/" : "";
|
|
1016
|
+
const resolvedPath = prefix + normalized.join("/");
|
|
1017
|
+
const content = this.files.get(resolvedPath);
|
|
1018
|
+
if (content === void 0) {
|
|
1019
|
+
throw new Error(`File not found: ${resolvedPath}`);
|
|
1020
|
+
}
|
|
1021
|
+
return content;
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
return __toCommonJS(browser_exports);
|
|
1025
|
+
})();
|
|
1026
|
+
//# sourceMappingURL=index.umd.js.map
|