@formfeed/engine 0.3.13 → 0.3.15
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/index.cjs +111 -57
- package/index.js +111 -57
- package/package.json +2 -2
- package/types/lib/errors.d.cts +7 -0
- package/types/lib/errors.d.ts +7 -0
package/index.cjs
CHANGED
|
@@ -229,6 +229,15 @@ var EngineSyntaxError = class extends Error {
|
|
|
229
229
|
return { line: this.line, column: this.column };
|
|
230
230
|
}
|
|
231
231
|
};
|
|
232
|
+
function notText(engine, source) {
|
|
233
|
+
const kind = source === null || source === void 0 ? String(source) : Array.isArray(source) ? "an array" : typeof source === "object" ? "an object" : `a ${typeof source}`;
|
|
234
|
+
return new EngineSyntaxError(
|
|
235
|
+
engine,
|
|
236
|
+
`A template must be text, not ${kind}`,
|
|
237
|
+
1,
|
|
238
|
+
1
|
|
239
|
+
);
|
|
240
|
+
}
|
|
232
241
|
var RenderLimitError = class extends Error {
|
|
233
242
|
constructor(limit, message) {
|
|
234
243
|
super(message);
|
|
@@ -1842,7 +1851,8 @@ var builtinRoots = /* @__PURE__ */ new Set([
|
|
|
1842
1851
|
]);
|
|
1843
1852
|
function withBrand(data, ctx) {
|
|
1844
1853
|
const root = data ?? {};
|
|
1845
|
-
if (!ctx.brand || typeof root !== "object" || Array.isArray(root))
|
|
1854
|
+
if (!ctx.brand || typeof root !== "object" || Array.isArray(root))
|
|
1855
|
+
return root;
|
|
1846
1856
|
return { brand: ctx.brand, ...root };
|
|
1847
1857
|
}
|
|
1848
1858
|
function createInstance(ctx) {
|
|
@@ -2141,6 +2151,11 @@ var HandlebarsCompiled = class {
|
|
|
2141
2151
|
}
|
|
2142
2152
|
const src = ctx.partials(inc.name);
|
|
2143
2153
|
if (src === void 0) continue;
|
|
2154
|
+
if (typeof src !== "string")
|
|
2155
|
+
throw new RenderError(
|
|
2156
|
+
"handlebars",
|
|
2157
|
+
`Partial "${inc.name}" is not text`
|
|
2158
|
+
);
|
|
2144
2159
|
partials[inc.name] = src;
|
|
2145
2160
|
collect(src, depth + 1);
|
|
2146
2161
|
}
|
|
@@ -2168,6 +2183,7 @@ var HandlebarsCompiled = class {
|
|
|
2168
2183
|
var handlebarsEngine = {
|
|
2169
2184
|
id: "handlebars",
|
|
2170
2185
|
compile(source, opts = {}) {
|
|
2186
|
+
if (typeof source !== "string") throw notText("handlebars", source);
|
|
2171
2187
|
const name = opts.name ?? "template";
|
|
2172
2188
|
try {
|
|
2173
2189
|
createInstance(validationContext).precompile(source, { strict: false });
|
|
@@ -2436,7 +2452,8 @@ var stringMethods = {
|
|
|
2436
2452
|
};
|
|
2437
2453
|
function keywordArgs(args) {
|
|
2438
2454
|
const last = args.at(-1);
|
|
2439
|
-
if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords"))
|
|
2455
|
+
if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords"))
|
|
2456
|
+
return void 0;
|
|
2440
2457
|
const { __keywords: _marker, ...named } = last;
|
|
2441
2458
|
return named;
|
|
2442
2459
|
}
|
|
@@ -2450,11 +2467,15 @@ function namespace(...args) {
|
|
|
2450
2467
|
return ns;
|
|
2451
2468
|
}
|
|
2452
2469
|
function setNamespaceAttribute(target, key, value) {
|
|
2453
|
-
if (!(target instanceof JinjaNamespace))
|
|
2470
|
+
if (!(target instanceof JinjaNamespace))
|
|
2471
|
+
throw new Error("Cannot assign attribute on non-namespace object");
|
|
2454
2472
|
target[String(key)] = value;
|
|
2455
2473
|
}
|
|
2456
2474
|
var toList = (v) => Array.isArray(v) ? v : v === void 0 || v === null ? [] : typeof v === "string" ? [...v] : [v];
|
|
2457
|
-
var attributeOf = (item, path) => String(path ?? "").split(".").reduce(
|
|
2475
|
+
var attributeOf = (item, path) => String(path ?? "").split(".").reduce(
|
|
2476
|
+
(acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0,
|
|
2477
|
+
item
|
|
2478
|
+
);
|
|
2458
2479
|
var jinjaTests = {
|
|
2459
2480
|
none: (v) => v === null,
|
|
2460
2481
|
true: (v) => v === true,
|
|
@@ -2475,50 +2496,65 @@ function runTest(self, name, value, arg) {
|
|
|
2475
2496
|
function installJinjaFilters(env) {
|
|
2476
2497
|
const lookup = env;
|
|
2477
2498
|
const helperTruncate = lookup.getFilter("truncate");
|
|
2478
|
-
lookup.addFilter(
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2499
|
+
lookup.addFilter(
|
|
2500
|
+
"truncate",
|
|
2501
|
+
function(value, ...args) {
|
|
2502
|
+
const named = keywordArgs(args);
|
|
2503
|
+
const p = named ? args.slice(0, -1) : args;
|
|
2504
|
+
if (!named && typeof p[1] !== "boolean" && p.length < 3)
|
|
2505
|
+
return helperTruncate.call(this, value, ...args);
|
|
2506
|
+
const s = String(value ?? "");
|
|
2507
|
+
const length = Number(p[0] ?? named?.["length"] ?? 255);
|
|
2508
|
+
const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
|
|
2509
|
+
const end = String(p[2] ?? named?.["end"] ?? "...");
|
|
2510
|
+
const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
|
|
2511
|
+
if (s.length <= length + leeway) return s;
|
|
2512
|
+
const cut = s.slice(0, Math.max(0, length - end.length));
|
|
2513
|
+
if (killwords) return cut + end;
|
|
2514
|
+
const space = cut.lastIndexOf(" ");
|
|
2515
|
+
return (space >= 0 ? cut.slice(0, space) : cut) + end;
|
|
2516
|
+
}
|
|
2517
|
+
);
|
|
2493
2518
|
const helperSum = lookup.getFilter("sum");
|
|
2494
|
-
lookup.addFilter(
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2519
|
+
lookup.addFilter(
|
|
2520
|
+
"sum",
|
|
2521
|
+
function(value, ...args) {
|
|
2522
|
+
const named = keywordArgs(args);
|
|
2523
|
+
if (!named) return helperSum.call(this, value, ...args);
|
|
2524
|
+
const p = args.slice(0, -1);
|
|
2525
|
+
const total = helperSum.call(
|
|
2526
|
+
this,
|
|
2527
|
+
value,
|
|
2528
|
+
p[0] ?? named["attribute"]
|
|
2529
|
+
);
|
|
2530
|
+
return total + Number(p[1] ?? named["start"] ?? 0);
|
|
2531
|
+
}
|
|
2532
|
+
);
|
|
2501
2533
|
const helperMap = lookup.getFilter("map");
|
|
2502
|
-
lookup.addFilter(
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
return
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
filter
|
|
2515
|
-
|
|
2516
|
-
|
|
2534
|
+
lookup.addFilter(
|
|
2535
|
+
"map",
|
|
2536
|
+
function(value, ...args) {
|
|
2537
|
+
const named = keywordArgs(args);
|
|
2538
|
+
const p = named ? args.slice(0, -1) : args;
|
|
2539
|
+
if (named && "attribute" in named)
|
|
2540
|
+
return toList(value).map((item) => {
|
|
2541
|
+
const v = attributeOf(item, named["attribute"]);
|
|
2542
|
+
return v === void 0 && "default" in named ? named["default"] : v;
|
|
2543
|
+
});
|
|
2544
|
+
const items = toList(value);
|
|
2545
|
+
if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
|
|
2546
|
+
let filter;
|
|
2547
|
+
try {
|
|
2548
|
+
filter = lookup.getFilter(p[0]);
|
|
2549
|
+
} catch {
|
|
2550
|
+
filter = void 0;
|
|
2551
|
+
}
|
|
2552
|
+
if (filter)
|
|
2553
|
+
return items.map((item) => filter.call(this, item, ...p.slice(1)));
|
|
2517
2554
|
}
|
|
2518
|
-
|
|
2555
|
+
return helperMap.call(this, value, ...args);
|
|
2519
2556
|
}
|
|
2520
|
-
|
|
2521
|
-
});
|
|
2557
|
+
);
|
|
2522
2558
|
const byAttribute = (keep) => function(value, attribute2, test, arg) {
|
|
2523
2559
|
return toList(value).filter((item) => {
|
|
2524
2560
|
const v = attributeOf(item, attribute2);
|
|
@@ -2547,14 +2583,16 @@ function pythonRepr(v) {
|
|
|
2547
2583
|
if (v === null || v === void 0) return "None";
|
|
2548
2584
|
if (v === true) return "True";
|
|
2549
2585
|
if (v === false) return "False";
|
|
2550
|
-
if (typeof v === "number")
|
|
2586
|
+
if (typeof v === "number")
|
|
2587
|
+
return Number.isNaN(v) ? "nan" : Number.isFinite(v) ? String(v) : v > 0 ? "inf" : "-inf";
|
|
2551
2588
|
if (typeof v === "string") {
|
|
2552
2589
|
const quote2 = v.includes("'") && !v.includes('"') ? '"' : "'";
|
|
2553
2590
|
const escaped = v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
2554
2591
|
return quote2 + (quote2 === "'" ? escaped.replace(/'/g, "\\'") : escaped) + quote2;
|
|
2555
2592
|
}
|
|
2556
2593
|
if (Array.isArray(v)) return `[${v.map(pythonRepr).join(", ")}]`;
|
|
2557
|
-
if (isPlainObject(v))
|
|
2594
|
+
if (isPlainObject(v))
|
|
2595
|
+
return `{${Object.entries(v).map(([k, item]) => `${pythonRepr(k)}: ${pythonRepr(item)}`).join(", ")}}`;
|
|
2558
2596
|
return String(v);
|
|
2559
2597
|
}
|
|
2560
2598
|
var compatInstalled = false;
|
|
@@ -2571,7 +2609,8 @@ function installCompat() {
|
|
|
2571
2609
|
import_nunjucks.default.runtime["formfeedSetAttribute"] = setNamespaceAttribute;
|
|
2572
2610
|
import_nunjucks.default.compiler.Compiler.prototype.compileSet = function(node, frame) {
|
|
2573
2611
|
const [target, ...more] = node.targets;
|
|
2574
|
-
if (!target || more.length || target.typename !== "LookupVal")
|
|
2612
|
+
if (!target || more.length || target.typename !== "LookupVal")
|
|
2613
|
+
return compileSet.call(this, node, frame);
|
|
2575
2614
|
this._emit("runtime.formfeedSetAttribute(");
|
|
2576
2615
|
this._compileExpression(target["target"], frame);
|
|
2577
2616
|
this._emit(", ");
|
|
@@ -2582,7 +2621,10 @@ function installCompat() {
|
|
|
2582
2621
|
this._emitLine(");");
|
|
2583
2622
|
};
|
|
2584
2623
|
const suppressValue = import_nunjucks.default.runtime.suppressValue;
|
|
2585
|
-
import_nunjucks.default.runtime.suppressValue = (val2, autoescape) => suppressValue(
|
|
2624
|
+
import_nunjucks.default.runtime.suppressValue = (val2, autoescape) => suppressValue(
|
|
2625
|
+
Array.isArray(val2) || isPlainObject(val2) ? pythonRepr(val2) : val2,
|
|
2626
|
+
autoescape
|
|
2627
|
+
);
|
|
2586
2628
|
const original = import_nunjucks.default.runtime.memberLookup;
|
|
2587
2629
|
import_nunjucks.default.runtime.memberLookup = (obj, val2, ...rest) => {
|
|
2588
2630
|
if (typeof obj === "string" && typeof val2 === "string" && Object.prototype.hasOwnProperty.call(stringMethods, val2)) {
|
|
@@ -2686,7 +2728,8 @@ function walk2(node, w) {
|
|
|
2686
2728
|
case "Symbol":
|
|
2687
2729
|
case "LookupVal": {
|
|
2688
2730
|
const p = pathOf(node);
|
|
2689
|
-
if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value)))
|
|
2731
|
+
if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value)))
|
|
2732
|
+
return;
|
|
2690
2733
|
if (p) {
|
|
2691
2734
|
const root = p.path[0] ?? "";
|
|
2692
2735
|
const bound = inScope(w, root);
|
|
@@ -2741,7 +2784,9 @@ function walk2(node, w) {
|
|
|
2741
2784
|
const nameNode = node["name"];
|
|
2742
2785
|
const names = nameNode.typename === "Array" ? nameNode.children ?? [] : [nameNode];
|
|
2743
2786
|
const root = source?.path[0] ?? "";
|
|
2744
|
-
const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(
|
|
2787
|
+
const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(
|
|
2788
|
+
sourceOf(w, root)
|
|
2789
|
+
) : source.path;
|
|
2745
2790
|
const scope = /* @__PURE__ */ new Map();
|
|
2746
2791
|
for (const n of names) {
|
|
2747
2792
|
const name = String(n.value);
|
|
@@ -2769,7 +2814,8 @@ function walk2(node, w) {
|
|
|
2769
2814
|
range: nodeRange(p.root, p.path.join(".").length),
|
|
2770
2815
|
kind: "assigned"
|
|
2771
2816
|
});
|
|
2772
|
-
if (p.path.length === 1 || !inScope(w, p.path[0] ?? ""))
|
|
2817
|
+
if (p.path.length === 1 || !inScope(w, p.path[0] ?? ""))
|
|
2818
|
+
w.scopes[0]?.set(p.path[0] ?? "", null);
|
|
2773
2819
|
}
|
|
2774
2820
|
}
|
|
2775
2821
|
walk2(node["value"], w);
|
|
@@ -2960,6 +3006,7 @@ var Jinja2Template = class {
|
|
|
2960
3006
|
var jinja2Engine = {
|
|
2961
3007
|
id: "jinja2",
|
|
2962
3008
|
compile(source, opts = {}) {
|
|
3009
|
+
if (typeof source !== "string") throw notText("jinja2", source);
|
|
2963
3010
|
const name = opts.name ?? "template";
|
|
2964
3011
|
try {
|
|
2965
3012
|
installCompat();
|
|
@@ -3129,12 +3176,18 @@ function createLiquid(ctx) {
|
|
|
3129
3176
|
parseLimit: 2e6
|
|
3130
3177
|
});
|
|
3131
3178
|
for (const [name, { fn }] of ctx.helpers.bind(ctx))
|
|
3132
|
-
liquid.registerFilter(
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3179
|
+
liquid.registerFilter(
|
|
3180
|
+
name,
|
|
3181
|
+
function(input, ...argv) {
|
|
3182
|
+
const tokens = this.token?.args ?? [];
|
|
3183
|
+
return fn(
|
|
3184
|
+
...collectKeywordArgs(
|
|
3185
|
+
[input, ...argv],
|
|
3186
|
+
(i) => i > 0 && Array.isArray(tokens[i - 1])
|
|
3187
|
+
)
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
);
|
|
3138
3191
|
return liquid;
|
|
3139
3192
|
}
|
|
3140
3193
|
var validationContext3 = {
|
|
@@ -3368,6 +3421,7 @@ var LiquidCompiled = class {
|
|
|
3368
3421
|
var liquidEngine = {
|
|
3369
3422
|
id: "liquid",
|
|
3370
3423
|
compile(source, opts = {}) {
|
|
3424
|
+
if (typeof source !== "string") throw notText("liquid", source);
|
|
3371
3425
|
const name = opts.name ?? "template";
|
|
3372
3426
|
try {
|
|
3373
3427
|
createLiquid(validationContext3).parse(source, name);
|
package/index.js
CHANGED
|
@@ -11,6 +11,15 @@ var EngineSyntaxError = class extends Error {
|
|
|
11
11
|
return { line: this.line, column: this.column };
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
|
+
function notText(engine, source) {
|
|
15
|
+
const kind = source === null || source === void 0 ? String(source) : Array.isArray(source) ? "an array" : typeof source === "object" ? "an object" : `a ${typeof source}`;
|
|
16
|
+
return new EngineSyntaxError(
|
|
17
|
+
engine,
|
|
18
|
+
`A template must be text, not ${kind}`,
|
|
19
|
+
1,
|
|
20
|
+
1
|
|
21
|
+
);
|
|
22
|
+
}
|
|
14
23
|
var RenderLimitError = class extends Error {
|
|
15
24
|
constructor(limit, message) {
|
|
16
25
|
super(message);
|
|
@@ -1624,7 +1633,8 @@ var builtinRoots = /* @__PURE__ */ new Set([
|
|
|
1624
1633
|
]);
|
|
1625
1634
|
function withBrand(data, ctx) {
|
|
1626
1635
|
const root = data ?? {};
|
|
1627
|
-
if (!ctx.brand || typeof root !== "object" || Array.isArray(root))
|
|
1636
|
+
if (!ctx.brand || typeof root !== "object" || Array.isArray(root))
|
|
1637
|
+
return root;
|
|
1628
1638
|
return { brand: ctx.brand, ...root };
|
|
1629
1639
|
}
|
|
1630
1640
|
function createInstance(ctx) {
|
|
@@ -1923,6 +1933,11 @@ var HandlebarsCompiled = class {
|
|
|
1923
1933
|
}
|
|
1924
1934
|
const src = ctx.partials(inc.name);
|
|
1925
1935
|
if (src === void 0) continue;
|
|
1936
|
+
if (typeof src !== "string")
|
|
1937
|
+
throw new RenderError(
|
|
1938
|
+
"handlebars",
|
|
1939
|
+
`Partial "${inc.name}" is not text`
|
|
1940
|
+
);
|
|
1926
1941
|
partials[inc.name] = src;
|
|
1927
1942
|
collect(src, depth + 1);
|
|
1928
1943
|
}
|
|
@@ -1950,6 +1965,7 @@ var HandlebarsCompiled = class {
|
|
|
1950
1965
|
var handlebarsEngine = {
|
|
1951
1966
|
id: "handlebars",
|
|
1952
1967
|
compile(source, opts = {}) {
|
|
1968
|
+
if (typeof source !== "string") throw notText("handlebars", source);
|
|
1953
1969
|
const name = opts.name ?? "template";
|
|
1954
1970
|
try {
|
|
1955
1971
|
createInstance(validationContext).precompile(source, { strict: false });
|
|
@@ -2218,7 +2234,8 @@ var stringMethods = {
|
|
|
2218
2234
|
};
|
|
2219
2235
|
function keywordArgs(args) {
|
|
2220
2236
|
const last = args.at(-1);
|
|
2221
|
-
if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords"))
|
|
2237
|
+
if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords"))
|
|
2238
|
+
return void 0;
|
|
2222
2239
|
const { __keywords: _marker, ...named } = last;
|
|
2223
2240
|
return named;
|
|
2224
2241
|
}
|
|
@@ -2232,11 +2249,15 @@ function namespace(...args) {
|
|
|
2232
2249
|
return ns;
|
|
2233
2250
|
}
|
|
2234
2251
|
function setNamespaceAttribute(target, key, value) {
|
|
2235
|
-
if (!(target instanceof JinjaNamespace))
|
|
2252
|
+
if (!(target instanceof JinjaNamespace))
|
|
2253
|
+
throw new Error("Cannot assign attribute on non-namespace object");
|
|
2236
2254
|
target[String(key)] = value;
|
|
2237
2255
|
}
|
|
2238
2256
|
var toList = (v) => Array.isArray(v) ? v : v === void 0 || v === null ? [] : typeof v === "string" ? [...v] : [v];
|
|
2239
|
-
var attributeOf = (item, path) => String(path ?? "").split(".").reduce(
|
|
2257
|
+
var attributeOf = (item, path) => String(path ?? "").split(".").reduce(
|
|
2258
|
+
(acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0,
|
|
2259
|
+
item
|
|
2260
|
+
);
|
|
2240
2261
|
var jinjaTests = {
|
|
2241
2262
|
none: (v) => v === null,
|
|
2242
2263
|
true: (v) => v === true,
|
|
@@ -2257,50 +2278,65 @@ function runTest(self, name, value, arg) {
|
|
|
2257
2278
|
function installJinjaFilters(env) {
|
|
2258
2279
|
const lookup = env;
|
|
2259
2280
|
const helperTruncate = lookup.getFilter("truncate");
|
|
2260
|
-
lookup.addFilter(
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2281
|
+
lookup.addFilter(
|
|
2282
|
+
"truncate",
|
|
2283
|
+
function(value, ...args) {
|
|
2284
|
+
const named = keywordArgs(args);
|
|
2285
|
+
const p = named ? args.slice(0, -1) : args;
|
|
2286
|
+
if (!named && typeof p[1] !== "boolean" && p.length < 3)
|
|
2287
|
+
return helperTruncate.call(this, value, ...args);
|
|
2288
|
+
const s = String(value ?? "");
|
|
2289
|
+
const length = Number(p[0] ?? named?.["length"] ?? 255);
|
|
2290
|
+
const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
|
|
2291
|
+
const end = String(p[2] ?? named?.["end"] ?? "...");
|
|
2292
|
+
const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
|
|
2293
|
+
if (s.length <= length + leeway) return s;
|
|
2294
|
+
const cut = s.slice(0, Math.max(0, length - end.length));
|
|
2295
|
+
if (killwords) return cut + end;
|
|
2296
|
+
const space = cut.lastIndexOf(" ");
|
|
2297
|
+
return (space >= 0 ? cut.slice(0, space) : cut) + end;
|
|
2298
|
+
}
|
|
2299
|
+
);
|
|
2275
2300
|
const helperSum = lookup.getFilter("sum");
|
|
2276
|
-
lookup.addFilter(
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2301
|
+
lookup.addFilter(
|
|
2302
|
+
"sum",
|
|
2303
|
+
function(value, ...args) {
|
|
2304
|
+
const named = keywordArgs(args);
|
|
2305
|
+
if (!named) return helperSum.call(this, value, ...args);
|
|
2306
|
+
const p = args.slice(0, -1);
|
|
2307
|
+
const total = helperSum.call(
|
|
2308
|
+
this,
|
|
2309
|
+
value,
|
|
2310
|
+
p[0] ?? named["attribute"]
|
|
2311
|
+
);
|
|
2312
|
+
return total + Number(p[1] ?? named["start"] ?? 0);
|
|
2313
|
+
}
|
|
2314
|
+
);
|
|
2283
2315
|
const helperMap = lookup.getFilter("map");
|
|
2284
|
-
lookup.addFilter(
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
return
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
filter
|
|
2297
|
-
|
|
2298
|
-
|
|
2316
|
+
lookup.addFilter(
|
|
2317
|
+
"map",
|
|
2318
|
+
function(value, ...args) {
|
|
2319
|
+
const named = keywordArgs(args);
|
|
2320
|
+
const p = named ? args.slice(0, -1) : args;
|
|
2321
|
+
if (named && "attribute" in named)
|
|
2322
|
+
return toList(value).map((item) => {
|
|
2323
|
+
const v = attributeOf(item, named["attribute"]);
|
|
2324
|
+
return v === void 0 && "default" in named ? named["default"] : v;
|
|
2325
|
+
});
|
|
2326
|
+
const items = toList(value);
|
|
2327
|
+
if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
|
|
2328
|
+
let filter;
|
|
2329
|
+
try {
|
|
2330
|
+
filter = lookup.getFilter(p[0]);
|
|
2331
|
+
} catch {
|
|
2332
|
+
filter = void 0;
|
|
2333
|
+
}
|
|
2334
|
+
if (filter)
|
|
2335
|
+
return items.map((item) => filter.call(this, item, ...p.slice(1)));
|
|
2299
2336
|
}
|
|
2300
|
-
|
|
2337
|
+
return helperMap.call(this, value, ...args);
|
|
2301
2338
|
}
|
|
2302
|
-
|
|
2303
|
-
});
|
|
2339
|
+
);
|
|
2304
2340
|
const byAttribute = (keep) => function(value, attribute2, test, arg) {
|
|
2305
2341
|
return toList(value).filter((item) => {
|
|
2306
2342
|
const v = attributeOf(item, attribute2);
|
|
@@ -2329,14 +2365,16 @@ function pythonRepr(v) {
|
|
|
2329
2365
|
if (v === null || v === void 0) return "None";
|
|
2330
2366
|
if (v === true) return "True";
|
|
2331
2367
|
if (v === false) return "False";
|
|
2332
|
-
if (typeof v === "number")
|
|
2368
|
+
if (typeof v === "number")
|
|
2369
|
+
return Number.isNaN(v) ? "nan" : Number.isFinite(v) ? String(v) : v > 0 ? "inf" : "-inf";
|
|
2333
2370
|
if (typeof v === "string") {
|
|
2334
2371
|
const quote2 = v.includes("'") && !v.includes('"') ? '"' : "'";
|
|
2335
2372
|
const escaped = v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
2336
2373
|
return quote2 + (quote2 === "'" ? escaped.replace(/'/g, "\\'") : escaped) + quote2;
|
|
2337
2374
|
}
|
|
2338
2375
|
if (Array.isArray(v)) return `[${v.map(pythonRepr).join(", ")}]`;
|
|
2339
|
-
if (isPlainObject(v))
|
|
2376
|
+
if (isPlainObject(v))
|
|
2377
|
+
return `{${Object.entries(v).map(([k, item]) => `${pythonRepr(k)}: ${pythonRepr(item)}`).join(", ")}}`;
|
|
2340
2378
|
return String(v);
|
|
2341
2379
|
}
|
|
2342
2380
|
var compatInstalled = false;
|
|
@@ -2353,7 +2391,8 @@ function installCompat() {
|
|
|
2353
2391
|
nunjucks.runtime["formfeedSetAttribute"] = setNamespaceAttribute;
|
|
2354
2392
|
nunjucks.compiler.Compiler.prototype.compileSet = function(node, frame) {
|
|
2355
2393
|
const [target, ...more] = node.targets;
|
|
2356
|
-
if (!target || more.length || target.typename !== "LookupVal")
|
|
2394
|
+
if (!target || more.length || target.typename !== "LookupVal")
|
|
2395
|
+
return compileSet.call(this, node, frame);
|
|
2357
2396
|
this._emit("runtime.formfeedSetAttribute(");
|
|
2358
2397
|
this._compileExpression(target["target"], frame);
|
|
2359
2398
|
this._emit(", ");
|
|
@@ -2364,7 +2403,10 @@ function installCompat() {
|
|
|
2364
2403
|
this._emitLine(");");
|
|
2365
2404
|
};
|
|
2366
2405
|
const suppressValue = nunjucks.runtime.suppressValue;
|
|
2367
|
-
nunjucks.runtime.suppressValue = (val2, autoescape) => suppressValue(
|
|
2406
|
+
nunjucks.runtime.suppressValue = (val2, autoescape) => suppressValue(
|
|
2407
|
+
Array.isArray(val2) || isPlainObject(val2) ? pythonRepr(val2) : val2,
|
|
2408
|
+
autoescape
|
|
2409
|
+
);
|
|
2368
2410
|
const original = nunjucks.runtime.memberLookup;
|
|
2369
2411
|
nunjucks.runtime.memberLookup = (obj, val2, ...rest) => {
|
|
2370
2412
|
if (typeof obj === "string" && typeof val2 === "string" && Object.prototype.hasOwnProperty.call(stringMethods, val2)) {
|
|
@@ -2468,7 +2510,8 @@ function walk2(node, w) {
|
|
|
2468
2510
|
case "Symbol":
|
|
2469
2511
|
case "LookupVal": {
|
|
2470
2512
|
const p = pathOf(node);
|
|
2471
|
-
if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value)))
|
|
2513
|
+
if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value)))
|
|
2514
|
+
return;
|
|
2472
2515
|
if (p) {
|
|
2473
2516
|
const root = p.path[0] ?? "";
|
|
2474
2517
|
const bound = inScope(w, root);
|
|
@@ -2523,7 +2566,9 @@ function walk2(node, w) {
|
|
|
2523
2566
|
const nameNode = node["name"];
|
|
2524
2567
|
const names = nameNode.typename === "Array" ? nameNode.children ?? [] : [nameNode];
|
|
2525
2568
|
const root = source?.path[0] ?? "";
|
|
2526
|
-
const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(
|
|
2569
|
+
const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(
|
|
2570
|
+
sourceOf(w, root)
|
|
2571
|
+
) : source.path;
|
|
2527
2572
|
const scope = /* @__PURE__ */ new Map();
|
|
2528
2573
|
for (const n of names) {
|
|
2529
2574
|
const name = String(n.value);
|
|
@@ -2551,7 +2596,8 @@ function walk2(node, w) {
|
|
|
2551
2596
|
range: nodeRange(p.root, p.path.join(".").length),
|
|
2552
2597
|
kind: "assigned"
|
|
2553
2598
|
});
|
|
2554
|
-
if (p.path.length === 1 || !inScope(w, p.path[0] ?? ""))
|
|
2599
|
+
if (p.path.length === 1 || !inScope(w, p.path[0] ?? ""))
|
|
2600
|
+
w.scopes[0]?.set(p.path[0] ?? "", null);
|
|
2555
2601
|
}
|
|
2556
2602
|
}
|
|
2557
2603
|
walk2(node["value"], w);
|
|
@@ -2742,6 +2788,7 @@ var Jinja2Template = class {
|
|
|
2742
2788
|
var jinja2Engine = {
|
|
2743
2789
|
id: "jinja2",
|
|
2744
2790
|
compile(source, opts = {}) {
|
|
2791
|
+
if (typeof source !== "string") throw notText("jinja2", source);
|
|
2745
2792
|
const name = opts.name ?? "template";
|
|
2746
2793
|
try {
|
|
2747
2794
|
installCompat();
|
|
@@ -2911,12 +2958,18 @@ function createLiquid(ctx) {
|
|
|
2911
2958
|
parseLimit: 2e6
|
|
2912
2959
|
});
|
|
2913
2960
|
for (const [name, { fn }] of ctx.helpers.bind(ctx))
|
|
2914
|
-
liquid.registerFilter(
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2961
|
+
liquid.registerFilter(
|
|
2962
|
+
name,
|
|
2963
|
+
function(input, ...argv) {
|
|
2964
|
+
const tokens = this.token?.args ?? [];
|
|
2965
|
+
return fn(
|
|
2966
|
+
...collectKeywordArgs(
|
|
2967
|
+
[input, ...argv],
|
|
2968
|
+
(i) => i > 0 && Array.isArray(tokens[i - 1])
|
|
2969
|
+
)
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
);
|
|
2920
2973
|
return liquid;
|
|
2921
2974
|
}
|
|
2922
2975
|
var validationContext3 = {
|
|
@@ -3150,6 +3203,7 @@ var LiquidCompiled = class {
|
|
|
3150
3203
|
var liquidEngine = {
|
|
3151
3204
|
id: "liquid",
|
|
3152
3205
|
compile(source, opts = {}) {
|
|
3206
|
+
if (typeof source !== "string") throw notText("liquid", source);
|
|
3153
3207
|
const name = opts.name ?? "template";
|
|
3154
3208
|
try {
|
|
3155
3209
|
createLiquid(validationContext3).parse(source, name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formfeed/engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "The Formfeed template engine (Jinja2, Liquid, Handlebars) for the browser and Node.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@date-fns/tz": "^1.5.0",
|
|
34
34
|
"bwip-js": "^4.11.4",
|
|
35
35
|
"date-fns": "^4.4.0",
|
|
36
|
-
"handlebars": "^4.7.
|
|
36
|
+
"handlebars": "^4.7.10",
|
|
37
37
|
"liquidjs": "^10.29.0",
|
|
38
38
|
"marked": "^18.0.11",
|
|
39
39
|
"n2words": "^6.1.2",
|
package/types/lib/errors.d.cts
CHANGED
|
@@ -8,6 +8,13 @@ export declare class EngineSyntaxError extends Error {
|
|
|
8
8
|
constructor(engine: EngineId, message: string, line: number, column: number);
|
|
9
9
|
get position(): Position;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* A template source that is not text. JSON can carry an object where a source belongs (a version's
|
|
13
|
+
* settings, a render request's `settings`), and Handlebars compiles an object as a parsed syntax tree
|
|
14
|
+
* whose values went into the generated code unchecked up to 4.7.9 (GHSA-8r5x-fm3f-whwj): a header
|
|
15
|
+
* sent as a tree ran as JavaScript in the render-worker. Every engine takes text only.
|
|
16
|
+
*/
|
|
17
|
+
export declare function notText(engine: EngineId, source: unknown): EngineSyntaxError;
|
|
11
18
|
/** A render exceeded one of the limits in `RenderLimits` (spec 05 §7). */
|
|
12
19
|
export declare class RenderLimitError extends Error {
|
|
13
20
|
readonly limit: 'ms' | 'outputBytes' | 'includeDepth';
|
package/types/lib/errors.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ export declare class EngineSyntaxError extends Error {
|
|
|
8
8
|
constructor(engine: EngineId, message: string, line: number, column: number);
|
|
9
9
|
get position(): Position;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* A template source that is not text. JSON can carry an object where a source belongs (a version's
|
|
13
|
+
* settings, a render request's `settings`), and Handlebars compiles an object as a parsed syntax tree
|
|
14
|
+
* whose values went into the generated code unchecked up to 4.7.9 (GHSA-8r5x-fm3f-whwj): a header
|
|
15
|
+
* sent as a tree ran as JavaScript in the render-worker. Every engine takes text only.
|
|
16
|
+
*/
|
|
17
|
+
export declare function notText(engine: EngineId, source: unknown): EngineSyntaxError;
|
|
11
18
|
/** A render exceeded one of the limits in `RenderLimits` (spec 05 §7). */
|
|
12
19
|
export declare class RenderLimitError extends Error {
|
|
13
20
|
readonly limit: 'ms' | 'outputBytes' | 'includeDepth';
|