binja 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/dist/cli.js +254 -2
- package/dist/filters/index.d.ts +9 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/index.js +853 -83
- package/dist/parser/index.d.ts +10 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/nodes.d.ts +49 -2
- package/dist/parser/nodes.d.ts.map +1 -1
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -660,6 +660,28 @@ class Parser {
|
|
|
660
660
|
case "autoescape":
|
|
661
661
|
case "verbatim":
|
|
662
662
|
return this.parseSimpleBlock(start, tagName.value);
|
|
663
|
+
case "cycle":
|
|
664
|
+
return this.parseCycle(start);
|
|
665
|
+
case "firstof":
|
|
666
|
+
return this.parseFirstof(start);
|
|
667
|
+
case "ifchanged":
|
|
668
|
+
return this.parseIfchanged(start);
|
|
669
|
+
case "regroup":
|
|
670
|
+
return this.parseRegroup(start);
|
|
671
|
+
case "widthratio":
|
|
672
|
+
return this.parseWidthratio(start);
|
|
673
|
+
case "lorem":
|
|
674
|
+
return this.parseLorem(start);
|
|
675
|
+
case "csrf_token":
|
|
676
|
+
return this.parseCsrfToken(start);
|
|
677
|
+
case "debug":
|
|
678
|
+
return this.parseDebug(start);
|
|
679
|
+
case "templatetag":
|
|
680
|
+
return this.parseTemplatetag(start);
|
|
681
|
+
case "ifequal":
|
|
682
|
+
return this.parseIfequal(start, false);
|
|
683
|
+
case "ifnotequal":
|
|
684
|
+
return this.parseIfequal(start, true);
|
|
663
685
|
default:
|
|
664
686
|
this.skipToBlockEnd();
|
|
665
687
|
return null;
|
|
@@ -963,7 +985,7 @@ class Parser {
|
|
|
963
985
|
column: start.column
|
|
964
986
|
};
|
|
965
987
|
}
|
|
966
|
-
parseComment(
|
|
988
|
+
parseComment(_start) {
|
|
967
989
|
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
968
990
|
while (!this.isAtEnd()) {
|
|
969
991
|
if (this.checkBlockTag("endcomment"))
|
|
@@ -973,7 +995,7 @@ class Parser {
|
|
|
973
995
|
this.expectBlockTag("endcomment");
|
|
974
996
|
return null;
|
|
975
997
|
}
|
|
976
|
-
parseSimpleBlock(
|
|
998
|
+
parseSimpleBlock(_start, tagName) {
|
|
977
999
|
this.skipToBlockEnd();
|
|
978
1000
|
const endTag = `end${tagName}`;
|
|
979
1001
|
while (!this.isAtEnd()) {
|
|
@@ -988,6 +1010,236 @@ class Parser {
|
|
|
988
1010
|
}
|
|
989
1011
|
return null;
|
|
990
1012
|
}
|
|
1013
|
+
parseCycle(start) {
|
|
1014
|
+
const values = [];
|
|
1015
|
+
let asVar = null;
|
|
1016
|
+
let silent = false;
|
|
1017
|
+
while (!this.check("BLOCK_END" /* BLOCK_END */)) {
|
|
1018
|
+
if (this.check("NAME" /* NAME */) && this.peek().value === "as") {
|
|
1019
|
+
this.advance();
|
|
1020
|
+
asVar = this.expect("NAME" /* NAME */).value;
|
|
1021
|
+
if (this.check("NAME" /* NAME */) && this.peek().value === "silent") {
|
|
1022
|
+
this.advance();
|
|
1023
|
+
silent = true;
|
|
1024
|
+
}
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
values.push(this.parseExpression());
|
|
1028
|
+
}
|
|
1029
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1030
|
+
return {
|
|
1031
|
+
type: "Cycle",
|
|
1032
|
+
values,
|
|
1033
|
+
asVar,
|
|
1034
|
+
silent,
|
|
1035
|
+
line: start.line,
|
|
1036
|
+
column: start.column
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
parseFirstof(start) {
|
|
1040
|
+
const values = [];
|
|
1041
|
+
let fallback = null;
|
|
1042
|
+
let asVar = null;
|
|
1043
|
+
while (!this.check("BLOCK_END" /* BLOCK_END */)) {
|
|
1044
|
+
if (this.check("NAME" /* NAME */) && this.peek().value === "as") {
|
|
1045
|
+
this.advance();
|
|
1046
|
+
asVar = this.expect("NAME" /* NAME */).value;
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
values.push(this.parseExpression());
|
|
1050
|
+
}
|
|
1051
|
+
if (values.length > 0) {
|
|
1052
|
+
const last = values[values.length - 1];
|
|
1053
|
+
if (last.type === "Literal" && typeof last.value === "string") {
|
|
1054
|
+
fallback = values.pop();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1058
|
+
return {
|
|
1059
|
+
type: "Firstof",
|
|
1060
|
+
values,
|
|
1061
|
+
fallback,
|
|
1062
|
+
asVar,
|
|
1063
|
+
line: start.line,
|
|
1064
|
+
column: start.column
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
parseIfchanged(start) {
|
|
1068
|
+
const values = [];
|
|
1069
|
+
while (!this.check("BLOCK_END" /* BLOCK_END */)) {
|
|
1070
|
+
values.push(this.parseExpression());
|
|
1071
|
+
}
|
|
1072
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1073
|
+
const body = [];
|
|
1074
|
+
let else_ = [];
|
|
1075
|
+
while (!this.isAtEnd()) {
|
|
1076
|
+
if (this.checkBlockTag("else") || this.checkBlockTag("endifchanged"))
|
|
1077
|
+
break;
|
|
1078
|
+
const node = this.parseStatement();
|
|
1079
|
+
if (node)
|
|
1080
|
+
body.push(node);
|
|
1081
|
+
}
|
|
1082
|
+
if (this.checkBlockTag("else")) {
|
|
1083
|
+
this.advance();
|
|
1084
|
+
this.advance();
|
|
1085
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1086
|
+
while (!this.isAtEnd()) {
|
|
1087
|
+
if (this.checkBlockTag("endifchanged"))
|
|
1088
|
+
break;
|
|
1089
|
+
const node = this.parseStatement();
|
|
1090
|
+
if (node)
|
|
1091
|
+
else_.push(node);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
this.expectBlockTag("endifchanged");
|
|
1095
|
+
return {
|
|
1096
|
+
type: "Ifchanged",
|
|
1097
|
+
values,
|
|
1098
|
+
body,
|
|
1099
|
+
else_,
|
|
1100
|
+
line: start.line,
|
|
1101
|
+
column: start.column
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
parseRegroup(start) {
|
|
1105
|
+
const target = this.parseExpression();
|
|
1106
|
+
this.expectName("by");
|
|
1107
|
+
const key = this.expect("NAME" /* NAME */).value;
|
|
1108
|
+
this.expectName("as");
|
|
1109
|
+
const asVar = this.expect("NAME" /* NAME */).value;
|
|
1110
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1111
|
+
return {
|
|
1112
|
+
type: "Regroup",
|
|
1113
|
+
target,
|
|
1114
|
+
key,
|
|
1115
|
+
asVar,
|
|
1116
|
+
line: start.line,
|
|
1117
|
+
column: start.column
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
parseWidthratio(start) {
|
|
1121
|
+
const value = this.parseExpression();
|
|
1122
|
+
const maxValue = this.parseExpression();
|
|
1123
|
+
const maxWidth = this.parseExpression();
|
|
1124
|
+
let asVar = null;
|
|
1125
|
+
if (this.check("NAME" /* NAME */) && this.peek().value === "as") {
|
|
1126
|
+
this.advance();
|
|
1127
|
+
asVar = this.expect("NAME" /* NAME */).value;
|
|
1128
|
+
}
|
|
1129
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1130
|
+
return {
|
|
1131
|
+
type: "Widthratio",
|
|
1132
|
+
value,
|
|
1133
|
+
maxValue,
|
|
1134
|
+
maxWidth,
|
|
1135
|
+
asVar,
|
|
1136
|
+
line: start.line,
|
|
1137
|
+
column: start.column
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
parseLorem(start) {
|
|
1141
|
+
let count = null;
|
|
1142
|
+
let method = "p";
|
|
1143
|
+
let random = false;
|
|
1144
|
+
if (this.check("NUMBER" /* NUMBER */)) {
|
|
1145
|
+
count = {
|
|
1146
|
+
type: "Literal",
|
|
1147
|
+
value: parseInt(this.advance().value, 10),
|
|
1148
|
+
line: start.line,
|
|
1149
|
+
column: start.column
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
if (this.check("NAME" /* NAME */)) {
|
|
1153
|
+
const m = this.peek().value.toLowerCase();
|
|
1154
|
+
if (m === "w" || m === "p" || m === "b") {
|
|
1155
|
+
method = m;
|
|
1156
|
+
this.advance();
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (this.check("NAME" /* NAME */) && this.peek().value === "random") {
|
|
1160
|
+
random = true;
|
|
1161
|
+
this.advance();
|
|
1162
|
+
}
|
|
1163
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1164
|
+
return {
|
|
1165
|
+
type: "Lorem",
|
|
1166
|
+
count,
|
|
1167
|
+
method,
|
|
1168
|
+
random,
|
|
1169
|
+
line: start.line,
|
|
1170
|
+
column: start.column
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
parseCsrfToken(start) {
|
|
1174
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1175
|
+
return {
|
|
1176
|
+
type: "CsrfToken",
|
|
1177
|
+
line: start.line,
|
|
1178
|
+
column: start.column
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
parseDebug(start) {
|
|
1182
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1183
|
+
return {
|
|
1184
|
+
type: "Debug",
|
|
1185
|
+
line: start.line,
|
|
1186
|
+
column: start.column
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
parseTemplatetag(start) {
|
|
1190
|
+
const tagType = this.expect("NAME" /* NAME */).value;
|
|
1191
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1192
|
+
return {
|
|
1193
|
+
type: "Templatetag",
|
|
1194
|
+
tagType,
|
|
1195
|
+
line: start.line,
|
|
1196
|
+
column: start.column
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
parseIfequal(start, negated) {
|
|
1200
|
+
const left = this.parseExpression();
|
|
1201
|
+
const right = this.parseExpression();
|
|
1202
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1203
|
+
const test = {
|
|
1204
|
+
type: "Compare",
|
|
1205
|
+
left,
|
|
1206
|
+
ops: [{ operator: negated ? "!=" : "==", right }],
|
|
1207
|
+
line: start.line,
|
|
1208
|
+
column: start.column
|
|
1209
|
+
};
|
|
1210
|
+
const body = [];
|
|
1211
|
+
let else_ = [];
|
|
1212
|
+
const endTag = negated ? "endifnotequal" : "endifequal";
|
|
1213
|
+
while (!this.isAtEnd()) {
|
|
1214
|
+
if (this.checkBlockTag("else") || this.checkBlockTag(endTag))
|
|
1215
|
+
break;
|
|
1216
|
+
const node = this.parseStatement();
|
|
1217
|
+
if (node)
|
|
1218
|
+
body.push(node);
|
|
1219
|
+
}
|
|
1220
|
+
if (this.checkBlockTag("else")) {
|
|
1221
|
+
this.advance();
|
|
1222
|
+
this.advance();
|
|
1223
|
+
this.expect("BLOCK_END" /* BLOCK_END */);
|
|
1224
|
+
while (!this.isAtEnd()) {
|
|
1225
|
+
if (this.checkBlockTag(endTag))
|
|
1226
|
+
break;
|
|
1227
|
+
const node = this.parseStatement();
|
|
1228
|
+
if (node)
|
|
1229
|
+
else_.push(node);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
this.expectBlockTag(endTag);
|
|
1233
|
+
return {
|
|
1234
|
+
type: "If",
|
|
1235
|
+
test,
|
|
1236
|
+
body,
|
|
1237
|
+
elifs: [],
|
|
1238
|
+
else_,
|
|
1239
|
+
line: start.line,
|
|
1240
|
+
column: start.column
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
991
1243
|
parseExpression() {
|
|
992
1244
|
return this.parseConditional();
|
|
993
1245
|
}
|
|
@@ -1423,6 +1675,7 @@ class Parser {
|
|
|
1423
1675
|
// src/runtime/context.ts
|
|
1424
1676
|
function createForLoop(items, index, depth, lastCycleValue, parentloop) {
|
|
1425
1677
|
const length = items.length;
|
|
1678
|
+
const revCount = length - index;
|
|
1426
1679
|
const forloop = {
|
|
1427
1680
|
_items: items,
|
|
1428
1681
|
_idx: index,
|
|
@@ -1435,17 +1688,19 @@ function createForLoop(items, index, depth, lastCycleValue, parentloop) {
|
|
|
1435
1688
|
index0: index,
|
|
1436
1689
|
depth,
|
|
1437
1690
|
depth0: depth - 1,
|
|
1438
|
-
revcounter:
|
|
1439
|
-
revcounter0:
|
|
1440
|
-
revindex:
|
|
1441
|
-
revindex0:
|
|
1691
|
+
revcounter: revCount,
|
|
1692
|
+
revcounter0: revCount - 1,
|
|
1693
|
+
revindex: revCount,
|
|
1694
|
+
revindex0: revCount - 1,
|
|
1442
1695
|
get previtem() {
|
|
1443
1696
|
return this._idx > 0 ? this._items[this._idx - 1] : undefined;
|
|
1444
1697
|
},
|
|
1445
1698
|
get nextitem() {
|
|
1446
1699
|
return this._idx < this._items.length - 1 ? this._items[this._idx + 1] : undefined;
|
|
1447
1700
|
},
|
|
1448
|
-
cycle
|
|
1701
|
+
cycle(...args) {
|
|
1702
|
+
return args[this._idx % args.length];
|
|
1703
|
+
},
|
|
1449
1704
|
changed: (value) => {
|
|
1450
1705
|
const changed = value !== lastCycleValue.value;
|
|
1451
1706
|
lastCycleValue.value = value;
|
|
@@ -1523,18 +1778,19 @@ class Context {
|
|
|
1523
1778
|
return;
|
|
1524
1779
|
const length = items.length;
|
|
1525
1780
|
forloop._idx = index;
|
|
1526
|
-
forloop._items
|
|
1781
|
+
if (forloop._items !== items)
|
|
1782
|
+
forloop._items = items;
|
|
1527
1783
|
forloop.counter = index + 1;
|
|
1528
1784
|
forloop.counter0 = index;
|
|
1529
|
-
forloop.first = index === 0;
|
|
1530
|
-
forloop.last = index === length - 1;
|
|
1531
1785
|
forloop.index = index + 1;
|
|
1532
1786
|
forloop.index0 = index;
|
|
1533
|
-
forloop.
|
|
1534
|
-
forloop.
|
|
1535
|
-
|
|
1536
|
-
forloop.
|
|
1537
|
-
forloop.
|
|
1787
|
+
forloop.first = index === 0;
|
|
1788
|
+
forloop.last = index === length - 1;
|
|
1789
|
+
const revCount = length - index;
|
|
1790
|
+
forloop.revcounter = revCount;
|
|
1791
|
+
forloop.revcounter0 = revCount - 1;
|
|
1792
|
+
forloop.revindex = revCount;
|
|
1793
|
+
forloop.revindex0 = revCount - 1;
|
|
1538
1794
|
}
|
|
1539
1795
|
toObject() {
|
|
1540
1796
|
const result = {};
|
|
@@ -2227,6 +2483,172 @@ ${indent2}`;
|
|
|
2227
2483
|
safeString.__safe__ = true;
|
|
2228
2484
|
return safeString;
|
|
2229
2485
|
};
|
|
2486
|
+
var addslashes = (value) => {
|
|
2487
|
+
const result = String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "\\\"");
|
|
2488
|
+
const safeString = new String(result);
|
|
2489
|
+
safeString.__safe__ = true;
|
|
2490
|
+
return safeString;
|
|
2491
|
+
};
|
|
2492
|
+
var get_digit = (value, digit) => {
|
|
2493
|
+
const num = parseInt(String(value), 10);
|
|
2494
|
+
if (isNaN(num))
|
|
2495
|
+
return value;
|
|
2496
|
+
const str = String(Math.abs(num));
|
|
2497
|
+
const pos = Number(digit) || 1;
|
|
2498
|
+
if (pos < 1 || pos > str.length)
|
|
2499
|
+
return value;
|
|
2500
|
+
return parseInt(str[str.length - pos], 10);
|
|
2501
|
+
};
|
|
2502
|
+
var iriencode = (value) => {
|
|
2503
|
+
return String(value).replace(/ /g, "%20").replace(/"/g, "%22").replace(/</g, "%3C").replace(/>/g, "%3E").replace(/\\/g, "%5C").replace(/\^/g, "%5E").replace(/`/g, "%60").replace(/\{/g, "%7B").replace(/\|/g, "%7C").replace(/\}/g, "%7D");
|
|
2504
|
+
};
|
|
2505
|
+
var json_script = (value, elementId) => {
|
|
2506
|
+
const jsonStr = JSON.stringify(value).replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026");
|
|
2507
|
+
const id = elementId ? ` id="${String(elementId)}"` : "";
|
|
2508
|
+
const html = `<script${id} type="application/json">${jsonStr}</script>`;
|
|
2509
|
+
const safeString = new String(html);
|
|
2510
|
+
safeString.__safe__ = true;
|
|
2511
|
+
return safeString;
|
|
2512
|
+
};
|
|
2513
|
+
var safeseq = (value) => {
|
|
2514
|
+
if (!Array.isArray(value))
|
|
2515
|
+
return value;
|
|
2516
|
+
return value.map((item) => {
|
|
2517
|
+
const safeString = new String(item);
|
|
2518
|
+
safeString.__safe__ = true;
|
|
2519
|
+
return safeString;
|
|
2520
|
+
});
|
|
2521
|
+
};
|
|
2522
|
+
var stringformat = (value, formatStr) => {
|
|
2523
|
+
const fmt = String(formatStr);
|
|
2524
|
+
const val = value;
|
|
2525
|
+
if (fmt === "s")
|
|
2526
|
+
return String(val);
|
|
2527
|
+
if (fmt === "d" || fmt === "i")
|
|
2528
|
+
return String(parseInt(String(val), 10) || 0);
|
|
2529
|
+
if (fmt === "f")
|
|
2530
|
+
return String(parseFloat(String(val)) || 0);
|
|
2531
|
+
if (fmt === "x")
|
|
2532
|
+
return (parseInt(String(val), 10) || 0).toString(16);
|
|
2533
|
+
if (fmt === "X")
|
|
2534
|
+
return (parseInt(String(val), 10) || 0).toString(16).toUpperCase();
|
|
2535
|
+
if (fmt === "o")
|
|
2536
|
+
return (parseInt(String(val), 10) || 0).toString(8);
|
|
2537
|
+
if (fmt === "b")
|
|
2538
|
+
return (parseInt(String(val), 10) || 0).toString(2);
|
|
2539
|
+
if (fmt === "e")
|
|
2540
|
+
return (parseFloat(String(val)) || 0).toExponential();
|
|
2541
|
+
const precisionMatch = fmt.match(/^\.?(\d+)f$/);
|
|
2542
|
+
if (precisionMatch) {
|
|
2543
|
+
const precision = parseInt(precisionMatch[1], 10);
|
|
2544
|
+
return (parseFloat(String(val)) || 0).toFixed(precision);
|
|
2545
|
+
}
|
|
2546
|
+
const widthMatch = fmt.match(/^(\d+)([sd])$/);
|
|
2547
|
+
if (widthMatch) {
|
|
2548
|
+
const width = parseInt(widthMatch[1], 10);
|
|
2549
|
+
const type = widthMatch[2];
|
|
2550
|
+
const strVal = type === "d" ? String(parseInt(String(val), 10) || 0) : String(val);
|
|
2551
|
+
return strVal.padStart(width, type === "d" ? "0" : " ");
|
|
2552
|
+
}
|
|
2553
|
+
return String(val);
|
|
2554
|
+
};
|
|
2555
|
+
var truncatechars_html = (value, length2 = 30) => {
|
|
2556
|
+
const str = String(value);
|
|
2557
|
+
const maxLen = Number(length2);
|
|
2558
|
+
let result = "";
|
|
2559
|
+
let textLen = 0;
|
|
2560
|
+
let inTag = false;
|
|
2561
|
+
const openTags = [];
|
|
2562
|
+
for (let i = 0;i < str.length && textLen < maxLen; i++) {
|
|
2563
|
+
const char = str[i];
|
|
2564
|
+
if (char === "<") {
|
|
2565
|
+
inTag = true;
|
|
2566
|
+
const closeMatch = str.slice(i).match(/^<\/(\w+)/);
|
|
2567
|
+
const openMatch = str.slice(i).match(/^<(\w+)/);
|
|
2568
|
+
if (closeMatch) {
|
|
2569
|
+
const tagName = closeMatch[1].toLowerCase();
|
|
2570
|
+
const lastOpen = openTags.lastIndexOf(tagName);
|
|
2571
|
+
if (lastOpen !== -1)
|
|
2572
|
+
openTags.splice(lastOpen, 1);
|
|
2573
|
+
} else if (openMatch && !str.slice(i).match(/^<\w+[^>]*\/>/)) {
|
|
2574
|
+
openTags.push(openMatch[1].toLowerCase());
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
result += char;
|
|
2578
|
+
if (char === ">") {
|
|
2579
|
+
inTag = false;
|
|
2580
|
+
} else if (!inTag) {
|
|
2581
|
+
textLen++;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
if (textLen >= maxLen && str.length > result.length) {
|
|
2585
|
+
result += "...";
|
|
2586
|
+
}
|
|
2587
|
+
for (let i = openTags.length - 1;i >= 0; i--) {
|
|
2588
|
+
result += `</${openTags[i]}>`;
|
|
2589
|
+
}
|
|
2590
|
+
const safeString = new String(result);
|
|
2591
|
+
safeString.__safe__ = true;
|
|
2592
|
+
return safeString;
|
|
2593
|
+
};
|
|
2594
|
+
var truncatewords_html = (value, count = 15) => {
|
|
2595
|
+
const str = String(value);
|
|
2596
|
+
const maxWords = Number(count);
|
|
2597
|
+
let result = "";
|
|
2598
|
+
let wordCount = 0;
|
|
2599
|
+
let inTag = false;
|
|
2600
|
+
let inWord = false;
|
|
2601
|
+
const openTags = [];
|
|
2602
|
+
for (let i = 0;i < str.length && wordCount < maxWords; i++) {
|
|
2603
|
+
const char = str[i];
|
|
2604
|
+
if (char === "<") {
|
|
2605
|
+
inTag = true;
|
|
2606
|
+
const closeMatch = str.slice(i).match(/^<\/(\w+)/);
|
|
2607
|
+
const openMatch = str.slice(i).match(/^<(\w+)/);
|
|
2608
|
+
if (closeMatch) {
|
|
2609
|
+
const tagName = closeMatch[1].toLowerCase();
|
|
2610
|
+
const lastOpen = openTags.lastIndexOf(tagName);
|
|
2611
|
+
if (lastOpen !== -1)
|
|
2612
|
+
openTags.splice(lastOpen, 1);
|
|
2613
|
+
} else if (openMatch && !str.slice(i).match(/^<\w+[^>]*\/>/)) {
|
|
2614
|
+
openTags.push(openMatch[1].toLowerCase());
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
result += char;
|
|
2618
|
+
if (char === ">") {
|
|
2619
|
+
inTag = false;
|
|
2620
|
+
} else if (!inTag) {
|
|
2621
|
+
const isSpace = /\s/.test(char);
|
|
2622
|
+
if (!isSpace && !inWord) {
|
|
2623
|
+
inWord = true;
|
|
2624
|
+
} else if (isSpace && inWord) {
|
|
2625
|
+
inWord = false;
|
|
2626
|
+
wordCount++;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if (inWord)
|
|
2631
|
+
wordCount++;
|
|
2632
|
+
if (wordCount >= maxWords) {
|
|
2633
|
+
result = result.trimEnd() + "...";
|
|
2634
|
+
}
|
|
2635
|
+
for (let i = openTags.length - 1;i >= 0; i--) {
|
|
2636
|
+
result += `</${openTags[i]}>`;
|
|
2637
|
+
}
|
|
2638
|
+
const safeString = new String(result);
|
|
2639
|
+
safeString.__safe__ = true;
|
|
2640
|
+
return safeString;
|
|
2641
|
+
};
|
|
2642
|
+
var urlizetrunc = (value, limit = 15) => {
|
|
2643
|
+
const maxLen = Number(limit);
|
|
2644
|
+
const html = String(value).replace(URLIZE_REGEX, (url) => {
|
|
2645
|
+
const displayUrl = url.length > maxLen ? url.slice(0, maxLen) + "..." : url;
|
|
2646
|
+
return `<a href="${url}">${displayUrl}</a>`;
|
|
2647
|
+
});
|
|
2648
|
+
const safeString = new String(html);
|
|
2649
|
+
safeString.__safe__ = true;
|
|
2650
|
+
return safeString;
|
|
2651
|
+
};
|
|
2230
2652
|
var builtinFilters = {
|
|
2231
2653
|
upper,
|
|
2232
2654
|
lower,
|
|
@@ -2305,7 +2727,16 @@ var builtinFilters = {
|
|
|
2305
2727
|
forceescape,
|
|
2306
2728
|
phone2numeric,
|
|
2307
2729
|
linenumbers,
|
|
2308
|
-
unordered_list
|
|
2730
|
+
unordered_list,
|
|
2731
|
+
addslashes,
|
|
2732
|
+
get_digit,
|
|
2733
|
+
iriencode,
|
|
2734
|
+
json_script,
|
|
2735
|
+
safeseq,
|
|
2736
|
+
stringformat,
|
|
2737
|
+
truncatechars_html,
|
|
2738
|
+
truncatewords_html,
|
|
2739
|
+
urlizetrunc
|
|
2309
2740
|
};
|
|
2310
2741
|
|
|
2311
2742
|
// src/tests/index.ts
|
|
@@ -2594,11 +3025,13 @@ class Runtime {
|
|
|
2594
3025
|
return parts.join("");
|
|
2595
3026
|
}
|
|
2596
3027
|
renderNodeSync(node, ctx) {
|
|
3028
|
+
if (node.type === "Text") {
|
|
3029
|
+
return node.value;
|
|
3030
|
+
}
|
|
3031
|
+
if (node.type === "Output") {
|
|
3032
|
+
return this.stringify(this.eval(node.expression, ctx));
|
|
3033
|
+
}
|
|
2597
3034
|
switch (node.type) {
|
|
2598
|
-
case "Text":
|
|
2599
|
-
return node.value;
|
|
2600
|
-
case "Output":
|
|
2601
|
-
return this.stringify(this.eval(node.expression, ctx));
|
|
2602
3035
|
case "If":
|
|
2603
3036
|
return this.renderIfSync(node, ctx);
|
|
2604
3037
|
case "For":
|
|
@@ -2619,6 +3052,24 @@ class Runtime {
|
|
|
2619
3052
|
case "Load":
|
|
2620
3053
|
case "Extends":
|
|
2621
3054
|
return null;
|
|
3055
|
+
case "Cycle":
|
|
3056
|
+
return this.renderCycleSync(node, ctx);
|
|
3057
|
+
case "Firstof":
|
|
3058
|
+
return this.renderFirstofSync(node, ctx);
|
|
3059
|
+
case "Ifchanged":
|
|
3060
|
+
return this.renderIfchangedSync(node, ctx);
|
|
3061
|
+
case "Regroup":
|
|
3062
|
+
return this.renderRegroupSync(node, ctx);
|
|
3063
|
+
case "Widthratio":
|
|
3064
|
+
return this.renderWidthratioSync(node, ctx);
|
|
3065
|
+
case "Lorem":
|
|
3066
|
+
return this.renderLoremSync(node, ctx);
|
|
3067
|
+
case "CsrfToken":
|
|
3068
|
+
return this.renderCsrfTokenSync();
|
|
3069
|
+
case "Debug":
|
|
3070
|
+
return this.renderDebugSync(ctx);
|
|
3071
|
+
case "Templatetag":
|
|
3072
|
+
return this.renderTemplatetagSync(node);
|
|
2622
3073
|
default:
|
|
2623
3074
|
return null;
|
|
2624
3075
|
}
|
|
@@ -2641,41 +3092,76 @@ class Runtime {
|
|
|
2641
3092
|
if (len === 0) {
|
|
2642
3093
|
return this.renderNodesSync(node.else_, ctx);
|
|
2643
3094
|
}
|
|
2644
|
-
const parts = new Array(len);
|
|
2645
3095
|
ctx.push();
|
|
2646
3096
|
ctx.pushForLoop(items, 0);
|
|
3097
|
+
let result;
|
|
2647
3098
|
if (Array.isArray(node.target)) {
|
|
2648
3099
|
const targets = node.target;
|
|
2649
3100
|
const targetsLen = targets.length;
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
values
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
3101
|
+
if (len < 50) {
|
|
3102
|
+
result = "";
|
|
3103
|
+
for (let i = 0;i < len; i++) {
|
|
3104
|
+
const item = items[i];
|
|
3105
|
+
if (i > 0)
|
|
3106
|
+
ctx.updateForLoop(i, items);
|
|
3107
|
+
let values;
|
|
3108
|
+
if (Array.isArray(item)) {
|
|
3109
|
+
values = item;
|
|
3110
|
+
} else if (item && typeof item === "object" && (("0" in item) || ("key" in item))) {
|
|
3111
|
+
values = [item[0] ?? item.key, item[1] ?? item.value];
|
|
3112
|
+
} else {
|
|
3113
|
+
values = [item, item];
|
|
3114
|
+
}
|
|
3115
|
+
for (let j = 0;j < targetsLen; j++) {
|
|
3116
|
+
ctx.set(targets[j], values[j]);
|
|
3117
|
+
}
|
|
3118
|
+
result += this.renderNodesSync(node.body, ctx);
|
|
2661
3119
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
3120
|
+
} else {
|
|
3121
|
+
const parts = new Array(len);
|
|
3122
|
+
for (let i = 0;i < len; i++) {
|
|
3123
|
+
const item = items[i];
|
|
3124
|
+
if (i > 0)
|
|
3125
|
+
ctx.updateForLoop(i, items);
|
|
3126
|
+
let values;
|
|
3127
|
+
if (Array.isArray(item)) {
|
|
3128
|
+
values = item;
|
|
3129
|
+
} else if (item && typeof item === "object" && (("0" in item) || ("key" in item))) {
|
|
3130
|
+
values = [item[0] ?? item.key, item[1] ?? item.value];
|
|
3131
|
+
} else {
|
|
3132
|
+
values = [item, item];
|
|
3133
|
+
}
|
|
3134
|
+
for (let j = 0;j < targetsLen; j++) {
|
|
3135
|
+
ctx.set(targets[j], values[j]);
|
|
3136
|
+
}
|
|
3137
|
+
parts[i] = this.renderNodesSync(node.body, ctx);
|
|
2664
3138
|
}
|
|
2665
|
-
|
|
3139
|
+
result = parts.join("");
|
|
2666
3140
|
}
|
|
2667
3141
|
} else {
|
|
2668
3142
|
const target = node.target;
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
3143
|
+
if (len < 50) {
|
|
3144
|
+
result = "";
|
|
3145
|
+
for (let i = 0;i < len; i++) {
|
|
3146
|
+
if (i > 0)
|
|
3147
|
+
ctx.updateForLoop(i, items);
|
|
3148
|
+
ctx.set(target, items[i]);
|
|
3149
|
+
result += this.renderNodesSync(node.body, ctx);
|
|
3150
|
+
}
|
|
3151
|
+
} else {
|
|
3152
|
+
const parts = new Array(len);
|
|
3153
|
+
for (let i = 0;i < len; i++) {
|
|
3154
|
+
if (i > 0)
|
|
3155
|
+
ctx.updateForLoop(i, items);
|
|
3156
|
+
ctx.set(target, items[i]);
|
|
3157
|
+
parts[i] = this.renderNodesSync(node.body, ctx);
|
|
3158
|
+
}
|
|
3159
|
+
result = parts.join("");
|
|
2674
3160
|
}
|
|
2675
3161
|
}
|
|
2676
3162
|
ctx.popForLoop();
|
|
2677
3163
|
ctx.pop();
|
|
2678
|
-
return
|
|
3164
|
+
return result;
|
|
2679
3165
|
}
|
|
2680
3166
|
renderBlockSync(node, ctx) {
|
|
2681
3167
|
const blockToRender = this.blocks.get(node.name) || node;
|
|
@@ -2729,6 +3215,217 @@ class Runtime {
|
|
|
2729
3215
|
}
|
|
2730
3216
|
return result;
|
|
2731
3217
|
}
|
|
3218
|
+
cycleState = new Map;
|
|
3219
|
+
renderCycleSync(node, ctx) {
|
|
3220
|
+
const key = node.values.map((v) => JSON.stringify(v)).join(",");
|
|
3221
|
+
const currentIndex = this.cycleState.get(key) ?? 0;
|
|
3222
|
+
const values = node.values.map((v) => this.eval(v, ctx));
|
|
3223
|
+
const value = values[currentIndex % values.length];
|
|
3224
|
+
this.cycleState.set(key, currentIndex + 1);
|
|
3225
|
+
if (node.asVar) {
|
|
3226
|
+
ctx.set(node.asVar, value);
|
|
3227
|
+
return node.silent ? "" : this.stringify(value);
|
|
3228
|
+
}
|
|
3229
|
+
return this.stringify(value);
|
|
3230
|
+
}
|
|
3231
|
+
renderFirstofSync(node, ctx) {
|
|
3232
|
+
for (const expr of node.values) {
|
|
3233
|
+
const value = this.eval(expr, ctx);
|
|
3234
|
+
if (this.isTruthy(value)) {
|
|
3235
|
+
if (node.asVar) {
|
|
3236
|
+
ctx.set(node.asVar, value);
|
|
3237
|
+
return "";
|
|
3238
|
+
}
|
|
3239
|
+
return this.stringify(value);
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
const fallback = node.fallback ? this.eval(node.fallback, ctx) : "";
|
|
3243
|
+
if (node.asVar) {
|
|
3244
|
+
ctx.set(node.asVar, fallback);
|
|
3245
|
+
return "";
|
|
3246
|
+
}
|
|
3247
|
+
return this.stringify(fallback);
|
|
3248
|
+
}
|
|
3249
|
+
ifchangedState = new Map;
|
|
3250
|
+
renderIfchangedSync(node, ctx) {
|
|
3251
|
+
const key = `ifchanged_${node.line}_${node.column}`;
|
|
3252
|
+
let currentValue;
|
|
3253
|
+
if (node.values.length > 0) {
|
|
3254
|
+
currentValue = node.values.map((v) => this.eval(v, ctx));
|
|
3255
|
+
} else {
|
|
3256
|
+
currentValue = this.renderNodesSync(node.body, ctx);
|
|
3257
|
+
}
|
|
3258
|
+
const lastValue = this.ifchangedState.get(key);
|
|
3259
|
+
const changed = JSON.stringify(currentValue) !== JSON.stringify(lastValue);
|
|
3260
|
+
this.ifchangedState.set(key, currentValue);
|
|
3261
|
+
if (changed) {
|
|
3262
|
+
if (node.values.length > 0) {
|
|
3263
|
+
return this.renderNodesSync(node.body, ctx);
|
|
3264
|
+
}
|
|
3265
|
+
return currentValue;
|
|
3266
|
+
} else {
|
|
3267
|
+
return this.renderNodesSync(node.else_, ctx);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
renderRegroupSync(node, ctx) {
|
|
3271
|
+
const list2 = this.toIterable(this.eval(node.target, ctx));
|
|
3272
|
+
const groups = [];
|
|
3273
|
+
let currentGrouper = Symbol("initial");
|
|
3274
|
+
let currentList = [];
|
|
3275
|
+
for (const item of list2) {
|
|
3276
|
+
const grouper = item && typeof item === "object" ? item[node.key] : undefined;
|
|
3277
|
+
if (grouper !== currentGrouper) {
|
|
3278
|
+
if (currentList.length > 0) {
|
|
3279
|
+
groups.push({ grouper: currentGrouper, list: currentList });
|
|
3280
|
+
}
|
|
3281
|
+
currentGrouper = grouper;
|
|
3282
|
+
currentList = [item];
|
|
3283
|
+
} else {
|
|
3284
|
+
currentList.push(item);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
if (currentList.length > 0) {
|
|
3288
|
+
groups.push({ grouper: currentGrouper, list: currentList });
|
|
3289
|
+
}
|
|
3290
|
+
ctx.set(node.asVar, groups);
|
|
3291
|
+
return "";
|
|
3292
|
+
}
|
|
3293
|
+
renderWidthratioSync(node, ctx) {
|
|
3294
|
+
const value = Number(this.eval(node.value, ctx));
|
|
3295
|
+
const maxValue = Number(this.eval(node.maxValue, ctx));
|
|
3296
|
+
const maxWidth = Number(this.eval(node.maxWidth, ctx));
|
|
3297
|
+
const ratio = maxValue === 0 ? 0 : Math.round(value / maxValue * maxWidth);
|
|
3298
|
+
if (node.asVar) {
|
|
3299
|
+
ctx.set(node.asVar, ratio);
|
|
3300
|
+
return "";
|
|
3301
|
+
}
|
|
3302
|
+
return String(ratio);
|
|
3303
|
+
}
|
|
3304
|
+
renderLoremSync(node, ctx) {
|
|
3305
|
+
const count = node.count ? Number(this.eval(node.count, ctx)) : 1;
|
|
3306
|
+
const method = node.method;
|
|
3307
|
+
const words = [
|
|
3308
|
+
"lorem",
|
|
3309
|
+
"ipsum",
|
|
3310
|
+
"dolor",
|
|
3311
|
+
"sit",
|
|
3312
|
+
"amet",
|
|
3313
|
+
"consectetur",
|
|
3314
|
+
"adipiscing",
|
|
3315
|
+
"elit",
|
|
3316
|
+
"sed",
|
|
3317
|
+
"do",
|
|
3318
|
+
"eiusmod",
|
|
3319
|
+
"tempor",
|
|
3320
|
+
"incididunt",
|
|
3321
|
+
"ut",
|
|
3322
|
+
"labore",
|
|
3323
|
+
"et",
|
|
3324
|
+
"dolore",
|
|
3325
|
+
"magna",
|
|
3326
|
+
"aliqua",
|
|
3327
|
+
"enim",
|
|
3328
|
+
"ad",
|
|
3329
|
+
"minim",
|
|
3330
|
+
"veniam",
|
|
3331
|
+
"quis",
|
|
3332
|
+
"nostrud",
|
|
3333
|
+
"exercitation",
|
|
3334
|
+
"ullamco",
|
|
3335
|
+
"laboris",
|
|
3336
|
+
"nisi",
|
|
3337
|
+
"aliquip",
|
|
3338
|
+
"ex",
|
|
3339
|
+
"ea",
|
|
3340
|
+
"commodo",
|
|
3341
|
+
"consequat",
|
|
3342
|
+
"duis",
|
|
3343
|
+
"aute",
|
|
3344
|
+
"irure",
|
|
3345
|
+
"in",
|
|
3346
|
+
"reprehenderit",
|
|
3347
|
+
"voluptate",
|
|
3348
|
+
"velit",
|
|
3349
|
+
"esse",
|
|
3350
|
+
"cillum",
|
|
3351
|
+
"fugiat",
|
|
3352
|
+
"nulla",
|
|
3353
|
+
"pariatur",
|
|
3354
|
+
"excepteur",
|
|
3355
|
+
"sint",
|
|
3356
|
+
"occaecat",
|
|
3357
|
+
"cupidatat",
|
|
3358
|
+
"non",
|
|
3359
|
+
"proident",
|
|
3360
|
+
"sunt",
|
|
3361
|
+
"culpa",
|
|
3362
|
+
"qui",
|
|
3363
|
+
"officia",
|
|
3364
|
+
"deserunt",
|
|
3365
|
+
"mollit",
|
|
3366
|
+
"anim",
|
|
3367
|
+
"id",
|
|
3368
|
+
"est",
|
|
3369
|
+
"laborum"
|
|
3370
|
+
];
|
|
3371
|
+
const getWord = (index) => {
|
|
3372
|
+
if (node.random) {
|
|
3373
|
+
return words[Math.floor(Math.random() * words.length)];
|
|
3374
|
+
}
|
|
3375
|
+
return words[index % words.length];
|
|
3376
|
+
};
|
|
3377
|
+
if (method === "w") {
|
|
3378
|
+
const result = [];
|
|
3379
|
+
for (let i = 0;i < count; i++) {
|
|
3380
|
+
result.push(getWord(i));
|
|
3381
|
+
}
|
|
3382
|
+
return result.join(" ");
|
|
3383
|
+
} else if (method === "p" || method === "b") {
|
|
3384
|
+
const paragraphs = [];
|
|
3385
|
+
for (let p = 0;p < count; p++) {
|
|
3386
|
+
const sentenceCount = 3 + p % 3;
|
|
3387
|
+
const sentences = [];
|
|
3388
|
+
for (let s = 0;s < sentenceCount; s++) {
|
|
3389
|
+
const wordCount = 8 + s % 7;
|
|
3390
|
+
const sentenceWords = [];
|
|
3391
|
+
for (let w = 0;w < wordCount; w++) {
|
|
3392
|
+
sentenceWords.push(getWord(p * 100 + s * 20 + w));
|
|
3393
|
+
}
|
|
3394
|
+
sentenceWords[0] = sentenceWords[0].charAt(0).toUpperCase() + sentenceWords[0].slice(1);
|
|
3395
|
+
sentences.push(sentenceWords.join(" ") + ".");
|
|
3396
|
+
}
|
|
3397
|
+
paragraphs.push(sentences.join(" "));
|
|
3398
|
+
}
|
|
3399
|
+
if (method === "b") {
|
|
3400
|
+
return paragraphs.join(`
|
|
3401
|
+
|
|
3402
|
+
`);
|
|
3403
|
+
}
|
|
3404
|
+
return paragraphs.map((p) => `<p>${p}</p>`).join(`
|
|
3405
|
+
`);
|
|
3406
|
+
}
|
|
3407
|
+
return "";
|
|
3408
|
+
}
|
|
3409
|
+
renderCsrfTokenSync() {
|
|
3410
|
+
return '<input type="hidden" name="csrfmiddlewaretoken" value="CSRF_TOKEN_PLACEHOLDER">';
|
|
3411
|
+
}
|
|
3412
|
+
renderDebugSync(ctx) {
|
|
3413
|
+
const data = ctx.toObject?.() || {};
|
|
3414
|
+
return `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
|
3415
|
+
}
|
|
3416
|
+
renderTemplatetagSync(node) {
|
|
3417
|
+
const tagMap = {
|
|
3418
|
+
openblock: "{%",
|
|
3419
|
+
closeblock: "%}",
|
|
3420
|
+
openvariable: "{{",
|
|
3421
|
+
closevariable: "}}",
|
|
3422
|
+
openbrace: "{",
|
|
3423
|
+
closebrace: "}",
|
|
3424
|
+
opencomment: "{#",
|
|
3425
|
+
closecomment: "#}"
|
|
3426
|
+
};
|
|
3427
|
+
return tagMap[node.tagType] || "";
|
|
3428
|
+
}
|
|
2732
3429
|
getDateInTimezone(d, tz) {
|
|
2733
3430
|
if (!tz) {
|
|
2734
3431
|
return {
|
|
@@ -2837,22 +3534,36 @@ class Runtime {
|
|
|
2837
3534
|
return result;
|
|
2838
3535
|
}
|
|
2839
3536
|
renderNodesSync(nodes2, ctx) {
|
|
3537
|
+
const len = nodes2.length;
|
|
3538
|
+
if (len === 0)
|
|
3539
|
+
return "";
|
|
3540
|
+
if (len === 1) {
|
|
3541
|
+
return this.renderNodeSync(nodes2[0], ctx) ?? "";
|
|
3542
|
+
}
|
|
3543
|
+
if (len === 2) {
|
|
3544
|
+
const a = this.renderNodeSync(nodes2[0], ctx) ?? "";
|
|
3545
|
+
const b = this.renderNodeSync(nodes2[1], ctx) ?? "";
|
|
3546
|
+
return a + b;
|
|
3547
|
+
}
|
|
2840
3548
|
const parts = [];
|
|
2841
|
-
for (
|
|
2842
|
-
const result = this.renderNodeSync(
|
|
3549
|
+
for (let i = 0;i < len; i++) {
|
|
3550
|
+
const result = this.renderNodeSync(nodes2[i], ctx);
|
|
2843
3551
|
if (result !== null)
|
|
2844
3552
|
parts.push(result);
|
|
2845
3553
|
}
|
|
2846
3554
|
return parts.join("");
|
|
2847
3555
|
}
|
|
2848
3556
|
eval(node, ctx) {
|
|
3557
|
+
if (node.type === "Literal") {
|
|
3558
|
+
return node.value;
|
|
3559
|
+
}
|
|
3560
|
+
if (node.type === "Name") {
|
|
3561
|
+
return ctx.get(node.name);
|
|
3562
|
+
}
|
|
3563
|
+
if (node.type === "GetAttr") {
|
|
3564
|
+
return this.evalGetAttr(node, ctx);
|
|
3565
|
+
}
|
|
2849
3566
|
switch (node.type) {
|
|
2850
|
-
case "Literal":
|
|
2851
|
-
return node.value;
|
|
2852
|
-
case "Name":
|
|
2853
|
-
return ctx.get(node.name);
|
|
2854
|
-
case "GetAttr":
|
|
2855
|
-
return this.evalGetAttr(node, ctx);
|
|
2856
3567
|
case "GetItem":
|
|
2857
3568
|
return this.evalGetItem(node, ctx);
|
|
2858
3569
|
case "FilterExpr":
|
|
@@ -2888,13 +3599,13 @@ class Runtime {
|
|
|
2888
3599
|
if (obj == null)
|
|
2889
3600
|
return;
|
|
2890
3601
|
const attr2 = node.attribute;
|
|
2891
|
-
if (Array.isArray(obj)) {
|
|
2892
|
-
const numIndex = parseInt(attr2, 10);
|
|
2893
|
-
if (!isNaN(numIndex))
|
|
2894
|
-
return obj[numIndex];
|
|
2895
|
-
}
|
|
2896
3602
|
const value = obj[attr2];
|
|
2897
|
-
if (value === undefined
|
|
3603
|
+
if (value === undefined) {
|
|
3604
|
+
if (Array.isArray(obj)) {
|
|
3605
|
+
const numIndex = parseInt(attr2, 10);
|
|
3606
|
+
if (!isNaN(numIndex))
|
|
3607
|
+
return obj[numIndex];
|
|
3608
|
+
}
|
|
2898
3609
|
return;
|
|
2899
3610
|
}
|
|
2900
3611
|
if (typeof value === "function") {
|
|
@@ -2923,22 +3634,32 @@ class Runtime {
|
|
|
2923
3634
|
}
|
|
2924
3635
|
evalBinaryOp(node, ctx) {
|
|
2925
3636
|
const left = this.eval(node.left, ctx);
|
|
2926
|
-
|
|
3637
|
+
const op = node.operator;
|
|
3638
|
+
if (op === "and")
|
|
2927
3639
|
return this.isTruthy(left) ? this.eval(node.right, ctx) : left;
|
|
2928
|
-
if (
|
|
3640
|
+
if (op === "or")
|
|
2929
3641
|
return this.isTruthy(left) ? left : this.eval(node.right, ctx);
|
|
2930
3642
|
const right = this.eval(node.right, ctx);
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
return
|
|
3643
|
+
if (op === "%") {
|
|
3644
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3645
|
+
return right === 0 ? NaN : left % right;
|
|
3646
|
+
}
|
|
3647
|
+
const r = Number(right);
|
|
3648
|
+
return r === 0 ? NaN : Number(left) % r;
|
|
3649
|
+
}
|
|
3650
|
+
if (op === "+") {
|
|
3651
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3652
|
+
return left + right;
|
|
3653
|
+
}
|
|
3654
|
+
return typeof left === "string" || typeof right === "string" ? String(left) + String(right) : Number(left) + Number(right);
|
|
3655
|
+
}
|
|
3656
|
+
switch (op) {
|
|
2934
3657
|
case "-":
|
|
2935
3658
|
return Number(left) - Number(right);
|
|
2936
3659
|
case "*":
|
|
2937
3660
|
return Number(left) * Number(right);
|
|
2938
3661
|
case "/":
|
|
2939
3662
|
return Number(left) / Number(right);
|
|
2940
|
-
case "%":
|
|
2941
|
-
return Number(right) === 0 ? NaN : Number(left) % Number(right);
|
|
2942
3663
|
case "~":
|
|
2943
3664
|
return String(left) + String(right);
|
|
2944
3665
|
default:
|
|
@@ -2959,47 +3680,74 @@ class Runtime {
|
|
|
2959
3680
|
}
|
|
2960
3681
|
}
|
|
2961
3682
|
evalCompare(node, ctx) {
|
|
2962
|
-
|
|
2963
|
-
|
|
3683
|
+
const left = this.eval(node.left, ctx);
|
|
3684
|
+
const ops = node.ops;
|
|
3685
|
+
if (ops.length === 1) {
|
|
3686
|
+
const { operator, right: rightNode } = ops[0];
|
|
3687
|
+
const right = this.eval(rightNode, ctx);
|
|
3688
|
+
if (operator === "==")
|
|
3689
|
+
return left === right;
|
|
3690
|
+
if (operator === "!=")
|
|
3691
|
+
return left !== right;
|
|
3692
|
+
if (operator === "<")
|
|
3693
|
+
return left < right;
|
|
3694
|
+
if (operator === ">")
|
|
3695
|
+
return left > right;
|
|
3696
|
+
if (operator === "<=")
|
|
3697
|
+
return left <= right;
|
|
3698
|
+
if (operator === ">=")
|
|
3699
|
+
return left >= right;
|
|
3700
|
+
if (operator === "in")
|
|
3701
|
+
return this.isIn(left, right);
|
|
3702
|
+
if (operator === "not in")
|
|
3703
|
+
return !this.isIn(left, right);
|
|
3704
|
+
if (operator === "is")
|
|
3705
|
+
return left === right;
|
|
3706
|
+
if (operator === "is not")
|
|
3707
|
+
return left !== right;
|
|
3708
|
+
return false;
|
|
3709
|
+
}
|
|
3710
|
+
let current = left;
|
|
3711
|
+
for (const { operator, right: rightNode } of ops) {
|
|
2964
3712
|
const right = this.eval(rightNode, ctx);
|
|
2965
3713
|
let result;
|
|
2966
3714
|
switch (operator) {
|
|
2967
3715
|
case "==":
|
|
2968
|
-
result =
|
|
3716
|
+
result = current === right;
|
|
2969
3717
|
break;
|
|
2970
3718
|
case "!=":
|
|
2971
|
-
result =
|
|
3719
|
+
result = current !== right;
|
|
2972
3720
|
break;
|
|
2973
3721
|
case "<":
|
|
2974
|
-
result =
|
|
3722
|
+
result = current < right;
|
|
2975
3723
|
break;
|
|
2976
3724
|
case ">":
|
|
2977
|
-
result =
|
|
3725
|
+
result = current > right;
|
|
2978
3726
|
break;
|
|
2979
3727
|
case "<=":
|
|
2980
|
-
result =
|
|
3728
|
+
result = current <= right;
|
|
2981
3729
|
break;
|
|
2982
3730
|
case ">=":
|
|
2983
|
-
result =
|
|
3731
|
+
result = current >= right;
|
|
2984
3732
|
break;
|
|
2985
3733
|
case "in":
|
|
2986
|
-
result = this.isIn(
|
|
3734
|
+
result = this.isIn(current, right);
|
|
2987
3735
|
break;
|
|
2988
3736
|
case "not in":
|
|
2989
|
-
result = !this.isIn(
|
|
3737
|
+
result = !this.isIn(current, right);
|
|
2990
3738
|
break;
|
|
2991
3739
|
case "is":
|
|
2992
|
-
result =
|
|
3740
|
+
result = current === right;
|
|
2993
3741
|
break;
|
|
2994
3742
|
case "is not":
|
|
2995
|
-
result =
|
|
3743
|
+
result = current !== right;
|
|
2996
3744
|
break;
|
|
2997
3745
|
default:
|
|
2998
3746
|
result = false;
|
|
2999
3747
|
}
|
|
3000
3748
|
if (!result)
|
|
3001
3749
|
return false;
|
|
3002
|
-
|
|
3750
|
+
current = right;
|
|
3003
3751
|
}
|
|
3004
3752
|
return true;
|
|
3005
3753
|
}
|
|
@@ -3100,6 +3848,24 @@ class Runtime {
|
|
|
3100
3848
|
return this.renderStaticSync(node, ctx);
|
|
3101
3849
|
case "Now":
|
|
3102
3850
|
return this.renderNowSync(node, ctx);
|
|
3851
|
+
case "Cycle":
|
|
3852
|
+
return this.renderCycleSync(node, ctx);
|
|
3853
|
+
case "Firstof":
|
|
3854
|
+
return this.renderFirstofSync(node, ctx);
|
|
3855
|
+
case "Ifchanged":
|
|
3856
|
+
return this.renderIfchangedSync(node, ctx);
|
|
3857
|
+
case "Regroup":
|
|
3858
|
+
return this.renderRegroupSync(node, ctx);
|
|
3859
|
+
case "Widthratio":
|
|
3860
|
+
return this.renderWidthratioSync(node, ctx);
|
|
3861
|
+
case "Lorem":
|
|
3862
|
+
return this.renderLoremSync(node, ctx);
|
|
3863
|
+
case "CsrfToken":
|
|
3864
|
+
return this.renderCsrfTokenSync();
|
|
3865
|
+
case "Debug":
|
|
3866
|
+
return this.renderDebugSync(ctx);
|
|
3867
|
+
case "Templatetag":
|
|
3868
|
+
return this.renderTemplatetagSync(node);
|
|
3103
3869
|
default:
|
|
3104
3870
|
return null;
|
|
3105
3871
|
}
|
|
@@ -3205,25 +3971,29 @@ class Runtime {
|
|
|
3205
3971
|
stringify(value) {
|
|
3206
3972
|
if (value == null)
|
|
3207
3973
|
return "";
|
|
3974
|
+
if (typeof value === "string") {
|
|
3975
|
+
if (value.__safe__)
|
|
3976
|
+
return value;
|
|
3977
|
+
return this.options.autoescape ? Bun.escapeHTML(value) : value;
|
|
3978
|
+
}
|
|
3208
3979
|
if (typeof value === "boolean")
|
|
3209
3980
|
return value ? "True" : "False";
|
|
3981
|
+
if (typeof value === "number")
|
|
3982
|
+
return String(value);
|
|
3210
3983
|
const str = String(value);
|
|
3211
3984
|
if (value.__safe__)
|
|
3212
3985
|
return str;
|
|
3213
|
-
|
|
3214
|
-
return Bun.escapeHTML(str);
|
|
3215
|
-
}
|
|
3216
|
-
return str;
|
|
3986
|
+
return this.options.autoescape ? Bun.escapeHTML(str) : str;
|
|
3217
3987
|
}
|
|
3218
3988
|
isTruthy(value) {
|
|
3219
|
-
if (value == null)
|
|
3220
|
-
return false;
|
|
3221
3989
|
if (typeof value === "boolean")
|
|
3222
3990
|
return value;
|
|
3223
|
-
if (
|
|
3224
|
-
return
|
|
3991
|
+
if (value == null)
|
|
3992
|
+
return false;
|
|
3225
3993
|
if (typeof value === "string")
|
|
3226
3994
|
return value.length > 0;
|
|
3995
|
+
if (typeof value === "number")
|
|
3996
|
+
return value !== 0;
|
|
3227
3997
|
if (Array.isArray(value))
|
|
3228
3998
|
return value.length > 0;
|
|
3229
3999
|
if (typeof value === "object") {
|