@dogsbay/minja 0.2.0-beta.100

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