miki-template 2.3.2 → 2.3.4
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 +3 -1
- package/package.json +2 -24
- package/src/codegen.js +189 -10
- package/src/filters.js +243 -4
- package/src/libraries.js +1 -1
- package/src/parser.js +69 -1
- package/src/tags/control.js +148 -52
- package/src/tags/extra.js +4 -17
- package/src/tags/i18n.js +78 -17
- package/src/tags/inheritance.js +6 -20
- package/src/tags/util.js +155 -21
package/src/tags/util.js
CHANGED
|
@@ -2,22 +2,7 @@
|
|
|
2
2
|
* Utility template tags: static, url, regroup, spaceless.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
function resolveValue(token, context) {
|
|
7
|
-
if (token === undefined || token === null) return '';
|
|
8
|
-
if (typeof token !== 'string') return token;
|
|
9
|
-
if (token === '') return '';
|
|
10
|
-
if ((token.startsWith('"') && token.endsWith('"')) || (token.startsWith('\'') && token.endsWith('\''))) {
|
|
11
|
-
return token.slice(1, -1);
|
|
12
|
-
}
|
|
13
|
-
if (token === 'true' || token === 'True') return true;
|
|
14
|
-
if (token === 'false' || token === 'False') return false;
|
|
15
|
-
if (token === 'none' || token === 'None' || token === 'null') return null;
|
|
16
|
-
// Numeric literal (integer or float)
|
|
17
|
-
if (/^-?\d+(\.\d+)?$/.test(token)) return Number(token);
|
|
18
|
-
// Otherwise treat as a context variable lookup
|
|
19
|
-
return context.get(token);
|
|
20
|
-
}
|
|
5
|
+
const { evaluateExpression, parseVariableExpression, VariableNode } = require('../parser');
|
|
21
6
|
|
|
22
7
|
class StaticNode {
|
|
23
8
|
constructor(pathExpr) {
|
|
@@ -25,7 +10,7 @@ class StaticNode {
|
|
|
25
10
|
}
|
|
26
11
|
|
|
27
12
|
render(context) {
|
|
28
|
-
const resolvedPath =
|
|
13
|
+
const resolvedPath = evaluateExpression(this.pathExpr, context);
|
|
29
14
|
|
|
30
15
|
// Resolve static prefix
|
|
31
16
|
let prefix = '/static/';
|
|
@@ -55,11 +40,11 @@ class UrlNode {
|
|
|
55
40
|
}
|
|
56
41
|
|
|
57
42
|
render(context) {
|
|
58
|
-
const routeName =
|
|
59
|
-
const resolvedPositional = this.positionalArgs.map(arg =>
|
|
43
|
+
const routeName = evaluateExpression(this.routeNameExpr, context);
|
|
44
|
+
const resolvedPositional = this.positionalArgs.map(arg => evaluateExpression(arg, context));
|
|
60
45
|
const resolvedKwargs = {};
|
|
61
46
|
for (const kw of this.kwargs) {
|
|
62
|
-
resolvedKwargs[kw.name] =
|
|
47
|
+
resolvedKwargs[kw.name] = evaluateExpression(kw.valueExpr, context);
|
|
63
48
|
}
|
|
64
49
|
const hasKwargs = Object.keys(resolvedKwargs).length > 0;
|
|
65
50
|
|
|
@@ -330,6 +315,147 @@ class LoadNode {
|
|
|
330
315
|
}
|
|
331
316
|
}
|
|
332
317
|
|
|
318
|
+
class FilterNode {
|
|
319
|
+
constructor(filterName, filterArg, body) {
|
|
320
|
+
this.filterName = filterName;
|
|
321
|
+
this.filterArg = filterArg;
|
|
322
|
+
this.body = body || [];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
render(context) {
|
|
326
|
+
const raw = this.body.map(n => n.render(context)).join('');
|
|
327
|
+
const { getFilter } = require('../filters');
|
|
328
|
+
const fn = getFilter(this.filterName);
|
|
329
|
+
if (!fn) throw new Error(`Unknown filter: '${this.filterName}'`);
|
|
330
|
+
return fn(raw, this.filterArg, context);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function parseFilter(tagContent, parser) {
|
|
335
|
+
const expr = tagContent.slice(6).trim();
|
|
336
|
+
const nameMatch = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
337
|
+
const filterName = nameMatch ? nameMatch[1] : expr;
|
|
338
|
+
const rest = expr.slice(filterName.length).trim();
|
|
339
|
+
let filterArg = undefined;
|
|
340
|
+
if (rest.startsWith(':')) {
|
|
341
|
+
const argStr = rest.slice(1).trim();
|
|
342
|
+
if ((argStr.startsWith('"') && argStr.endsWith('"')) || (argStr.startsWith('\'') && argStr.endsWith('\''))) {
|
|
343
|
+
filterArg = argStr.slice(1, -1);
|
|
344
|
+
} else if (argStr) {
|
|
345
|
+
const num = Number(argStr);
|
|
346
|
+
filterArg = Number.isNaN(num) ? argStr : num;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const body = parser.parse(['endfilter']);
|
|
350
|
+
const next = parser.peek();
|
|
351
|
+
if (next && next.type === 'block' && next.content.split(/\s+/)[0] === 'endfilter') {
|
|
352
|
+
parser.advance();
|
|
353
|
+
}
|
|
354
|
+
return new FilterNode(filterName, filterArg, body);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
class VerbatimNode {
|
|
358
|
+
constructor(body) {
|
|
359
|
+
this.body = body || [];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
render(_context) {
|
|
363
|
+
return this.body.map(n => {
|
|
364
|
+
if (n.constructor.name === 'TextNode') return n.content;
|
|
365
|
+
return '';
|
|
366
|
+
}).join('');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function parseVerbatim(tagContent, _parser) {
|
|
371
|
+
const body = _parser.parse(['endverbatim']);
|
|
372
|
+
const next = _parser.peek();
|
|
373
|
+
if (next && next.type === 'block' && next.content.split(/\s+/)[0] === 'endverbatim') {
|
|
374
|
+
_parser.advance();
|
|
375
|
+
}
|
|
376
|
+
return new VerbatimNode(body);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
class QueryStringNode {
|
|
380
|
+
constructor(args) {
|
|
381
|
+
this.args = args || [];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
render(context) {
|
|
385
|
+
const params = new URLSearchParams();
|
|
386
|
+
|
|
387
|
+
for (const arg of this.args) {
|
|
388
|
+
const parsed = parseVariableExpression(arg.valueExpr);
|
|
389
|
+
const node = new VariableNode(parsed.varPath, parsed.filters, parsed.isLiteral, parsed.literalValue);
|
|
390
|
+
const value = node.render(context);
|
|
391
|
+
|
|
392
|
+
if (value === null || value === undefined || value === '') {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
params.append(arg.name, String(value));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const qs = params.toString();
|
|
399
|
+
return qs ? '?' + qs : '';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function parseQuerystring(tagContent, _parser) {
|
|
404
|
+
const trimmed = tagContent.replace(/^querystring\s+/, '').trim();
|
|
405
|
+
const args = [];
|
|
406
|
+
|
|
407
|
+
const isQuotedString = (str) => {
|
|
408
|
+
if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith('\'') && str.endsWith('\''))) {
|
|
409
|
+
const quote = str[0];
|
|
410
|
+
const inner = str.slice(1, -1);
|
|
411
|
+
return !inner.includes(quote);
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
if (isQuotedString(trimmed)) {
|
|
417
|
+
const raw = trimmed.slice(1, -1);
|
|
418
|
+
const pairs = raw.split('&');
|
|
419
|
+
for (const pair of pairs) {
|
|
420
|
+
const [name, value] = pair.split('=');
|
|
421
|
+
if (name) {
|
|
422
|
+
args.push({ name, valueExpr: value ? `'${value}'` : '\'\'' });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return new QueryStringNode(args);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const argRegex = /(".*?"|'.*?'|[^\s]+)/g;
|
|
429
|
+
const matches = trimmed.match(argRegex) || [];
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < matches.length; i++) {
|
|
432
|
+
const tok = matches[i];
|
|
433
|
+
const isQuoted = (tok.startsWith('"') && tok.endsWith('"')) ||
|
|
434
|
+
(tok.startsWith('\'') && tok.endsWith('\''));
|
|
435
|
+
if (isQuoted) {
|
|
436
|
+
const name = tok.slice(1, -1);
|
|
437
|
+
if (name.includes('=')) {
|
|
438
|
+
const [n, v] = name.split('=');
|
|
439
|
+
args.push({ name: n, valueExpr: v ? `"${v}"` : '""' });
|
|
440
|
+
} else if (i + 1 < matches.length) {
|
|
441
|
+
const valueExpr = matches[i + 1];
|
|
442
|
+
i++;
|
|
443
|
+
args.push({ name, valueExpr });
|
|
444
|
+
}
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const eq = tok.indexOf('=');
|
|
449
|
+
if (eq > 0) {
|
|
450
|
+
const name = tok.slice(0, eq);
|
|
451
|
+
const valueExpr = tok.slice(eq + 1);
|
|
452
|
+
args.push({ name, valueExpr });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return new QueryStringNode(args);
|
|
457
|
+
}
|
|
458
|
+
|
|
333
459
|
class TemplatetagNode {
|
|
334
460
|
constructor(token) {
|
|
335
461
|
this.token = token;
|
|
@@ -385,9 +511,14 @@ module.exports = {
|
|
|
385
511
|
TemplatetagNode,
|
|
386
512
|
WidthRatioNode,
|
|
387
513
|
DebugNode,
|
|
514
|
+
FilterNode,
|
|
515
|
+
VerbatimNode,
|
|
516
|
+
QueryStringNode,
|
|
388
517
|
parsers: {
|
|
389
518
|
static: parseStatic,
|
|
390
519
|
url: parseUrl,
|
|
520
|
+
urlpk: parseUrl,
|
|
521
|
+
urlslug: parseUrl,
|
|
391
522
|
regroup: parseRegroup,
|
|
392
523
|
spaceless: parseSpaceless,
|
|
393
524
|
csrf_token: parseCsrfToken,
|
|
@@ -395,6 +526,9 @@ module.exports = {
|
|
|
395
526
|
load: parseLoad,
|
|
396
527
|
templatetag: parseTemplatetag,
|
|
397
528
|
widthratio: parseWidthRatio,
|
|
398
|
-
debug: parseDebug
|
|
529
|
+
debug: parseDebug,
|
|
530
|
+
filter: parseFilter,
|
|
531
|
+
verbatim: parseVerbatim,
|
|
532
|
+
querystring: parseQuerystring
|
|
399
533
|
}
|
|
400
534
|
};
|