@rhinostone/swig 2.4.3 → 2.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/HISTORY.md +6 -1
- package/README.md +1 -1
- package/ROADMAP.md +6 -1
- package/dist/swig.js +93 -9
- package/dist/swig.min.js +2 -2
- package/dist/swig.min.js.map +1 -1
- package/lib/dateformatter.js +1 -1
- package/lib/swig.js +1 -1
- package/lib/tags/extends.js +8 -8
- package/lib/tags/filter.js +1 -1
- package/lib/tags/if.js +5 -6
- package/lib/tags/import.js +6 -6
- package/lib/tags/parent.js +4 -4
- package/package.json +2 -2
package/HISTORY.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
[2.5.0](https://github.com/gina-io/swig/tree/v2.5.0) / 2026-05-27
|
|
2
|
+
-----------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
* **Added** @rhinostone/swig-jinja2 — a Python Jinja2-syntax frontend on the shared swig-core engine (13 tags, 39 filters, 16 is-tests, async loader support, autoescape and CVE-2023-25345 guards inherited from swig-core). Releases in lockstep with @rhinostone/swig, @rhinostone/swig-core, and @rhinostone/swig-twig.
|
|
5
|
+
|
|
1
6
|
[2.4.3](https://github.com/gina-io/swig/tree/v2.4.3) / 2026-05-22
|
|
2
7
|
-----------------------------------------------------------------
|
|
3
8
|
|
|
@@ -57,7 +62,7 @@
|
|
|
57
62
|
[2.0.0](https://github.com/gina-io/swig/tree/v2.0.0) / 2026-05-06
|
|
58
63
|
-----------------------------------------------------------------
|
|
59
64
|
|
|
60
|
-
* **Fixed** Refreshed stale external URL references across documentation surfaces from the 2026-04-13 link-health audit (gina-io/gina#18). `README.md`, `packages/swig-core/README.md`, `packages/swig-twig/README.md` — swapped `www.npmjs.org` for the canonical `www.npmjs.com` in the NPM badges (the old `.org` form now redirects). `README.md` — rewrote the "Swig v0.x → v1.x migration notes" bullet, since the `paularmstrong/swig/wiki/Migrating-from-...` page was deleted upstream (302s to repo root). `HISTORY.md` — dropped the three dead upstream-wiki links around the v1.0.0 / v1.0.0-pre1 entries; the breaking-change prose stays intact. `browser/comments.js` — updated the DateZ `@license` URL from `TomoUniversalis/DateZ` to `ocrybit/DateZ` (the upstream GitHub user was renamed). `packages/swig-core/lib/dateformatter.js` — dropped the dead `
|
|
65
|
+
* **Fixed** Refreshed stale external URL references across documentation surfaces from the 2026-04-13 link-health audit (gina-io/gina#18). `README.md`, `packages/swig-core/README.md`, `packages/swig-twig/README.md` — swapped `www.npmjs.org` for the canonical `www.npmjs.com` in the NPM badges (the old `.org` form now redirects). `README.md` — rewrote the "Swig v0.x → v1.x migration notes" bullet, since the `paularmstrong/swig/wiki/Migrating-from-...` page was deleted upstream (302s to repo root). `HISTORY.md` — dropped the three dead upstream-wiki links around the v1.0.0 / v1.0.0-pre1 entries; the breaking-change prose stays intact. `browser/comments.js` — updated the DateZ `@license` URL from `TomoUniversalis/DateZ` to `ocrybit/DateZ` (the upstream GitHub user was renamed). `packages/swig-core/lib/dateformatter.js` — dropped the dead `tomouniversalis.com` parenthetical from the DateZ copyright comment (domain NXDOMAINs). `.changes/v1.0.0.md` and `.changes/v1.0.0-pre1.md` synced to match the rewritten `HISTORY.md` lines so future `changie merge` runs stay idempotent. Documentation and comment-only; no runtime or API change.
|
|
61
66
|
|
|
62
67
|
* **Changed** `2.0.0` stable. Multi-flavor architecture (introduced across `2.0.0-alpha.1` through `2.0.0-alpha.5`) is the production-ready cut. No functional or API changes since `2.0.0-alpha.5`; the `alpha.6`–`alpha.8` cycle was metadata republishes plus removal of the soft-deprecated `exports.parse` Path B wrapper from `@rhinostone/swig-twig`. IR ABI is stable from this release onward; cross-package dependencies pin exact versions and frontends + core release in lockstep. README messaging refreshed across all three packages to reflect production-ready status; package descriptions cleaned of historical internal-tracking references.
|
|
63
68
|
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ For Twig syntax:
|
|
|
64
64
|
Documentation
|
|
65
65
|
-------------
|
|
66
66
|
|
|
67
|
-
User-facing documentation lives in the Gina Docusaurus site under the [Swig Template Engine](https://gina.io/docs/swig) section, maintained in [gina-io/docs](https://github.com/gina-io/docs) at `docs/swig/`. The JSDoc blocks in `lib/swig.js`, `lib/filters.js`, `lib/tags/`, and `lib/loaders/` remain the canonical source-of-truth for the public API and are mirrored into the Docusaurus pages.
|
|
67
|
+
User-facing documentation lives in the Gina Docusaurus site under the [Swig Template Engine](https://gina.io/docs/swig) section, maintained in [gina-io/docs](https://github.com/gina-io/docs) at `docs/templating/swig/`. The JSDoc blocks in `lib/swig.js`, `lib/filters.js`, `lib/tags/`, and `lib/loaders/` remain the canonical source-of-truth for the public API and are mirrored into the Docusaurus pages.
|
|
68
68
|
|
|
69
69
|
Basic Example
|
|
70
70
|
-------------
|
package/ROADMAP.md
CHANGED
|
@@ -15,13 +15,18 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
|
|
|
15
15
|
| Status | Item |
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| Planned | Async parse path for dynamic targets — full support for `{% extends parent_var %}`, `{% include user_template %}`, and runtime-resolved `import` / `from` paths on the async-codegen branch. Static-target async dispatch shipped in 2.2.0; dynamic-target support is on hold pending consumer demand. |
|
|
18
|
-
| Planned | Ship
|
|
18
|
+
| Planned | Ship a Django frontend as an additional `@rhinostone/swig-*` package. On demand — when there's concrete user demand. (The Jinja2 frontend shipped in `2.5.0`.) |
|
|
19
19
|
| Planned | Test framework migration. Replace mocha 1.x + expect.js with `node:test` + `node:assert/strict`, swap mocha-phantomjs for a modern browser-test harness, swap blanket for `c8`. (The Node engines bump is upstream-driven by gina and is being treated as done.) |
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
## Completed
|
|
24
24
|
|
|
25
|
+
### v2.5.0 (May 2026)
|
|
26
|
+
|
|
27
|
+
- Added `@rhinostone/swig-jinja2`, a Python Jinja2-syntax frontend on the shared `@rhinostone/swig-core` engine — the third dialect in the multi-flavor family alongside native swig and `@rhinostone/swig-twig`. Ships 13 tags (`set`, `if` / `elif` / `else`, `for` with `else`, `block`, `extends`, `include`, `macro`, `import`, `from`, `raw`, `filter`, `with`, `autoescape`), 39 filters, and 16 `is` tests, plus the `**` / `//` / `~` operators, inline-if, Python slicing, and `{{- … -}}` whitespace control. Async loader support via `renderFileAsync` / `compileFileAsync`. Autoescape and the CVE-2023-25345 guards are inherited from `@rhinostone/swig-core`. Every filter and is-test was cross-checked against Python Jinja2 3.x; the behavioural differences (where the JavaScript runtime diverges from CPython) and the explicit non-goals (no sandboxed rendering, `{% call %}` / `{% do %}` / `{% trans %}`, the `map` / `select` filter family, macro kwargs) are documented in the Jinja2 templating guide.
|
|
28
|
+
- All four packages (`@rhinostone/swig`, `@rhinostone/swig-core`, `@rhinostone/swig-twig`, `@rhinostone/swig-jinja2`) released in lockstep at `2.5.0`. `@rhinostone/swig-core` gained additive `slice` and `coerceOutput` runtime helpers consumed by the Jinja2 frontend; native swig and Twig are functionally unchanged.
|
|
29
|
+
|
|
25
30
|
### v2.4.3 (May 2026)
|
|
26
31
|
|
|
27
32
|
- Fixed native `import` leaking an imported file's own import aliases into the importing template's scope. The `{% import %}` carry-through added in `2.4.1` emitted those nested imports bare into the caller's context, where the leaked alias could clobber a same-named caller variable, corrupt a macro when the caller later reassigned that name (macros read the live context at call time), or cascade across import depth. The nested imports are now re-homed under the importing alias and kept local to the file that declares them — matching `@rhinostone/swig-twig`'s scoping and the Jinja2/Twig import contract. Macros still resolve their own file's imports at call time.
|
package/dist/swig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Swig v2.
|
|
1
|
+
/*! Swig v2.5.0 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
|
|
2
2
|
/*! DateZ (c) 2011 Tomo Universalis | @license https://github.com/ocrybit/DateZ/blob/master/LISENCE */
|
|
3
3
|
(() => {
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -139,6 +139,68 @@
|
|
|
139
139
|
}
|
|
140
140
|
return out;
|
|
141
141
|
};
|
|
142
|
+
exports.slice = function(obj, start, stop, step) {
|
|
143
|
+
var isString = typeof obj === "string", length, lower, upper, s, e, result = [], i;
|
|
144
|
+
function toInt(n) {
|
|
145
|
+
n = Number(n);
|
|
146
|
+
if (isNaN(n)) {
|
|
147
|
+
return NaN;
|
|
148
|
+
}
|
|
149
|
+
return n < 0 ? Math.ceil(n) : Math.floor(n);
|
|
150
|
+
}
|
|
151
|
+
function clamp(v, dflt) {
|
|
152
|
+
if (v === null || v === void 0) {
|
|
153
|
+
return dflt;
|
|
154
|
+
}
|
|
155
|
+
v = toInt(v);
|
|
156
|
+
if (isNaN(v)) {
|
|
157
|
+
return dflt;
|
|
158
|
+
}
|
|
159
|
+
if (v < 0) {
|
|
160
|
+
v += length;
|
|
161
|
+
if (v < lower) {
|
|
162
|
+
v = lower;
|
|
163
|
+
}
|
|
164
|
+
} else if (v > upper) {
|
|
165
|
+
v = upper;
|
|
166
|
+
}
|
|
167
|
+
return v;
|
|
168
|
+
}
|
|
169
|
+
if (obj === null || obj === void 0 || typeof obj.length !== "number") {
|
|
170
|
+
return isString ? "" : [];
|
|
171
|
+
}
|
|
172
|
+
length = obj.length;
|
|
173
|
+
if (step === null || step === void 0) {
|
|
174
|
+
step = 1;
|
|
175
|
+
} else {
|
|
176
|
+
step = toInt(step);
|
|
177
|
+
if (isNaN(step) || step === 0) {
|
|
178
|
+
step = 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (step < 0) {
|
|
182
|
+
lower = -1;
|
|
183
|
+
upper = length - 1;
|
|
184
|
+
} else {
|
|
185
|
+
lower = 0;
|
|
186
|
+
upper = length;
|
|
187
|
+
}
|
|
188
|
+
s = clamp(start, step < 0 ? upper : lower);
|
|
189
|
+
e = clamp(stop, step < 0 ? lower : upper);
|
|
190
|
+
if (step > 0) {
|
|
191
|
+
for (i = s; i < e; i += step) {
|
|
192
|
+
result.push(obj[i]);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
for (i = s; i > e; i += step) {
|
|
196
|
+
result.push(obj[i]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return isString ? result.join("") : result;
|
|
200
|
+
};
|
|
201
|
+
exports.coerceOutput = function(v) {
|
|
202
|
+
return v === null || v === void 0 ? "" : v;
|
|
203
|
+
};
|
|
142
204
|
}
|
|
143
205
|
});
|
|
144
206
|
|
|
@@ -1366,7 +1428,22 @@
|
|
|
1366
1428
|
return;
|
|
1367
1429
|
}
|
|
1368
1430
|
});
|
|
1369
|
-
|
|
1431
|
+
var forEmptyCheck = " if (!__l) { return; }\n";
|
|
1432
|
+
if (node.emptyBody) {
|
|
1433
|
+
var forEmptyJS = "";
|
|
1434
|
+
utils.each(node.emptyBody, function(b) {
|
|
1435
|
+
if (b.type === "LegacyJS") {
|
|
1436
|
+
forEmptyJS += b.js;
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
if (b.type === "Text" || b.type === "Raw") {
|
|
1440
|
+
forEmptyJS += '_output += "' + escapeTextValue(b.value) + '";\n';
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
forEmptyCheck = " if (!__l || !__len) {\n" + forEmptyJS + " return;\n }\n";
|
|
1445
|
+
}
|
|
1446
|
+
out += "(function () {\n var __l = " + forIterable + ', __len = (_utils.isArray(__l) || typeof __l === "string") ? __l.length : _utils.keys(__l).length;\n' + forEmptyCheck + " var " + ctxloopcache + " = { loop: " + ctxloop + ", " + forVal + ": " + ctx + forVal + ", " + forKey + ": " + ctx + forKey + " };\n " + ctxloop + " = { first: false, index: 1, index0: 0, revindex: __len, revindex0: __len - 1, length: __len, last: false };\n _utils.each(__l, function (" + forVal + ", " + forKey + ") {\n " + ctx + forVal + " = " + forVal + ";\n " + ctx + forKey + " = " + forKey + ";\n " + ctxloop + ".key = " + forKey + ";\n " + ctxloop + ".first = (" + ctxloop + ".index0 === 0);\n " + ctxloop + ".last = (" + ctxloop + ".revindex0 === 0);\n " + forBodyJS + " " + ctxloop + ".index += 1; " + ctxloop + ".index0 += 1; " + ctxloop + ".revindex -= 1; " + ctxloop + ".revindex0 -= 1;\n });\n " + ctxloop + " = " + ctxloopcache + ".loop;\n " + ctx + forVal + " = " + ctxloopcache + "." + forVal + ";\n " + ctx + forKey + " = " + ctxloopcache + "." + forKey + ";\n " + ctxloopcache + " = undefined;\n})();\n";
|
|
1370
1447
|
return;
|
|
1371
1448
|
}
|
|
1372
1449
|
if (node.type === "Macro") {
|
|
@@ -1381,11 +1458,14 @@
|
|
|
1381
1458
|
return;
|
|
1382
1459
|
}
|
|
1383
1460
|
});
|
|
1384
|
-
var macroParams = node.params || [], macroSigJS, macroIndexOfJS;
|
|
1461
|
+
var macroParams = node.params || [], macroSigJS, macroIndexOfJS, macroDefaultsJS = "";
|
|
1385
1462
|
if (macroParams.length && typeof macroParams[0] === "object" && macroParams[0] !== null && typeof macroParams[0].name === "string") {
|
|
1386
1463
|
var macroNames = [];
|
|
1387
1464
|
utils.each(macroParams, function(p) {
|
|
1388
1465
|
macroNames.push(p.name);
|
|
1466
|
+
if (p["default"]) {
|
|
1467
|
+
macroDefaultsJS += " if (" + p.name + " === undefined) { " + p.name + " = " + exports.emitExpr(p["default"]) + "; }\n";
|
|
1468
|
+
}
|
|
1389
1469
|
});
|
|
1390
1470
|
macroSigJS = macroNames.join(", ");
|
|
1391
1471
|
var macroJsonNames = [];
|
|
@@ -1397,7 +1477,7 @@
|
|
|
1397
1477
|
macroSigJS = macroParams.join("");
|
|
1398
1478
|
macroIndexOfJS = '"' + macroParams.join('","') + '"';
|
|
1399
1479
|
}
|
|
1400
|
-
out += "_ctx." + node.name + " = function (" + macroSigJS + ') {\n var _output = "",\n __ctx = _utils.extend({}, _ctx);\n _utils.each(_ctx, function (v, k) {\n if ([
|
|
1480
|
+
out += "_ctx." + node.name + " = function (" + macroSigJS + ') {\n var _output = "",\n __ctx = _utils.extend({}, _ctx);\n' + macroDefaultsJS + " _utils.each(_ctx, function (v, k) {\n if ([" + macroIndexOfJS + "].indexOf(k) !== -1) { delete _ctx[k]; }\n });\n" + macroBodyJS + "\n _ctx = _utils.extend(_ctx, __ctx);\n return _output;\n};\n_ctx." + node.name + ".safe = true;\n";
|
|
1401
1481
|
if (options && options.codegenMode === "async") {
|
|
1402
1482
|
if (_security.dangerousProps.indexOf(node.name) !== -1) {
|
|
1403
1483
|
throw new Error('Macro name "' + node.name + '" is reserved.');
|
|
@@ -1625,7 +1705,11 @@
|
|
|
1625
1705
|
outExprJS = '_filters["' + fc.name + '"](' + outExprJS + fcArgsJS + ")";
|
|
1626
1706
|
});
|
|
1627
1707
|
}
|
|
1628
|
-
|
|
1708
|
+
if (node.coerce) {
|
|
1709
|
+
out += "_output += _utils.coerceOutput(" + outExprJS + ");\n";
|
|
1710
|
+
} else {
|
|
1711
|
+
out += "_output += " + outExprJS + ";\n";
|
|
1712
|
+
}
|
|
1629
1713
|
return;
|
|
1630
1714
|
}
|
|
1631
1715
|
if (node.type === "Filter") {
|
|
@@ -2356,9 +2440,9 @@
|
|
|
2356
2440
|
* `parseExpr` emits structured IR that {@link backend.emitExpr}
|
|
2357
2441
|
* later lowers into an equivalent JS-source fragment. `.parse()` is
|
|
2358
2442
|
* unchanged and remains the production path; `parseExpr` is the
|
|
2359
|
-
* incoming target shape for
|
|
2360
|
-
*
|
|
2361
|
-
*
|
|
2443
|
+
* incoming target shape for the IR migration, introduced additively
|
|
2444
|
+
* so the IR grammar can be proven against real lexer output before
|
|
2445
|
+
* consumers are flipped over to it.
|
|
2362
2446
|
*
|
|
2363
2447
|
* The CVE-2023-25345 prototype-chain guards (`_dangerousProps` on
|
|
2364
2448
|
* VAR segments, DOTKEY matches, STRING-inside-BRACKETOPEN values,
|
|
@@ -4598,7 +4682,7 @@
|
|
|
4598
4682
|
var loaders = require_loaders2();
|
|
4599
4683
|
var preWalker = require_pre_walker();
|
|
4600
4684
|
var engine = require_engine();
|
|
4601
|
-
exports.version = "2.
|
|
4685
|
+
exports.version = "2.5.0";
|
|
4602
4686
|
var defaultOptions = {
|
|
4603
4687
|
autoescape: true,
|
|
4604
4688
|
varControls: ["{{", "}}"],
|