@rhinostone/swig-django 2.9.13 → 2.11.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 +4 -2
- package/lib/index.js +1 -1
- package/lib/lexer.js +6 -4
- package/lib/parser.js +68 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ var out = swig.render('Hello, {{ name|upper }}!', {
|
|
|
24
24
|
// => Hello, WORLD!
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Supported surface
|
|
28
|
-
|
|
27
|
+
Supported surface
|
|
28
|
+
-----------------
|
|
29
29
|
|
|
30
30
|
Renders real Django templates — everything below was cross-checked against Django 5.2.
|
|
31
31
|
|
|
@@ -33,6 +33,8 @@ Renders real Django templates — everything below was cross-checked against Dja
|
|
|
33
33
|
* **Variable resolution** — Django-faithful attribute / dictionary / index lookup. Callable attributes are auto-called (honoring `alters_data` / `do_not_call_in_templates`), numeric indexing works (`{{ list.0 }}`), and dicts iterate via `.keys` / `.values` / `.items`.
|
|
34
34
|
* **Filter arguments** — Django colon syntax (`{{ value|date:"Y-m-d" }}`).
|
|
35
35
|
* **Filters** — 42 built-ins (`add`, `addslashes`, `capfirst`, `center`, `cut`, `date`, `default`, `default_if_none`, `divisibleby`, `escape` / `e`, `escapejs`, `filesizeformat`, `first`, `floatformat`, `force_escape`, `get_digit`, `iriencode`, `join`, `last`, `length`, `linebreaks`, `linebreaksbr`, `linenumbers`, `ljust`, `lower`, `make_list`, `phone2numeric`, `pluralize`, `rjust`, `safe`, `slice`, `slugify`, `striptags`, `time`, `title`, `truncatechars`, `truncatewords`, `upper`, `urlencode`, `wordcount`, `wordwrap`, `yesno`). See `lib/filters.js` for the full list.
|
|
36
|
+
* **Equality** — `==`, `!=` and `in` compare as Python does: a string never equals a number, and lists and mappings compare item by item.
|
|
37
|
+
* **Unknown names** — a filter name the instance does not hold is a compile error wherever it stands, as in Django. Register custom filters before the compile that uses them.
|
|
36
38
|
* **Async** — `renderFile(path, locals, cb)` against a loader with `async === true`, or `compileFile` with `codegenMode: 'async'`; `renderFileAsync` / `compileFileAsync` still work but are deprecated.
|
|
37
39
|
|
|
38
40
|
Explicitly unsupported (parse-time throw or absent)
|
package/lib/index.js
CHANGED
package/lib/lexer.js
CHANGED
|
@@ -243,9 +243,9 @@ exports.types = TYPES;
|
|
|
243
243
|
/**
|
|
244
244
|
* Match the next token at the start of `str`.
|
|
245
245
|
*
|
|
246
|
-
* Throws
|
|
247
|
-
*
|
|
248
|
-
*
|
|
246
|
+
* Throws when no rule matches. This module does not know the line or the
|
|
247
|
+
* file, so the error is flagged `unplaced` and the Django frontend's
|
|
248
|
+
* `exports.parse` names them.
|
|
249
249
|
*
|
|
250
250
|
* @param {string} str Input slice starting at the unconsumed offset.
|
|
251
251
|
* @return {LexerToken} Matched token.
|
|
@@ -277,7 +277,9 @@ function reader(str) {
|
|
|
277
277
|
});
|
|
278
278
|
|
|
279
279
|
if (!matched) {
|
|
280
|
-
|
|
280
|
+
var err = new Error('Unexpected token "' + str.charAt(0) + '" in Django expression.');
|
|
281
|
+
err.unplaced = true;
|
|
282
|
+
throw err;
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
return matched;
|
package/lib/parser.js
CHANGED
|
@@ -5,6 +5,10 @@ var ir = require('@rhinostone/swig-core/lib/ir'),
|
|
|
5
5
|
var lexer = require('./lexer');
|
|
6
6
|
var _t = require('./tokentypes');
|
|
7
7
|
|
|
8
|
+
// A catalog entry is read as an own property: a filter can be registered under
|
|
9
|
+
// the name hasOwnProperty, and a name like toString is not a filter.
|
|
10
|
+
var hasOwn = Object.prototype.hasOwnProperty;
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* Make a string safe for embedding into a regular expression.
|
|
10
14
|
* @param {string} str
|
|
@@ -21,6 +25,23 @@ function escapeRegExp(str) {
|
|
|
21
25
|
*/
|
|
22
26
|
var _reserved = ['break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with'];
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Build the IR of `left == right` or `left != right`, flagged so swig-core
|
|
30
|
+
* emits Python's equality (`_utils.pyEq`) instead of JavaScript's loose
|
|
31
|
+
* operators.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} op `==` or `!=`.
|
|
34
|
+
* @param {object} left Left operand IR.
|
|
35
|
+
* @param {object} right Right operand IR.
|
|
36
|
+
* @return {object} IRBinaryOp.
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
function equality(op, left, right) {
|
|
40
|
+
var node = ir.binaryOp(op, left, right);
|
|
41
|
+
node.pyEq = 'django';
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
/**
|
|
25
46
|
* Build the IR of `left in right`, or of `left not in right` when `negate`
|
|
26
47
|
* is set: a BinaryOp `in` flagged with this flavor's name, so swig-core
|
|
@@ -154,9 +175,13 @@ exports.missingAsEmpty = function (expr) {
|
|
|
154
175
|
* @param {object} [_posOut] Optional out-param; final cursor stored on
|
|
155
176
|
* `_posOut.pos` to let callers detect partial
|
|
156
177
|
* consumption.
|
|
178
|
+
* @param {object} [_scope] Set by `exports.parse`: the `line` and the
|
|
179
|
+
* `filename` an error names, and the instance's
|
|
180
|
+
* `extensions`, which make a filter name the
|
|
181
|
+
* catalog does not hold an error.
|
|
157
182
|
* @return {object} IRExpr tree.
|
|
158
183
|
*/
|
|
159
|
-
exports.parseExpr = function (tokens, filters, _posOut) {
|
|
184
|
+
exports.parseExpr = function (tokens, filters, _posOut, _scope) {
|
|
160
185
|
var pos = 0;
|
|
161
186
|
filters = filters || {};
|
|
162
187
|
|
|
@@ -173,7 +198,7 @@ exports.parseExpr = function (tokens, filters, _posOut) {
|
|
|
173
198
|
return t;
|
|
174
199
|
}
|
|
175
200
|
function bail(msg) {
|
|
176
|
-
utils.throwError(msg);
|
|
201
|
+
utils.throwError(msg, _scope && _scope.line, _scope && _scope.filename);
|
|
177
202
|
}
|
|
178
203
|
|
|
179
204
|
function guardSegment(segment) {
|
|
@@ -406,7 +431,7 @@ exports.parseExpr = function (tokens, filters, _posOut) {
|
|
|
406
431
|
} else if (tok.type === _t.FILTEREMPTY || tok.type === _t.FILTER) {
|
|
407
432
|
consume();
|
|
408
433
|
fname = tok.match;
|
|
409
|
-
if (
|
|
434
|
+
if (hasOwn.call(filters, fname) ? typeof filters[fname] !== 'function' : (_scope && _scope.extensions)) {
|
|
410
435
|
bail('Invalid filter "' + fname + '"');
|
|
411
436
|
}
|
|
412
437
|
// Django colon-filter: an optional single positional argument
|
|
@@ -525,7 +550,11 @@ exports.parseExpr = function (tokens, filters, _posOut) {
|
|
|
525
550
|
continue;
|
|
526
551
|
}
|
|
527
552
|
var right = parseExpression(info.prec + 1);
|
|
528
|
-
|
|
553
|
+
if (info.op === 'in' || info.op === 'not in') {
|
|
554
|
+
left = membership(left, right, info.op === 'not in');
|
|
555
|
+
} else {
|
|
556
|
+
left = (info.op === '==' || info.op === '!=') ? equality(info.op, left, right) : ir.binaryOp(info.op, left, right);
|
|
557
|
+
}
|
|
529
558
|
}
|
|
530
559
|
return left;
|
|
531
560
|
}
|
|
@@ -590,6 +619,25 @@ exports.parse = function (swig, source, opts, tags, filters) {
|
|
|
590
619
|
tags = tags || {};
|
|
591
620
|
filters = filters || {};
|
|
592
621
|
|
|
622
|
+
// Where parseExpr is working, so that an expression error can name its line
|
|
623
|
+
// and file, and whether it has a catalog to check a filter name against (only
|
|
624
|
+
// an engine instance has). A tag gets a handle on this module whose parseExpr
|
|
625
|
+
// works in the same scope and on this parse's filter catalog.
|
|
626
|
+
var scope = { line: 0, filename: opts.filename, extensions: swig && swig.extensions };
|
|
627
|
+
var exprParser = Object.create(exports);
|
|
628
|
+
exprParser.parseExpr = function (tokens, _filters, posOut) {
|
|
629
|
+
return exports.parseExpr(tokens, filters, posOut, scope);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// The lexer does not know where it is working, so the place of its error is
|
|
633
|
+
// named here. An error from a nested parse has its place already.
|
|
634
|
+
function throwPlaced(err) {
|
|
635
|
+
if (err.unplaced) {
|
|
636
|
+
utils.throwError(err.message.slice(0, -1), scope.line, scope.filename);
|
|
637
|
+
}
|
|
638
|
+
throw err;
|
|
639
|
+
}
|
|
640
|
+
|
|
593
641
|
var varControls = opts.varControls || ['{{', '}}'];
|
|
594
642
|
var tagControls = opts.tagControls || ['{%', '%}'];
|
|
595
643
|
var cmtControls = opts.cmtControls || ['{#', '#}'];
|
|
@@ -642,16 +690,22 @@ exports.parse = function (swig, source, opts, tags, filters) {
|
|
|
642
690
|
* @private
|
|
643
691
|
*/
|
|
644
692
|
function parseVariable(str, _line) {
|
|
645
|
-
|
|
693
|
+
scope.line = _line;
|
|
694
|
+
var lexed;
|
|
695
|
+
try {
|
|
696
|
+
lexed = lexer.read(utils.strip(str));
|
|
697
|
+
} catch (e) {
|
|
698
|
+
throwPlaced(e);
|
|
699
|
+
}
|
|
646
700
|
var sawSafe = false;
|
|
647
701
|
utils.each(lexed, function (tok) {
|
|
648
702
|
if (tok.type === _t.FILTEREMPTY || tok.type === _t.FILTER) {
|
|
649
|
-
if (
|
|
703
|
+
if (hasOwn.call(filters, tok.match) && filters[tok.match].safe === true) {
|
|
650
704
|
sawSafe = true;
|
|
651
705
|
}
|
|
652
706
|
}
|
|
653
707
|
});
|
|
654
|
-
var expr = exports.missingAsEmpty(exports.parseExpr(lexed, filters));
|
|
708
|
+
var expr = exports.missingAsEmpty(exports.parseExpr(lexed, filters, undefined, scope));
|
|
655
709
|
var tail;
|
|
656
710
|
var esc = escapeStack[escapeStack.length - 1];
|
|
657
711
|
if (esc && !sawSafe) {
|
|
@@ -734,7 +788,13 @@ exports.parse = function (swig, source, opts, tags, filters) {
|
|
|
734
788
|
escape: escapeStack[escapeStack.length - 1]
|
|
735
789
|
};
|
|
736
790
|
|
|
737
|
-
|
|
791
|
+
scope.line = _line;
|
|
792
|
+
var ok;
|
|
793
|
+
try {
|
|
794
|
+
ok = tag.parse(tagArgs, _line, exprParser, _t, stack, opts, swig, token);
|
|
795
|
+
} catch (e) {
|
|
796
|
+
throwPlaced(e);
|
|
797
|
+
}
|
|
738
798
|
if (!ok) {
|
|
739
799
|
utils.throwError('Unexpected tag "' + tagName + '"', _line, opts.filename);
|
|
740
800
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhinostone/swig-django",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Django-syntax frontend for the @rhinostone/swig-core template engine. Part of the @rhinostone/swig multi-flavor family.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"template",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=12"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@rhinostone/swig-core": "2.
|
|
25
|
+
"@rhinostone/swig-core": "2.11.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|