binja 0.7.1 → 0.8.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 +65 -1
- package/dist/cli.js +10 -10
- package/dist/engines/handlebars/index.d.ts +30 -0
- package/dist/engines/handlebars/lexer.d.ts +61 -0
- package/dist/engines/handlebars/parser.d.ts +37 -0
- package/dist/engines/index.d.ts +53 -0
- package/dist/engines/liquid/index.d.ts +30 -0
- package/dist/engines/liquid/lexer.d.ts +69 -0
- package/dist/engines/liquid/parser.d.ts +47 -0
- package/dist/index.js +55 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
|---------|-----------|------------------|
|
|
28
28
|
| **Runtime Performance** | ✅ 2-4x faster | ❌ |
|
|
29
29
|
| **AOT Compilation** | ✅ 160x faster | ❌ |
|
|
30
|
+
| **Multi-Engine** | ✅ Jinja2, Handlebars, Liquid | ❌ |
|
|
30
31
|
| Django DTL Compatible | ✅ 100% | ❌ Partial |
|
|
31
32
|
| Jinja2 Compatible | ✅ Full | ⚠️ Limited |
|
|
32
33
|
| Template Inheritance | ✅ | ⚠️ |
|
|
33
|
-
|
|
|
34
|
+
| 84 Built-in Filters | ✅ | ❌ |
|
|
34
35
|
| 28 Built-in Tests | ✅ | ❌ |
|
|
35
36
|
| Debug Panel | ✅ | ❌ |
|
|
36
37
|
| CLI Tool | ✅ | ⚠️ |
|
|
@@ -418,6 +419,69 @@ console.log(Object.keys(builtinTests))
|
|
|
418
419
|
|
|
419
420
|
---
|
|
420
421
|
|
|
422
|
+
## Multi-Engine Support
|
|
423
|
+
|
|
424
|
+
Binja supports multiple template engines through a unified API. All engines parse to a common AST and share the same runtime, filters, and optimizations.
|
|
425
|
+
|
|
426
|
+
### Supported Engines
|
|
427
|
+
|
|
428
|
+
| Engine | Syntax | Use Case |
|
|
429
|
+
|--------|--------|----------|
|
|
430
|
+
| **Jinja2/DTL** | `{{ var }}` `{% if %}` | Default, Python/Django compatibility |
|
|
431
|
+
| **Handlebars** | `{{var}}` `{{#if}}` | JavaScript ecosystem, Ember.js |
|
|
432
|
+
| **Liquid** | `{{ var }}` `{% if %}` | Shopify, Jekyll, static sites |
|
|
433
|
+
|
|
434
|
+
### Usage
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// Direct engine imports
|
|
438
|
+
import * as handlebars from 'binja/engines/handlebars'
|
|
439
|
+
import * as liquid from 'binja/engines/liquid'
|
|
440
|
+
|
|
441
|
+
// Handlebars
|
|
442
|
+
await handlebars.render('Hello {{name}}!', { name: 'World' })
|
|
443
|
+
await handlebars.render('{{#each items}}{{this}}{{/each}}', { items: ['a', 'b'] })
|
|
444
|
+
await handlebars.render('{{{html}}}', { html: '<b>unescaped</b>' })
|
|
445
|
+
|
|
446
|
+
// Liquid (Shopify)
|
|
447
|
+
await liquid.render('Hello {{ name }}!', { name: 'World' })
|
|
448
|
+
await liquid.render('{% for item in items %}{{ item }}{% endfor %}', { items: ['a', 'b'] })
|
|
449
|
+
await liquid.render('{% assign x = "value" %}{{ x }}', {})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### MultiEngine API
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { MultiEngine } from 'binja/engines'
|
|
456
|
+
|
|
457
|
+
const engine = new MultiEngine()
|
|
458
|
+
|
|
459
|
+
// Render with any engine
|
|
460
|
+
await engine.render('Hello {{name}}!', { name: 'World' }, 'handlebars')
|
|
461
|
+
await engine.render('Hello {{ name }}!', { name: 'World' }, 'liquid')
|
|
462
|
+
await engine.render('Hello {{ name }}!', { name: 'World' }, 'jinja2')
|
|
463
|
+
|
|
464
|
+
// Auto-detect from file extension
|
|
465
|
+
import { detectEngine } from 'binja/engines'
|
|
466
|
+
const eng = detectEngine('template.hbs') // Returns Handlebars engine
|
|
467
|
+
const eng2 = detectEngine('page.liquid') // Returns Liquid engine
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Engine Feature Matrix
|
|
471
|
+
|
|
472
|
+
| Feature | Jinja2 | Handlebars | Liquid |
|
|
473
|
+
|---------|--------|------------|--------|
|
|
474
|
+
| Variables | `{{ x }}` | `{{x}}` | `{{ x }}` |
|
|
475
|
+
| Conditionals | `{% if %}` | `{{#if}}` | `{% if %}` |
|
|
476
|
+
| Loops | `{% for %}` | `{{#each}}` | `{% for %}` |
|
|
477
|
+
| Filters | `{{ x\|filter }}` | `{{ x }}` | `{{ x \| filter }}` |
|
|
478
|
+
| Raw output | `{% raw %}` | - | `{% raw %}` |
|
|
479
|
+
| Comments | `{# #}` | `{{! }}` | `{% comment %}` |
|
|
480
|
+
| Assignment | `{% set %}` | - | `{% assign %}` |
|
|
481
|
+
| Unescaped | `{{ x\|safe }}` | `{{{x}}}` | - |
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
421
485
|
## Django Compatibility
|
|
422
486
|
|
|
423
487
|
binja is designed to be a drop-in replacement for Django templates:
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import*as X from"fs";import*as Y from"path";var P={and:"AND",or:"OR",not:"NOT",true:"NAME",false:"NAME",True:"NAME",False:"NAME",None:"NAME",none:"NAME",is:"NAME",in:"NAME"};var
|
|
3
|
+
import*as X from"fs";import*as Y from"path";var P={and:"AND",or:"OR",not:"NOT",true:"NAME",false:"NAME",True:"NAME",False:"NAME",None:"NAME",none:"NAME",is:"NAME",in:"NAME"};var x={red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",gray:"\x1B[90m",bold:"\x1B[1m",dim:"\x1B[2m",reset:"\x1B[0m"},p=process.stdout?.isTTY!==!1;function q(A,B){return p?`${x[A]}${B}${x.reset}`:B}class H extends Error{line;column;source;templateName;suggestion;constructor(A,B){let R=v("TemplateSyntaxError",A,B);super(R);this.name="TemplateSyntaxError",this.line=B.line,this.column=B.column,this.source=B.source,this.templateName=B.templateName,this.suggestion=B.suggestion}}function v(A,B,R){let K=[],O=R.templateName?`${R.templateName}:${R.line}:${R.column}`:`line ${R.line}, column ${R.column}`;if(K.push(`${q("red",q("bold",A))}: ${B} at ${q("cyan",O)}`),R.source)K.push(""),K.push(d(R.source,R.line,R.column));if(R.suggestion)K.push(""),K.push(`${q("yellow","Did you mean")}: ${q("cyan",R.suggestion)}?`);if(R.availableOptions&&R.availableOptions.length>0){K.push("");let Q=R.availableOptions.slice(0,8),U=R.availableOptions.length>8?` ${q("gray",`... and ${R.availableOptions.length-8} more`)}`:"";K.push(`${q("gray","Available")}: ${Q.join(", ")}${U}`)}return K.join(`
|
|
4
4
|
`)}function d(A,B,R){let K=A.split(`
|
|
5
|
-
`),O=[],Q=Math.max(1,B-2),U=Math.min(K.length,B+1),M=String(U).length;for(let Z=Q;Z<=U;Z++){let $=K[Z-1]||"",G=String(Z).padStart(M," ");if(Z===B){O.push(`${q("red"," \u2192")} ${q("gray",G)} ${q("dim","\u2502")} ${$}`);let
|
|
6
|
-
`)}class D{state;variableStart;variableEnd;blockStart;blockEnd;commentStart;commentEnd;constructor(A,B={}){this.state={source:A,pos:0,line:1,column:1,tokens:[]},this.variableStart=B.variableStart??"{{",this.variableEnd=B.variableEnd??"}}",this.blockStart=B.blockStart??"{%",this.blockEnd=B.blockEnd??"%}",this.commentStart=B.commentStart??"{#",this.commentEnd=B.commentEnd??"#}"}tokenize(){while(!this.isAtEnd())this.scanToken();return this.addToken("EOF",""),this.state.tokens}scanToken(){if(this.match(this.variableStart)){this.addToken("VARIABLE_START",this.variableStart),this.scanExpression(this.variableEnd,"VARIABLE_END");return}if(this.match(this.blockStart)){let A=this.peek()==="-";if(A)this.advance();let B=this.state.pos;if(this.skipWhitespace(),this.checkWord("raw")||this.checkWord("verbatim")){let R=this.checkWord("raw")?"raw":"verbatim";this.scanRawBlock(R,A);return}this.state.pos=B,this.addToken("BLOCK_START",this.blockStart+(A?"-":"")),this.scanExpression(this.blockEnd,"BLOCK_END");return}if(this.match(this.commentStart)){this.scanComment();return}this.scanText()}checkWord(A){let B=this.state.pos;for(let K=0;K<A.length;K++)if(this.state.source[B+K]?.toLowerCase()!==A[K])return!1;let R=this.state.source[B+A.length];return!R||!this.isAlphaNumeric(R)}scanRawBlock(A,B){let R=this.state.line,K=this.state.column;for(let U=0;U<A.length;U++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new
|
|
7
|
-
`)this.state.line++,this.state.column=0;this.advance()}throw new
|
|
8
|
-
`)this.state.line++,this.state.column=0;this.advance()}if(this.state.pos>A){let K=this.state.source.slice(A,this.state.pos);this.state.tokens.push({type:"TEXT",value:K,line:B,column:R})}}scanExpression(A,B){this.skipWhitespace();while(!this.isAtEnd()){if(this.skipWhitespace(),this.peek()==="-"&&this.check(A,1))this.advance();if(this.match(A)){this.addToken(B,A);return}this.scanExpressionToken()}throw new
|
|
9
|
-
`)this.state.line++,this.state.column=0;this.advance()}if(this.isAtEnd())throw new
|
|
5
|
+
`),O=[],Q=Math.max(1,B-2),U=Math.min(K.length,B+1),M=String(U).length;for(let Z=Q;Z<=U;Z++){let $=K[Z-1]||"",G=String(Z).padStart(M," ");if(Z===B){O.push(`${q("red"," \u2192")} ${q("gray",G)} ${q("dim","\u2502")} ${$}`);let z=" ".repeat(M+4+Math.max(0,R-1)),E=q("red","^");O.push(`${z}${E}`)}else O.push(` ${q("gray",G)} ${q("dim","\u2502")} ${q("gray",$)}`)}return O.join(`
|
|
6
|
+
`)}class D{state;variableStart;variableEnd;blockStart;blockEnd;commentStart;commentEnd;constructor(A,B={}){this.state={source:A,pos:0,line:1,column:1,tokens:[]},this.variableStart=B.variableStart??"{{",this.variableEnd=B.variableEnd??"}}",this.blockStart=B.blockStart??"{%",this.blockEnd=B.blockEnd??"%}",this.commentStart=B.commentStart??"{#",this.commentEnd=B.commentEnd??"#}"}tokenize(){while(!this.isAtEnd())this.scanToken();return this.addToken("EOF",""),this.state.tokens}scanToken(){if(this.match(this.variableStart)){this.addToken("VARIABLE_START",this.variableStart),this.scanExpression(this.variableEnd,"VARIABLE_END");return}if(this.match(this.blockStart)){let A=this.peek()==="-";if(A)this.advance();let B=this.state.pos;if(this.skipWhitespace(),this.checkWord("raw")||this.checkWord("verbatim")){let R=this.checkWord("raw")?"raw":"verbatim";this.scanRawBlock(R,A);return}this.state.pos=B,this.addToken("BLOCK_START",this.blockStart+(A?"-":"")),this.scanExpression(this.blockEnd,"BLOCK_END");return}if(this.match(this.commentStart)){this.scanComment();return}this.scanText()}checkWord(A){let B=this.state.pos;for(let K=0;K<A.length;K++)if(this.state.source[B+K]?.toLowerCase()!==A[K])return!1;let R=this.state.source[B+A.length];return!R||!this.isAlphaNumeric(R)}scanRawBlock(A,B){let R=this.state.line,K=this.state.column;for(let U=0;U<A.length;U++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new H(`Expected '${this.blockEnd}' after '${A}'`,{line:this.state.line,column:this.state.column,source:this.state.source});let O=`end${A}`,Q=this.state.pos;while(!this.isAtEnd()){if(this.check(this.blockStart)){let U=this.state.pos,M=this.state.line,Z=this.state.column;if(this.match(this.blockStart),this.peek()==="-")this.advance();if(this.skipWhitespace(),this.checkWord(O)){let $=this.state.source.slice(Q,U);if($.length>0)this.state.tokens.push({type:"TEXT",value:$,line:R,column:K});for(let G=0;G<O.length;G++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new H(`Expected '${this.blockEnd}' after '${O}'`,{line:this.state.line,column:this.state.column,source:this.state.source});return}this.state.pos=U,this.state.line=M,this.state.column=Z}if(this.peek()===`
|
|
7
|
+
`)this.state.line++,this.state.column=0;this.advance()}throw new H(`Unclosed '${A}' block`,{line:R,column:K,source:this.state.source,suggestion:`Add {% end${A} %} to close the block`})}scanText(){let A=this.state.pos,B=this.state.line,R=this.state.column;while(!this.isAtEnd()){if(this.check(this.variableStart)||this.check(this.blockStart)||this.check(this.commentStart))break;if(this.peek()===`
|
|
8
|
+
`)this.state.line++,this.state.column=0;this.advance()}if(this.state.pos>A){let K=this.state.source.slice(A,this.state.pos);this.state.tokens.push({type:"TEXT",value:K,line:B,column:R})}}scanExpression(A,B){this.skipWhitespace();while(!this.isAtEnd()){if(this.skipWhitespace(),this.peek()==="-"&&this.check(A,1))this.advance();if(this.match(A)){this.addToken(B,A);return}this.scanExpressionToken()}throw new H("Unclosed template tag",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing delimiter '${A}'`})}scanExpressionToken(){if(this.skipWhitespace(),this.isAtEnd())return;let A=this.peek();if(A==='"'||A==="'"){this.scanString(A);return}if(this.isDigit(A)){this.scanNumber();return}if(this.isAlpha(A)||A==="_"){this.scanIdentifier();return}this.scanOperator()}scanString(A){this.advance();let B=this.state.pos;while(!this.isAtEnd()&&this.peek()!==A){if(this.peek()==="\\"&&this.peekNext()===A)this.advance();if(this.peek()===`
|
|
9
|
+
`)this.state.line++,this.state.column=0;this.advance()}if(this.isAtEnd())throw new H("Unterminated string literal",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing quote '${A}'`});let R=this.state.source.slice(B,this.state.pos);this.advance(),this.addToken("STRING",R)}scanNumber(){let A=this.state.pos;while(this.isDigit(this.peek()))this.advance();if(this.peek()==="."&&this.isDigit(this.peekNext())){this.advance();while(this.isDigit(this.peek()))this.advance()}let B=this.state.source.slice(A,this.state.pos);this.addToken("NUMBER",B)}scanIdentifier(){let A=this.state.pos;while(this.isAlphaNumeric(this.peek())||this.peek()==="_")this.advance();let B=this.state.source.slice(A,this.state.pos),R=P[B]??"NAME";this.addToken(R,B)}scanOperator(){let A=this.advance();switch(A){case".":this.addToken("DOT",A);break;case",":this.addToken("COMMA",A);break;case":":this.addToken("COLON",A);break;case"|":this.addToken("PIPE",A);break;case"(":this.addToken("LPAREN",A);break;case")":this.addToken("RPAREN",A);break;case"[":this.addToken("LBRACKET",A);break;case"]":this.addToken("RBRACKET",A);break;case"{":this.addToken("LBRACE",A);break;case"}":this.addToken("RBRACE",A);break;case"+":this.addToken("ADD",A);break;case"-":this.addToken("SUB",A);break;case"*":this.addToken("MUL",A);break;case"/":this.addToken("DIV",A);break;case"%":this.addToken("MOD",A);break;case"~":this.addToken("TILDE",A);break;case"=":if(this.match("="))this.addToken("EQ","==");else this.addToken("ASSIGN","=");break;case"!":if(this.match("="))this.addToken("NE","!=");else throw new H("Unexpected character '!'",{line:this.state.line,column:this.state.column-1,source:this.state.source,suggestion:"Use '!=' for not-equal comparison or 'not' for negation"});break;case"<":if(this.match("="))this.addToken("LE","<=");else this.addToken("LT","<");break;case">":if(this.match("="))this.addToken("GE",">=");else this.addToken("GT",">");break;default:if(!this.isWhitespace(A))throw new H(`Unexpected character '${A}'`,{line:this.state.line,column:this.state.column-1,source:this.state.source})}}scanComment(){while(!this.isAtEnd()&&!this.check(this.commentEnd)){if(this.peek()===`
|
|
10
10
|
`)this.state.line++,this.state.column=0;this.advance()}if(!this.isAtEnd())this.match(this.commentEnd)}isAtEnd(){return this.state.pos>=this.state.source.length}peek(){if(this.isAtEnd())return"\x00";return this.state.source[this.state.pos]}peekNext(){if(this.state.pos+1>=this.state.source.length)return"\x00";return this.state.source[this.state.pos+1]}advance(){let A=this.state.source[this.state.pos];return this.state.pos++,this.state.column++,A}match(A,B=0){let R=this.state.source,K=this.state.pos+B,O=A.length;if(K+O>R.length)return!1;for(let Q=0;Q<O;Q++)if(R[K+Q]!==A[Q])return!1;if(B===0)this.state.pos+=O,this.state.column+=O;return!0}check(A,B=0){let R=this.state.source,K=this.state.pos+B,O=A.length;if(K+O>R.length)return!1;for(let Q=0;Q<O;Q++)if(R[K+Q]!==A[Q])return!1;return!0}skipWhitespace(){while(!this.isAtEnd()&&this.isWhitespace(this.peek())){if(this.peek()===`
|
|
11
11
|
`)this.state.line++,this.state.column=0;this.advance()}}isWhitespace(A){return A===" "||A==="\t"||A===`
|
|
12
|
-
`||A==="\r"}isDigit(A){let B=A.charCodeAt(0);return B>=48&&B<=57}isAlpha(A){let B=A.charCodeAt(0);return B>=97&&B<=122||B>=65&&B<=90}isAlphaNumeric(A){let B=A.charCodeAt(0);return B>=48&&B<=57||B>=97&&B<=122||B>=65&&B<=90}addToken(A,B){this.state.tokens.push({type:A,value:B,line:this.state.line,column:this.state.column-B.length})}}class S{tokens;current=0;source;constructor(A,B){this.tokens=A,this.source=B}parse(){let A=[];while(!this.isAtEnd()){let B=this.parseStatement();if(B)A.push(B)}return{type:"Template",body:A,line:1,column:1}}parseStatement(){switch(this.peek().type){case"TEXT":return this.parseText();case"VARIABLE_START":return this.parseOutput();case"BLOCK_START":return this.parseBlock();case"EOF":return null;default:return this.advance(),null}}parseText(){let A=this.advance();return{type:"Text",value:A.value,line:A.line,column:A.column}}parseOutput(){let A=this.advance(),B=this.parseExpression();return this.expect("VARIABLE_END"),{type:"Output",expression:B,line:A.line,column:A.column}}parseBlock(){let A=this.advance(),B=this.expect("NAME");switch(B.value){case"if":return this.parseIf(A);case"for":return this.parseFor(A);case"block":return this.parseBlockTag(A);case"extends":return this.parseExtends(A);case"include":return this.parseInclude(A);case"set":return this.parseSet(A);case"with":return this.parseWith(A);case"load":return this.parseLoad(A);case"url":return this.parseUrl(A);case"static":return this.parseStatic(A);case"now":return this.parseNow(A);case"comment":return this.parseComment(A);case"spaceless":case"autoescape":case"verbatim":return this.parseSimpleBlock(A,B.value);case"cycle":return this.parseCycle(A);case"firstof":return this.parseFirstof(A);case"ifchanged":return this.parseIfchanged(A);case"regroup":return this.parseRegroup(A);case"widthratio":return this.parseWidthratio(A);case"lorem":return this.parseLorem(A);case"csrf_token":return this.parseCsrfToken(A);case"debug":return this.parseDebug(A);case"templatetag":return this.parseTemplatetag(A);case"ifequal":return this.parseIfequal(A,!1);case"ifnotequal":return this.parseIfequal(A,!0);default:return this.skipToBlockEnd(),null}}parseIf(A){let B=this.parseExpression();this.expect("BLOCK_END");let R=[],K=[],O=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)R.push(Q)}while(this.checkBlockTag("elif")){this.advance(),this.advance();let Q=this.parseExpression();this.expect("BLOCK_END");let U=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let M=this.parseStatement();if(M)U.push(M)}K.push({test:Q,body:U})}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)O.push(Q)}}return this.expectBlockTag("endif"),{type:"If",test:B,body:R,elifs:K,else_:O,line:A.line,column:A.column}}parseFor(A){let B,R=this.expect("NAME").value;if(this.check("COMMA")){let Z=[R];while(this.match("COMMA"))Z.push(this.expect("NAME").value);B=Z}else B=R;let K=this.expect("NAME");if(K.value!=="in")throw this.error(`Expected 'in' in for loop, got '${K.value}'`);let O=this.parseExpression(),Q=this.check("NAME")&&this.peek().value==="recursive";if(Q)this.advance();this.expect("BLOCK_END");let U=[],M=[];while(!this.isAtEnd()){if(this.checkBlockTag("empty")||this.checkBlockTag("else")||this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)U.push(Z)}if(this.checkBlockTag("empty")||this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)M.push(Z)}}return this.expectBlockTag("endfor"),{type:"For",target:B,iter:O,body:U,else_:M,recursive:Q,line:A.line,column:A.column}}parseBlockTag(A){let B=this.expect("NAME").value,R=this.check("NAME")&&this.peek().value==="scoped";if(R)this.advance();this.expect("BLOCK_END");let K=[];while(!this.isAtEnd()){if(this.checkBlockTag("endblock"))break;let O=this.parseStatement();if(O)K.push(O)}if(this.advance(),this.advance(),this.check("NAME"))this.advance();return this.expect("BLOCK_END"),{type:"Block",name:B,body:K,scoped:R,line:A.line,column:A.column}}parseExtends(A){let B=this.parseExpression();return this.expect("BLOCK_END"),{type:"Extends",template:B,line:A.line,column:A.column}}parseInclude(A){let B=this.parseExpression(),R=null,K=!1,O=!1;while(this.check("NAME")){let Q=this.peek().value;if(Q==="ignore"&&this.peekNext()?.value==="missing")this.advance(),this.advance(),O=!0;else if(Q==="with")this.advance(),R=this.parseKeywordArgs();else if(Q==="only")this.advance(),K=!0;else if(Q==="without"){if(this.advance(),this.check("NAME")&&this.peek().value==="context")this.advance(),K=!0}else break}return this.expect("BLOCK_END"),{type:"Include",template:B,context:R,only:K,ignoreMissing:O,line:A.line,column:A.column}}parseSet(A){let B=this.expect("NAME").value;this.expect("ASSIGN");let R=this.parseExpression();return this.expect("BLOCK_END"),{type:"Set",target:B,value:R,line:A.line,column:A.column}}parseWith(A){let B=[];do{let K=this.expect("NAME").value;this.expect("ASSIGN");let O=this.parseExpression();B.push({target:K,value:O})}while(this.match("COMMA")||this.check("NAME")&&this.peekNext()?.type==="ASSIGN");this.expect("BLOCK_END");let R=[];while(!this.isAtEnd()){if(this.checkBlockTag("endwith"))break;let K=this.parseStatement();if(K)R.push(K)}return this.expectBlockTag("endwith"),{type:"With",assignments:B,body:R,line:A.line,column:A.column}}parseLoad(A){let B=[];while(this.check("NAME"))B.push(this.advance().value);return this.expect("BLOCK_END"),{type:"Load",names:B,line:A.line,column:A.column}}parseUrl(A){let B=this.parseExpression(),R=[],K={},O=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),O=this.expect("NAME").value;break}if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let Q=this.advance().value;this.advance(),K[Q]=this.parseExpression()}else R.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Url",name:B,args:R,kwargs:K,asVar:O,line:A.line,column:A.column}}parseStatic(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Static",path:B,asVar:R,line:A.line,column:A.column}}parseNow(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Now",format:B,asVar:R,line:A.line,column:A.column}}parseComment(A){this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endcomment"))break;this.advance()}return this.expectBlockTag("endcomment"),null}parseSimpleBlock(A,B){this.skipToBlockEnd();let R=`end${B}`;while(!this.isAtEnd()){if(this.checkBlockTag(R))break;this.advance()}if(this.checkBlockTag(R))this.advance(),this.advance(),this.expect("BLOCK_END");return null}parseCycle(A){let B=[],R=null,K=!1;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){if(this.advance(),R=this.expect("NAME").value,this.check("NAME")&&this.peek().value==="silent")this.advance(),K=!0;break}B.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Cycle",values:B,asVar:R,silent:K,line:A.line,column:A.column}}parseFirstof(A){let B=[],R=null,K=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),K=this.expect("NAME").value;break}B.push(this.parseExpression())}if(B.length>0){let O=B[B.length-1];if(O.type==="Literal"&&typeof O.value==="string")R=B.pop()}return this.expect("BLOCK_END"),{type:"Firstof",values:B,fallback:R,asVar:K,line:A.line,column:A.column}}parseIfchanged(A){let B=[];while(!this.check("BLOCK_END"))B.push(this.parseExpression());this.expect("BLOCK_END");let R=[],K=[];while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)R.push(O)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)K.push(O)}}return this.expectBlockTag("endifchanged"),{type:"Ifchanged",values:B,body:R,else_:K,line:A.line,column:A.column}}parseRegroup(A){let B=this.parseExpression();this.expectName("by");let R=this.expect("NAME").value;this.expectName("as");let K=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Regroup",target:B,key:R,asVar:K,line:A.line,column:A.column}}parseWidthratio(A){let B=this.parseExpression(),R=this.parseExpression(),K=this.parseExpression(),O=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Widthratio",value:B,maxValue:R,maxWidth:K,asVar:O,line:A.line,column:A.column}}parseLorem(A){let B=null,R="p",K=!1;if(this.check("NUMBER"))B={type:"Literal",value:parseInt(this.advance().value,10),line:A.line,column:A.column};if(this.check("NAME")){let O=this.peek().value.toLowerCase();if(O==="w"||O==="p"||O==="b")R=O,this.advance()}if(this.check("NAME")&&this.peek().value==="random")K=!0,this.advance();return this.expect("BLOCK_END"),{type:"Lorem",count:B,method:R,random:K,line:A.line,column:A.column}}parseCsrfToken(A){return this.expect("BLOCK_END"),{type:"CsrfToken",line:A.line,column:A.column}}parseDebug(A){return this.expect("BLOCK_END"),{type:"Debug",line:A.line,column:A.column}}parseTemplatetag(A){let B=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Templatetag",tagType:B,line:A.line,column:A.column}}parseIfequal(A,B){let R=this.parseExpression(),K=this.parseExpression();this.expect("BLOCK_END");let O={type:"Compare",left:R,ops:[{operator:B?"!=":"==",right:K}],line:A.line,column:A.column},Q=[],U=[],M=B?"endifnotequal":"endifequal";while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)Q.push(Z)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)U.push(Z)}}return this.expectBlockTag(M),{type:"If",test:O,body:Q,elifs:[],else_:U,line:A.line,column:A.column}}parseExpression(){return this.parseConditional()}parseConditional(){let A=this.parseOr();if(this.check("NAME")&&this.peek().value==="if"){this.advance();let B=this.parseOr();this.expectName("else");let R=this.parseConditional();A={type:"Conditional",test:B,trueExpr:A,falseExpr:R,line:A.line,column:A.column}}return A}parseOr(){let A=this.parseAnd();while(this.check("OR")||this.check("NAME")&&this.peek().value==="or"){this.advance();let B=this.parseAnd();A={type:"BinaryOp",operator:"or",left:A,right:B,line:A.line,column:A.column}}return A}parseAnd(){let A=this.parseNot();while(this.check("AND")||this.check("NAME")&&this.peek().value==="and"){this.advance();let B=this.parseNot();A={type:"BinaryOp",operator:"and",left:A,right:B,line:A.line,column:A.column}}return A}parseNot(){if(this.check("NOT")||this.check("NAME")&&this.peek().value==="not"){let A=this.advance();return{type:"UnaryOp",operator:"not",operand:this.parseNot(),line:A.line,column:A.column}}return this.parseCompare()}parseCompare(){let A=this.parseAddSub(),B=[];while(!0){let R=null;if(this.match("EQ"))R="==";else if(this.match("NE"))R="!=";else if(this.match("LT"))R="<";else if(this.match("GT"))R=">";else if(this.match("LE"))R="<=";else if(this.match("GE"))R=">=";else if(this.check("NAME")){let O=this.peek().value;if(O==="in")this.advance(),R="in";else if(O==="not"&&this.peekNext()?.value==="in")this.advance(),this.advance(),R="not in";else if(O==="is"){this.advance();let Q=this.check("NOT");if(Q)this.advance();let M=this.expect("NAME").value,Z=[];if(this.match("LPAREN")){while(!this.check("RPAREN"))if(Z.push(this.parseExpression()),!this.check("RPAREN"))this.expect("COMMA");this.expect("RPAREN")}A={type:"TestExpr",node:A,test:M,args:Z,negated:Q,line:A.line,column:A.column};continue}}if(!R)break;let K=this.parseAddSub();B.push({operator:R,right:K})}if(B.length===0)return A;return{type:"Compare",left:A,ops:B,line:A.line,column:A.column}}parseAddSub(){let A=this.parseMulDiv();while(this.check("ADD")||this.check("SUB")||this.check("TILDE")){let B=this.advance(),R=this.parseMulDiv();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseMulDiv(){let A=this.parseUnary();while(this.check("MUL")||this.check("DIV")||this.check("MOD")){let B=this.advance(),R=this.parseUnary();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseUnary(){if(this.check("SUB")||this.check("ADD")){let A=this.advance(),B=this.parseUnary();return{type:"UnaryOp",operator:A.value,operand:B,line:A.line,column:A.column}}return this.parseFilter()}parseFilter(){let A=this.parsePostfix();while(this.match("PIPE")){let B=this.expect("NAME").value,R=[],K={};if(this.match("COLON"))if(this.check("SUB")||this.check("ADD")){let O=this.advance(),Q=this.parsePostfix();R.push({type:"UnaryOp",operator:O.value,operand:Q,line:O.line,column:O.column})}else R.push(this.parsePostfix());else if(this.match("LPAREN")){while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let O=this.advance().value;this.advance(),K[O]=this.parseExpression()}else R.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN")}A={type:"FilterExpr",node:A,filter:B,args:R,kwargs:K,line:A.line,column:A.column}}return A}parsePostfix(){let A=this.parsePrimary();while(!0)if(this.match("DOT")){let B;if(this.check("NUMBER"))B=this.advance().value;else B=this.expect("NAME").value;A={type:"GetAttr",object:A,attribute:B,line:A.line,column:A.column}}else if(this.match("LBRACKET")){let B=this.parseExpression();this.expect("RBRACKET"),A={type:"GetItem",object:A,index:B,line:A.line,column:A.column}}else if(this.match("LPAREN")){let B=[],R={};while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let K=this.advance().value;this.advance(),R[K]=this.parseExpression()}else B.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN"),A={type:"FunctionCall",callee:A,args:B,kwargs:R,line:A.line,column:A.column}}else break;return A}parsePrimary(){let A=this.peek();if(this.match("STRING"))return{type:"Literal",value:A.value,line:A.line,column:A.column};if(this.match("NUMBER"))return{type:"Literal",value:A.value.includes(".")?parseFloat(A.value):parseInt(A.value,10),line:A.line,column:A.column};if(this.check("NAME")){let B=this.advance().value;if(B==="true"||B==="True")return{type:"Literal",value:!0,line:A.line,column:A.column};if(B==="false"||B==="False")return{type:"Literal",value:!1,line:A.line,column:A.column};if(B==="none"||B==="None"||B==="null")return{type:"Literal",value:null,line:A.line,column:A.column};return{type:"Name",name:B,line:A.line,column:A.column}}if(this.match("LPAREN")){let B=this.parseExpression();return this.expect("RPAREN"),B}if(this.match("LBRACKET")){let B=[];while(!this.check("RBRACKET"))if(B.push(this.parseExpression()),!this.check("RBRACKET"))this.expect("COMMA");return this.expect("RBRACKET"),{type:"Array",elements:B,line:A.line,column:A.column}}if(this.match("LBRACE")){let B=[];while(!this.check("RBRACE")){let R=this.parseExpression();this.expect("COLON");let K=this.parseExpression();if(B.push({key:R,value:K}),!this.check("RBRACE"))this.expect("COMMA")}return this.expect("RBRACE"),{type:"Object",pairs:B,line:A.line,column:A.column}}throw this.error(`Unexpected token: ${A.type} (${A.value})`)}parseKeywordArgs(){let A={};while(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let B=this.advance().value;this.advance(),A[B]=this.parseExpression()}return A}checkBlockTag(A){if(this.peek().type!=="BLOCK_START")return!1;let B=this.current;this.advance();let R=this.check("NAME")&&this.peek().value===A;return this.current=B,R}expectBlockTag(A){this.advance();let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`);this.expect("BLOCK_END")}expectName(A){let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`)}skipToBlockEnd(){while(!this.isAtEnd()&&!this.check("BLOCK_END"))this.advance();if(this.check("BLOCK_END"))this.advance()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}peekNext(){if(this.current+1>=this.tokens.length)return null;return this.tokens[this.current+1]}advance(){if(!this.isAtEnd())this.current++;return this.tokens[this.current-1]}check(A){if(this.isAtEnd())return!1;return this.peek().type===A}match(A){if(this.check(A))return this.advance(),!0;return!1}expect(A){if(this.check(A))return this.advance();let B=this.peek();throw this.error(`Expected ${A}, got ${B.type} (${B.value})`)}error(A){let B=this.peek();return new F(A,{line:B.line,column:B.column,source:this.source})}}function f(A,B={}){return new k(B).compile(A)}class k{options;indent=0;varCounter=0;loopStack=[];localVars=[];constructor(A={}){this.options={functionName:A.functionName??"render",inlineHelpers:A.inlineHelpers??!0,minify:A.minify??!1,autoescape:A.autoescape??!0}}pushScope(){this.localVars.push(new Set)}popScope(){this.localVars.pop()}addLocalVar(A){if(this.localVars.length>0)this.localVars[this.localVars.length-1].add(A)}isLocalVar(A){for(let B=this.localVars.length-1;B>=0;B--)if(this.localVars[B].has(A))return!0;return!1}compile(A){let B=this.compileNodes(A.body),R=this.options.minify?"":`
|
|
12
|
+
`||A==="\r"}isDigit(A){let B=A.charCodeAt(0);return B>=48&&B<=57}isAlpha(A){let B=A.charCodeAt(0);return B>=97&&B<=122||B>=65&&B<=90}isAlphaNumeric(A){let B=A.charCodeAt(0);return B>=48&&B<=57||B>=97&&B<=122||B>=65&&B<=90}addToken(A,B){this.state.tokens.push({type:A,value:B,line:this.state.line,column:this.state.column-B.length})}}class S{tokens;current=0;source;constructor(A,B){this.tokens=A,this.source=B}parse(){let A=[];while(!this.isAtEnd()){let B=this.parseStatement();if(B)A.push(B)}return{type:"Template",body:A,line:1,column:1}}parseStatement(){switch(this.peek().type){case"TEXT":return this.parseText();case"VARIABLE_START":return this.parseOutput();case"BLOCK_START":return this.parseBlock();case"EOF":return null;default:return this.advance(),null}}parseText(){let A=this.advance();return{type:"Text",value:A.value,line:A.line,column:A.column}}parseOutput(){let A=this.advance(),B=this.parseExpression();return this.expect("VARIABLE_END"),{type:"Output",expression:B,line:A.line,column:A.column}}parseBlock(){let A=this.advance(),B=this.expect("NAME");switch(B.value){case"if":return this.parseIf(A);case"for":return this.parseFor(A);case"block":return this.parseBlockTag(A);case"extends":return this.parseExtends(A);case"include":return this.parseInclude(A);case"set":return this.parseSet(A);case"with":return this.parseWith(A);case"load":return this.parseLoad(A);case"url":return this.parseUrl(A);case"static":return this.parseStatic(A);case"now":return this.parseNow(A);case"comment":return this.parseComment(A);case"spaceless":case"autoescape":case"verbatim":return this.parseSimpleBlock(A,B.value);case"cycle":return this.parseCycle(A);case"firstof":return this.parseFirstof(A);case"ifchanged":return this.parseIfchanged(A);case"regroup":return this.parseRegroup(A);case"widthratio":return this.parseWidthratio(A);case"lorem":return this.parseLorem(A);case"csrf_token":return this.parseCsrfToken(A);case"debug":return this.parseDebug(A);case"templatetag":return this.parseTemplatetag(A);case"ifequal":return this.parseIfequal(A,!1);case"ifnotequal":return this.parseIfequal(A,!0);default:return this.skipToBlockEnd(),null}}parseIf(A){let B=this.parseExpression();this.expect("BLOCK_END");let R=[],K=[],O=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)R.push(Q)}while(this.checkBlockTag("elif")){this.advance(),this.advance();let Q=this.parseExpression();this.expect("BLOCK_END");let U=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let M=this.parseStatement();if(M)U.push(M)}K.push({test:Q,body:U})}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)O.push(Q)}}return this.expectBlockTag("endif"),{type:"If",test:B,body:R,elifs:K,else_:O,line:A.line,column:A.column}}parseFor(A){let B,R=this.expect("NAME").value;if(this.check("COMMA")){let Z=[R];while(this.match("COMMA"))Z.push(this.expect("NAME").value);B=Z}else B=R;let K=this.expect("NAME");if(K.value!=="in")throw this.error(`Expected 'in' in for loop, got '${K.value}'`);let O=this.parseExpression(),Q=this.check("NAME")&&this.peek().value==="recursive";if(Q)this.advance();this.expect("BLOCK_END");let U=[],M=[];while(!this.isAtEnd()){if(this.checkBlockTag("empty")||this.checkBlockTag("else")||this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)U.push(Z)}if(this.checkBlockTag("empty")||this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)M.push(Z)}}return this.expectBlockTag("endfor"),{type:"For",target:B,iter:O,body:U,else_:M,recursive:Q,line:A.line,column:A.column}}parseBlockTag(A){let B=this.expect("NAME").value,R=this.check("NAME")&&this.peek().value==="scoped";if(R)this.advance();this.expect("BLOCK_END");let K=[];while(!this.isAtEnd()){if(this.checkBlockTag("endblock"))break;let O=this.parseStatement();if(O)K.push(O)}if(this.advance(),this.advance(),this.check("NAME"))this.advance();return this.expect("BLOCK_END"),{type:"Block",name:B,body:K,scoped:R,line:A.line,column:A.column}}parseExtends(A){let B=this.parseExpression();return this.expect("BLOCK_END"),{type:"Extends",template:B,line:A.line,column:A.column}}parseInclude(A){let B=this.parseExpression(),R=null,K=!1,O=!1;while(this.check("NAME")){let Q=this.peek().value;if(Q==="ignore"&&this.peekNext()?.value==="missing")this.advance(),this.advance(),O=!0;else if(Q==="with")this.advance(),R=this.parseKeywordArgs();else if(Q==="only")this.advance(),K=!0;else if(Q==="without"){if(this.advance(),this.check("NAME")&&this.peek().value==="context")this.advance(),K=!0}else break}return this.expect("BLOCK_END"),{type:"Include",template:B,context:R,only:K,ignoreMissing:O,line:A.line,column:A.column}}parseSet(A){let B=this.expect("NAME").value;this.expect("ASSIGN");let R=this.parseExpression();return this.expect("BLOCK_END"),{type:"Set",target:B,value:R,line:A.line,column:A.column}}parseWith(A){let B=[];do{let K=this.expect("NAME").value;this.expect("ASSIGN");let O=this.parseExpression();B.push({target:K,value:O})}while(this.match("COMMA")||this.check("NAME")&&this.peekNext()?.type==="ASSIGN");this.expect("BLOCK_END");let R=[];while(!this.isAtEnd()){if(this.checkBlockTag("endwith"))break;let K=this.parseStatement();if(K)R.push(K)}return this.expectBlockTag("endwith"),{type:"With",assignments:B,body:R,line:A.line,column:A.column}}parseLoad(A){let B=[];while(this.check("NAME"))B.push(this.advance().value);return this.expect("BLOCK_END"),{type:"Load",names:B,line:A.line,column:A.column}}parseUrl(A){let B=this.parseExpression(),R=[],K={},O=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),O=this.expect("NAME").value;break}if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let Q=this.advance().value;this.advance(),K[Q]=this.parseExpression()}else R.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Url",name:B,args:R,kwargs:K,asVar:O,line:A.line,column:A.column}}parseStatic(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Static",path:B,asVar:R,line:A.line,column:A.column}}parseNow(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Now",format:B,asVar:R,line:A.line,column:A.column}}parseComment(A){this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endcomment"))break;this.advance()}return this.expectBlockTag("endcomment"),null}parseSimpleBlock(A,B){this.skipToBlockEnd();let R=`end${B}`;while(!this.isAtEnd()){if(this.checkBlockTag(R))break;this.advance()}if(this.checkBlockTag(R))this.advance(),this.advance(),this.expect("BLOCK_END");return null}parseCycle(A){let B=[],R=null,K=!1;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){if(this.advance(),R=this.expect("NAME").value,this.check("NAME")&&this.peek().value==="silent")this.advance(),K=!0;break}B.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Cycle",values:B,asVar:R,silent:K,line:A.line,column:A.column}}parseFirstof(A){let B=[],R=null,K=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),K=this.expect("NAME").value;break}B.push(this.parseExpression())}if(B.length>0){let O=B[B.length-1];if(O.type==="Literal"&&typeof O.value==="string")R=B.pop()}return this.expect("BLOCK_END"),{type:"Firstof",values:B,fallback:R,asVar:K,line:A.line,column:A.column}}parseIfchanged(A){let B=[];while(!this.check("BLOCK_END"))B.push(this.parseExpression());this.expect("BLOCK_END");let R=[],K=[];while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)R.push(O)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)K.push(O)}}return this.expectBlockTag("endifchanged"),{type:"Ifchanged",values:B,body:R,else_:K,line:A.line,column:A.column}}parseRegroup(A){let B=this.parseExpression();this.expectName("by");let R=this.expect("NAME").value;this.expectName("as");let K=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Regroup",target:B,key:R,asVar:K,line:A.line,column:A.column}}parseWidthratio(A){let B=this.parseExpression(),R=this.parseExpression(),K=this.parseExpression(),O=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Widthratio",value:B,maxValue:R,maxWidth:K,asVar:O,line:A.line,column:A.column}}parseLorem(A){let B=null,R="p",K=!1;if(this.check("NUMBER"))B={type:"Literal",value:parseInt(this.advance().value,10),line:A.line,column:A.column};if(this.check("NAME")){let O=this.peek().value.toLowerCase();if(O==="w"||O==="p"||O==="b")R=O,this.advance()}if(this.check("NAME")&&this.peek().value==="random")K=!0,this.advance();return this.expect("BLOCK_END"),{type:"Lorem",count:B,method:R,random:K,line:A.line,column:A.column}}parseCsrfToken(A){return this.expect("BLOCK_END"),{type:"CsrfToken",line:A.line,column:A.column}}parseDebug(A){return this.expect("BLOCK_END"),{type:"Debug",line:A.line,column:A.column}}parseTemplatetag(A){let B=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Templatetag",tagType:B,line:A.line,column:A.column}}parseIfequal(A,B){let R=this.parseExpression(),K=this.parseExpression();this.expect("BLOCK_END");let O={type:"Compare",left:R,ops:[{operator:B?"!=":"==",right:K}],line:A.line,column:A.column},Q=[],U=[],M=B?"endifnotequal":"endifequal";while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)Q.push(Z)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)U.push(Z)}}return this.expectBlockTag(M),{type:"If",test:O,body:Q,elifs:[],else_:U,line:A.line,column:A.column}}parseExpression(){return this.parseConditional()}parseConditional(){let A=this.parseOr();if(this.check("NAME")&&this.peek().value==="if"){this.advance();let B=this.parseOr();this.expectName("else");let R=this.parseConditional();A={type:"Conditional",test:B,trueExpr:A,falseExpr:R,line:A.line,column:A.column}}return A}parseOr(){let A=this.parseAnd();while(this.check("OR")||this.check("NAME")&&this.peek().value==="or"){this.advance();let B=this.parseAnd();A={type:"BinaryOp",operator:"or",left:A,right:B,line:A.line,column:A.column}}return A}parseAnd(){let A=this.parseNot();while(this.check("AND")||this.check("NAME")&&this.peek().value==="and"){this.advance();let B=this.parseNot();A={type:"BinaryOp",operator:"and",left:A,right:B,line:A.line,column:A.column}}return A}parseNot(){if(this.check("NOT")||this.check("NAME")&&this.peek().value==="not"){let A=this.advance();return{type:"UnaryOp",operator:"not",operand:this.parseNot(),line:A.line,column:A.column}}return this.parseCompare()}parseCompare(){let A=this.parseAddSub(),B=[];while(!0){let R=null;if(this.match("EQ"))R="==";else if(this.match("NE"))R="!=";else if(this.match("LT"))R="<";else if(this.match("GT"))R=">";else if(this.match("LE"))R="<=";else if(this.match("GE"))R=">=";else if(this.check("NAME")){let O=this.peek().value;if(O==="in")this.advance(),R="in";else if(O==="not"&&this.peekNext()?.value==="in")this.advance(),this.advance(),R="not in";else if(O==="is"){this.advance();let Q=this.check("NOT");if(Q)this.advance();let M=this.expect("NAME").value,Z=[];if(this.match("LPAREN")){while(!this.check("RPAREN"))if(Z.push(this.parseExpression()),!this.check("RPAREN"))this.expect("COMMA");this.expect("RPAREN")}A={type:"TestExpr",node:A,test:M,args:Z,negated:Q,line:A.line,column:A.column};continue}}if(!R)break;let K=this.parseAddSub();B.push({operator:R,right:K})}if(B.length===0)return A;return{type:"Compare",left:A,ops:B,line:A.line,column:A.column}}parseAddSub(){let A=this.parseMulDiv();while(this.check("ADD")||this.check("SUB")||this.check("TILDE")){let B=this.advance(),R=this.parseMulDiv();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseMulDiv(){let A=this.parseUnary();while(this.check("MUL")||this.check("DIV")||this.check("MOD")){let B=this.advance(),R=this.parseUnary();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseUnary(){if(this.check("SUB")||this.check("ADD")){let A=this.advance(),B=this.parseUnary();return{type:"UnaryOp",operator:A.value,operand:B,line:A.line,column:A.column}}return this.parseFilter()}parseFilter(){let A=this.parsePostfix();while(this.match("PIPE")){let B=this.expect("NAME").value,R=[],K={};if(this.match("COLON"))if(this.check("SUB")||this.check("ADD")){let O=this.advance(),Q=this.parsePostfix();R.push({type:"UnaryOp",operator:O.value,operand:Q,line:O.line,column:O.column})}else R.push(this.parsePostfix());else if(this.match("LPAREN")){while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let O=this.advance().value;this.advance(),K[O]=this.parseExpression()}else R.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN")}A={type:"FilterExpr",node:A,filter:B,args:R,kwargs:K,line:A.line,column:A.column}}return A}parsePostfix(){let A=this.parsePrimary();while(!0)if(this.match("DOT")){let B;if(this.check("NUMBER"))B=this.advance().value;else B=this.expect("NAME").value;A={type:"GetAttr",object:A,attribute:B,line:A.line,column:A.column}}else if(this.match("LBRACKET")){let B=this.parseExpression();this.expect("RBRACKET"),A={type:"GetItem",object:A,index:B,line:A.line,column:A.column}}else if(this.match("LPAREN")){let B=[],R={};while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let K=this.advance().value;this.advance(),R[K]=this.parseExpression()}else B.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN"),A={type:"FunctionCall",callee:A,args:B,kwargs:R,line:A.line,column:A.column}}else break;return A}parsePrimary(){let A=this.peek();if(this.match("STRING"))return{type:"Literal",value:A.value,line:A.line,column:A.column};if(this.match("NUMBER"))return{type:"Literal",value:A.value.includes(".")?parseFloat(A.value):parseInt(A.value,10),line:A.line,column:A.column};if(this.check("NAME")){let B=this.advance().value;if(B==="true"||B==="True")return{type:"Literal",value:!0,line:A.line,column:A.column};if(B==="false"||B==="False")return{type:"Literal",value:!1,line:A.line,column:A.column};if(B==="none"||B==="None"||B==="null")return{type:"Literal",value:null,line:A.line,column:A.column};return{type:"Name",name:B,line:A.line,column:A.column}}if(this.match("LPAREN")){let B=this.parseExpression();return this.expect("RPAREN"),B}if(this.match("LBRACKET")){let B=[];while(!this.check("RBRACKET"))if(B.push(this.parseExpression()),!this.check("RBRACKET"))this.expect("COMMA");return this.expect("RBRACKET"),{type:"Array",elements:B,line:A.line,column:A.column}}if(this.match("LBRACE")){let B=[];while(!this.check("RBRACE")){let R=this.parseExpression();this.expect("COLON");let K=this.parseExpression();if(B.push({key:R,value:K}),!this.check("RBRACE"))this.expect("COMMA")}return this.expect("RBRACE"),{type:"Object",pairs:B,line:A.line,column:A.column}}throw this.error(`Unexpected token: ${A.type} (${A.value})`)}parseKeywordArgs(){let A={};while(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let B=this.advance().value;this.advance(),A[B]=this.parseExpression()}return A}checkBlockTag(A){if(this.peek().type!=="BLOCK_START")return!1;let B=this.current+1;if(B>=this.tokens.length)return!1;let R=this.tokens[B];return R.type==="NAME"&&R.value===A}expectBlockTag(A){this.advance();let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`);this.expect("BLOCK_END")}expectName(A){let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`)}skipToBlockEnd(){while(!this.isAtEnd()&&!this.check("BLOCK_END"))this.advance();if(this.check("BLOCK_END"))this.advance()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}peekNext(){if(this.current+1>=this.tokens.length)return null;return this.tokens[this.current+1]}advance(){if(!this.isAtEnd())this.current++;return this.tokens[this.current-1]}check(A){if(this.isAtEnd())return!1;return this.peek().type===A}match(A){if(this.check(A))return this.advance(),!0;return!1}expect(A){if(this.check(A))return this.advance();let B=this.peek();throw this.error(`Expected ${A}, got ${B.type} (${B.value})`)}error(A){let B=this.peek();return new H(A,{line:B.line,column:B.column,source:this.source})}}function f(A,B={}){return new y(B).compile(A)}class y{options;indent=0;varCounter=0;loopStack=[];localVars=[];constructor(A={}){this.options={functionName:A.functionName??"render",inlineHelpers:A.inlineHelpers??!0,minify:A.minify??!1,autoescape:A.autoescape??!0}}pushScope(){this.localVars.push(new Set)}popScope(){this.localVars.pop()}addLocalVar(A){if(this.localVars.length>0)this.localVars[this.localVars.length-1].add(A)}isLocalVar(A){for(let B=this.localVars.length-1;B>=0;B--)if(this.localVars[B].has(A))return!0;return!1}compile(A){let B=this.compileNodes(A.body),R=this.options.minify?"":`
|
|
13
13
|
`;return`function ${this.options.functionName}(__ctx) {${R} let __out = '';${R}`+B+` return __out;${R}}`}compileNodes(A){return A.map((B)=>this.compileNode(B)).join("")}compileNode(A){switch(A.type){case"Text":return this.compileText(A);case"Output":return this.compileOutput(A);case"If":return this.compileIf(A);case"For":return this.compileFor(A);case"Set":return this.compileSet(A);case"With":return this.compileWith(A);case"Comment":return"";case"Extends":case"Block":case"Include":throw Error(`AOT compilation does not support '${A.type}' - use Environment.render() for templates with inheritance`);case"Url":case"Static":throw Error(`AOT compilation does not support '${A.type}' tag - use Environment.render() with urlResolver/staticResolver`);default:throw Error(`Unknown node type in AOT compiler: ${A.type}`)}}compileText(A){return` __out += ${JSON.stringify(A.value)};${this.nl()}`}compileOutput(A){let B=this.compileExpr(A.expression);if(this.options.autoescape&&!this.isMarkedSafe(A.expression))return` __out += escape(${B});${this.nl()}`;return` __out += (${B}) ?? '';${this.nl()}`}compileIf(A){let B="",R=this.compileExpr(A.test);B+=` if (isTruthy(${R})) {${this.nl()}`,B+=this.compileNodes(A.body),B+=" }";for(let K of A.elifs){let O=this.compileExpr(K.test);B+=` else if (isTruthy(${O})) {${this.nl()}`,B+=this.compileNodes(K.body),B+=" }"}if(A.else_.length>0)B+=` else {${this.nl()}`,B+=this.compileNodes(A.else_),B+=" }";return B+=this.nl(),B}compileFor(A){let B=this.genVar("iter"),R=this.genVar("i"),K=this.genVar("len"),O=this.genVar("loop"),Q=Array.isArray(A.target)?A.target[0]:A.target,U=Array.isArray(A.target)&&A.target[1]?A.target[1]:null,M=this.loopStack.length>0?this.loopStack[this.loopStack.length-1]:null,Z=this.compileExpr(A.iter),$="";if($+=` const ${B} = toArray(${Z});${this.nl()}`,$+=` const ${K} = ${B}.length;${this.nl()}`,A.else_.length>0)$+=` if (${K} === 0) {${this.nl()}`,$+=this.compileNodes(A.else_),$+=` } else {${this.nl()}`;if($+=` for (let ${R} = 0; ${R} < ${K}; ${R}++) {${this.nl()}`,U)$+=` const ${Q} = ${B}[${R}][0];${this.nl()}`,$+=` const ${U} = ${B}[${R}][1];${this.nl()}`;else $+=` const ${Q} = ${B}[${R}];${this.nl()}`;if($+=` const ${O} = {${this.nl()}`,$+=` counter: ${R} + 1,${this.nl()}`,$+=` counter0: ${R},${this.nl()}`,$+=` revcounter: ${K} - ${R},${this.nl()}`,$+=` revcounter0: ${K} - ${R} - 1,${this.nl()}`,$+=` first: ${R} === 0,${this.nl()}`,$+=` last: ${R} === ${K} - 1,${this.nl()}`,$+=` length: ${K},${this.nl()}`,$+=` index: ${R} + 1,${this.nl()}`,$+=` index0: ${R},${this.nl()}`,M)$+=` parentloop: ${M},${this.nl()}`,$+=` parent: ${M}${this.nl()}`;else $+=` parentloop: null,${this.nl()}`,$+=` parent: null${this.nl()}`;$+=` };${this.nl()}`,$+=` const forloop = ${O};${this.nl()}`,$+=` const loop = ${O};${this.nl()}`,this.loopStack.push(O);let G=this.compileNodes(A.body);if($+=G.replace(new RegExp(`__ctx\\.${Q}`,"g"),Q),this.loopStack.pop(),$+=` }${this.nl()}`,A.else_.length>0)$+=` }${this.nl()}`;return $}compileSet(A){let B=this.compileExpr(A.value);return` const ${A.target} = ${B};${this.nl()}`}compileWith(A){let B=` {${this.nl()}`;this.pushScope();for(let{target:R,value:K}of A.assignments){let O=this.compileExpr(K);B+=` const ${R} = ${O};${this.nl()}`,this.addLocalVar(R)}return B+=this.compileNodes(A.body),B+=` }${this.nl()}`,this.popScope(),B}compileExpr(A){switch(A.type){case"Name":return this.compileName(A);case"Literal":return this.compileLiteral(A);case"Array":return this.compileArray(A);case"Object":return this.compileObject(A);case"BinaryOp":return this.compileBinaryOp(A);case"UnaryOp":return this.compileUnaryOp(A);case"Compare":return this.compileCompare(A);case"GetAttr":return this.compileGetAttr(A);case"GetItem":return this.compileGetItem(A);case"FilterExpr":return this.compileFilter(A);case"TestExpr":return this.compileTest(A);case"Conditional":return this.compileConditional(A);default:return"undefined"}}compileName(A){if(A.name==="true"||A.name==="True")return"true";if(A.name==="false"||A.name==="False")return"false";if(A.name==="none"||A.name==="None"||A.name==="null")return"null";if(A.name==="forloop"||A.name==="loop")return A.name;if(this.isLocalVar(A.name))return A.name;return`__ctx.${A.name}`}compileLiteral(A){if(typeof A.value==="string")return JSON.stringify(A.value);return String(A.value)}compileArray(A){return`[${A.elements.map((R)=>this.compileExpr(R)).join(", ")}]`}compileObject(A){return`{${A.pairs.map(({key:R,value:K})=>{let O=this.compileExpr(R),Q=this.compileExpr(K);return`[${O}]: ${Q}`}).join(", ")}}`}compileBinaryOp(A){let B=this.compileExpr(A.left),R=this.compileExpr(A.right);switch(A.operator){case"and":return`(${B} && ${R})`;case"or":return`(${B} || ${R})`;case"~":return`(String(${B}) + String(${R}))`;case"in":return`(Array.isArray(${R}) ? ${R}.includes(${B}) : String(${R}).includes(String(${B})))`;case"not in":return`!(Array.isArray(${R}) ? ${R}.includes(${B}) : String(${R}).includes(String(${B})))`;default:return`(${B} ${A.operator} ${R})`}}compileUnaryOp(A){let B=this.compileExpr(A.operand);switch(A.operator){case"not":return`!isTruthy(${B})`;case"-":return`-(${B})`;case"+":return`+(${B})`;default:return B}}compileCompare(A){let B=this.compileExpr(A.left);for(let{operator:R,right:K}of A.ops){let O=this.compileExpr(K);switch(R){case"==":case"===":B=`(${B} === ${O})`;break;case"!=":case"!==":B=`(${B} !== ${O})`;break;default:B=`(${B} ${R} ${O})`}}return B}compileGetAttr(A){return`${this.compileExpr(A.object)}?.${A.attribute}`}compileGetItem(A){let B=this.compileExpr(A.object),R=this.compileExpr(A.index);return`${B}?.[${R}]`}compileFilter(A){let B=this.compileExpr(A.node),R=A.args.map((K)=>this.compileExpr(K));switch(A.filter){case"upper":return`String(${B}).toUpperCase()`;case"lower":return`String(${B}).toLowerCase()`;case"title":return`String(${B}).replace(/\\b\\w/g, c => c.toUpperCase())`;case"trim":return`String(${B}).trim()`;case"length":return`(${B}?.length ?? Object.keys(${B} ?? {}).length)`;case"first":return`(${B})?.[0]`;case"last":return`(${B})?.[(${B})?.length - 1]`;case"default":return`((${B}) ?? ${R[0]??'""'})`;case"safe":return`{ __safe: true, value: String(${B}) }`;case"escape":case"e":return`escape(${B})`;case"join":return`(${B} ?? []).join(${R[0]??'""'})`;case"abs":return`Math.abs(${B})`;case"round":return R.length?`Number(${B}).toFixed(${R[0]})`:`Math.round(${B})`;case"int":return`parseInt(${B}, 10)`;case"float":return`parseFloat(${B})`;case"floatformat":return`Number(${B}).toFixed(${R[0]??1})`;case"filesizeformat":return`applyFilter('filesizeformat', ${B})`;default:let K=R.length?", "+R.join(", "):"";return`applyFilter('${A.filter}', ${B}${K})`}}compileTest(A){let B=this.compileExpr(A.node),R=A.args.map((O)=>this.compileExpr(O)),K=A.negated?"!":"";switch(A.test){case"defined":return`${K}(${B} !== undefined)`;case"undefined":return`${K}(${B} === undefined)`;case"none":return`${K}(${B} === null)`;case"even":return`${K}(${B} % 2 === 0)`;case"odd":return`${K}(${B} % 2 !== 0)`;case"divisibleby":return`${K}(${B} % ${R[0]} === 0)`;case"empty":return`${K}((${B} == null) || (${B}.length === 0) || (Object.keys(${B}).length === 0))`;case"iterable":return`${K}(Array.isArray(${B}) || typeof ${B} === 'string')`;case"number":return`${K}(typeof ${B} === 'number' && !isNaN(${B}))`;case"string":return`${K}(typeof ${B} === 'string')`;default:let O=R.length?", "+R.join(", "):"";return`${K}applyTest('${A.test}', ${B}${O})`}}compileConditional(A){let B=this.compileExpr(A.test),R=this.compileExpr(A.trueExpr),K=this.compileExpr(A.falseExpr);return`(isTruthy(${B}) ? ${R} : ${K})`}isMarkedSafe(A){if(A.type==="FilterExpr")return A.filter==="safe";return!1}genVar(A){return`__${A}${this.varCounter++}`}nl(){return this.options.minify?"":`
|
|
14
|
-
`}}function
|
|
14
|
+
`}}function k(A,B){return new g(B).flatten(A)}function N(A){return new m().check(A)}class g{loader;maxDepth;blocks=new Map;depth=0;constructor(A){this.loader=A.loader,this.maxDepth=A.maxDepth??10}flatten(A){return this.blocks.clear(),this.depth=0,this.processTemplate(A)}processTemplate(A,B=!0){if(this.depth>this.maxDepth)throw Error(`Maximum template inheritance depth (${this.maxDepth}) exceeded`);this.collectBlocks(A.body,B);let R=this.findExtends(A.body);if(R){let K=this.getStaticTemplateName(R.template),O=this.loader.load(K),Q=this.loader.parse(O);this.depth++;let U=this.processTemplate(Q,!1);return this.depth--,{type:"Template",body:this.replaceBlocks(U.body),line:A.line,column:A.column}}return{type:"Template",body:this.processNodes(A.body),line:A.line,column:A.column}}collectBlocks(A,B=!0){for(let R of A){if(R.type==="Block"){let K=R;if(B||!this.blocks.has(K.name))this.blocks.set(K.name,K)}this.collectBlocksFromNode(R,B)}}collectBlocksFromNode(A,B=!0){switch(A.type){case"If":{let R=A;this.collectBlocks(R.body,B);for(let K of R.elifs)this.collectBlocks(K.body,B);this.collectBlocks(R.else_,B);break}case"For":{let R=A;this.collectBlocks(R.body,B),this.collectBlocks(R.else_,B);break}case"With":{let R=A;this.collectBlocks(R.body,B);break}case"Block":{let R=A;this.collectBlocks(R.body,B);break}}}findExtends(A){for(let B of A)if(B.type==="Extends")return B;return null}processNodes(A){let B=[];for(let R of A){if(R.type==="Extends")continue;if(R.type==="Include"){let O=R,Q=this.inlineInclude(O);B.push(...Q);continue}if(R.type==="Block"){let O=R,Q=this.blocks.get(O.name);if(Q&&Q!==O)B.push(...this.processNodes(Q.body));else B.push(...this.processNodes(O.body));continue}let K=this.processNode(R);if(K)B.push(K)}return B}processNode(A){switch(A.type){case"If":{let B=A;return{...B,body:this.processNodes(B.body),elifs:B.elifs.map((R)=>({test:R.test,body:this.processNodes(R.body)})),else_:this.processNodes(B.else_)}}case"For":{let B=A;return{...B,body:this.processNodes(B.body),else_:this.processNodes(B.else_)}}case"With":{let B=A;return{...B,body:this.processNodes(B.body)}}default:return A}}replaceBlocks(A){return this.processNodes(A)}inlineInclude(A){let B=this.getStaticTemplateName(A.template);try{let R=this.loader.load(B),K=this.loader.parse(R);this.depth++;let O=this.processTemplate(K);if(this.depth--,A.context&&Object.keys(A.context).length>0)return[{type:"With",assignments:Object.entries(A.context).map(([U,M])=>({target:U,value:M})),body:O.body,line:A.line,column:A.column}];return O.body}catch(R){if(A.ignoreMissing)return[];throw R}}getStaticTemplateName(A){if(A.type==="Literal"){let B=A;if(typeof B.value==="string")return B.value}throw Error(`AOT compilation requires static template names. Found dynamic expression at line ${A.line}. Use Environment.render() for dynamic template names.`)}}class m{check(A){return this.checkNodes(A.body)}checkNodes(A){for(let B of A){let R=this.checkNode(B);if(!R.canFlatten)return R}return{canFlatten:!0}}checkNode(A){switch(A.type){case"Extends":{let B=A;if(!this.isStaticName(B.template))return{canFlatten:!1,reason:`Dynamic extends at line ${A.line} - use static string literal`};break}case"Include":{let B=A;if(!this.isStaticName(B.template))return{canFlatten:!1,reason:`Dynamic include at line ${A.line} - use static string literal`};break}case"If":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;for(let K of B.elifs)if(R=this.checkNodes(K.body),!R.canFlatten)return R;if(R=this.checkNodes(B.else_),!R.canFlatten)return R;break}case"For":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;if(R=this.checkNodes(B.else_),!R.canFlatten)return R;break}case"With":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;break}case"Block":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;break}}return{canFlatten:!0}}isStaticName(A){return A.type==="Literal"&&typeof A.value==="string"}}var i="0.1.1",J={reset:"\x1B[0m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",cyan:"\x1B[36m",dim:"\x1B[2m"};function F(A){console.log(A)}function C(A){console.log(`${J.green}\u2713${J.reset} ${A}`)}function _(A){console.log(`${J.yellow}\u26A0${J.reset} ${A}`)}function I(A){console.error(`${J.red}\u2717${J.reset} ${A}`)}function w(){console.log(`
|
|
15
15
|
${J.cyan}binja${J.reset} - High-performance template compiler
|
|
16
16
|
|
|
17
17
|
${J.yellow}Usage:${J.reset}
|
|
@@ -94,5 +94,5 @@ ${A}
|
|
|
94
94
|
|
|
95
95
|
export { ${B} as render };
|
|
96
96
|
export default ${B};
|
|
97
|
-
`}async function
|
|
98
|
-
Stopped watching.`),process.exit(0)})}async function n(){let A=process.argv.slice(2);if(A.length===0)w(),process.exit(0);let{command:B,source:R,options:K}=u(A);if(!B)
|
|
97
|
+
`}async function b(A,B,R,K){try{let O=X.readFileSync(A,"utf-8"),Q=V(R,K.extensions),U=Q.parse(O),M=N(U),Z=U;if(!M.canFlatten){if(K.verbose)_(`${Y.basename(A)}: ${M.reason} - compiling without inheritance resolution`)}else Z=k(U,{loader:Q});let $=Y.relative(R,A),G=K.name||"render"+$.replace(/\.[^.]+$/,"").replace(/[^a-zA-Z0-9]/g,"_").replace(/^_+|_+$/g,"").replace(/_([a-z])/g,(o,c)=>c.toUpperCase()),W=f(Z,{functionName:G,minify:K.minify}),z=$.replace(/\.[^.]+$/,".js"),E=Y.join(B,z),L=Y.dirname(E);if(!X.existsSync(L))X.mkdirSync(L,{recursive:!0});let j=s(W,G);return X.writeFileSync(E,j),{success:!0,outputPath:E}}catch(O){return{success:!1,error:O.message}}}async function T(A,B,R){let K=0,O=0,Q=[];function U(M){let Z=X.readdirSync(M,{withFileTypes:!0});for(let $ of Z){let G=Y.join(M,$.name);if($.isDirectory())U(G);else if($.isFile()){let W=Y.extname($.name);if(R.extensions.includes(W))Q.push(G)}}}U(A);for(let M of Q){let Z=await b(M,B,A,R);if(Z.success){if(K++,R.verbose)C(`${Y.relative(A,M)} \u2192 ${Y.relative(process.cwd(),Z.outputPath)}`)}else O++,I(`${Y.relative(A,M)}: ${Z.error}`)}return{compiled:K,failed:O}}async function r(A,B){let R=V(A,B.extensions),K=0,O=0,Q=0;function U(M){let Z=X.readdirSync(M,{withFileTypes:!0});for(let $ of Z){let G=Y.join(M,$.name);if($.isDirectory())U(G);else if($.isFile()){let W=Y.extname($.name);if(B.extensions.includes(W)){K++;try{let z=X.readFileSync(G,"utf-8"),E=R.parse(z),L=N(E),j=Y.relative(A,G);if(L.canFlatten)O++,C(`${j}`);else Q++,_(`${j}: ${L.reason}`)}catch(z){Q++,I(`${Y.relative(A,G)}: ${z.message}`)}}}}}if(U(A),F(""),F(`Total: ${K} templates`),F(`${J.green}AOT compatible: ${O}${J.reset}`),Q>0)F(`${J.yellow}Require runtime: ${Q}${J.reset}`)}async function l(A,B,R){F(`${J.cyan}Watching${J.reset} ${A} for changes...`),F(`${J.dim}Press Ctrl+C to stop${J.reset}`),F("");let{compiled:K,failed:O}=await T(A,B,{...R,verbose:!0});F(""),F(`Compiled ${K} templates${O>0?`, ${O} failed`:""}`),F("");let Q=X.watch(A,{recursive:!0},async(U,M)=>{if(!M)return;let Z=Y.extname(M);if(!R.extensions.includes(Z))return;let $=Y.join(A,M);if(!X.existsSync($))return;F(`${J.dim}[${new Date().toLocaleTimeString()}]${J.reset} ${M} changed`);let G=await b($,B,A,R);if(G.success)C(`Compiled ${M}`);else I(`${M}: ${G.error}`)});process.on("SIGINT",()=>{Q.close(),F(`
|
|
98
|
+
Stopped watching.`),process.exit(0)})}async function n(){let A=process.argv.slice(2);if(A.length===0)w(),process.exit(0);let{command:B,source:R,options:K}=u(A);if(!B)I('No command specified. Use "compile" or "check".'),w(),process.exit(1);if(!R)I("No source path specified."),process.exit(1);let O=Y.resolve(R);if(!X.existsSync(O))I(`Source not found: ${R}`),process.exit(1);let Q=X.statSync(O).isDirectory();if(B==="check")if(Q)await r(O,K);else{let U=V(Y.dirname(O),K.extensions),M=X.readFileSync(O,"utf-8"),Z=U.parse(M),$=N(Z);if($.canFlatten)C(`${R} can be AOT compiled`);else _(`${R}: ${$.reason}`)}else if(B==="compile"){if(!K.output)I("Output directory required. Use -o <dir>"),process.exit(1);let U=Y.resolve(K.output);if(K.watch){if(!Q)I("Watch mode requires a directory, not a single file."),process.exit(1);await l(O,U,K)}else if(Q){let M=Date.now(),{compiled:Z,failed:$}=await T(O,U,K),G=Date.now()-M;if(F(""),$===0)C(`Compiled ${Z} templates in ${G}ms`);else _(`Compiled ${Z} templates, ${$} failed (${G}ms)`)}else{let M=await b(O,U,Y.dirname(O),K);if(M.success)C(`Compiled to ${M.outputPath}`);else I(M.error),process.exit(1)}}}n().catch((A)=>{I(A.message),process.exit(1)});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handlebars Engine
|
|
3
|
+
* Converts Handlebars templates to binja's common AST format
|
|
4
|
+
*/
|
|
5
|
+
import type { TemplateNode } from '../../parser/nodes';
|
|
6
|
+
export { HandlebarsLexer, HbsTokenType, type HbsToken } from './lexer';
|
|
7
|
+
export { HandlebarsParser } from './parser';
|
|
8
|
+
/**
|
|
9
|
+
* Parse a Handlebars template string into an AST
|
|
10
|
+
*/
|
|
11
|
+
export declare function parse(source: string): TemplateNode;
|
|
12
|
+
/**
|
|
13
|
+
* Compile a Handlebars template to a render function
|
|
14
|
+
*/
|
|
15
|
+
export declare function compile(source: string): (context: Record<string, any>) => Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Render a Handlebars template with context
|
|
18
|
+
*/
|
|
19
|
+
export declare function render(source: string, context?: Record<string, any>): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Engine interface for multi-engine support
|
|
22
|
+
*/
|
|
23
|
+
export declare const engine: {
|
|
24
|
+
name: string;
|
|
25
|
+
extensions: string[];
|
|
26
|
+
parse: typeof parse;
|
|
27
|
+
compile: typeof compile;
|
|
28
|
+
render: typeof render;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handlebars Lexer
|
|
3
|
+
* Tokenizes Handlebars template syntax: {{expr}}, {{#if}}, {{/if}}, {{>partial}}
|
|
4
|
+
*/
|
|
5
|
+
export declare enum HbsTokenType {
|
|
6
|
+
TEXT = "TEXT",
|
|
7
|
+
OPEN = "OPEN",// {{
|
|
8
|
+
OPEN_BLOCK = "OPEN_BLOCK",// {{#
|
|
9
|
+
OPEN_END = "OPEN_END",// {{/
|
|
10
|
+
OPEN_PARTIAL = "OPEN_PARTIAL",// {{>
|
|
11
|
+
OPEN_UNESCAPED = "OPEN_UNESCAPED",// {{{
|
|
12
|
+
OPEN_COMMENT = "OPEN_COMMENT",// {{!
|
|
13
|
+
CLOSE = "CLOSE",// }}
|
|
14
|
+
CLOSE_UNESCAPED = "CLOSE_UNESCAPED",// }}}
|
|
15
|
+
ID = "ID",// identifier
|
|
16
|
+
STRING = "STRING",
|
|
17
|
+
NUMBER = "NUMBER",
|
|
18
|
+
BOOLEAN = "BOOLEAN",
|
|
19
|
+
DOT = "DOT",
|
|
20
|
+
DOTDOT = "DOTDOT",// ..
|
|
21
|
+
SLASH = "SLASH",
|
|
22
|
+
EQUALS = "EQUALS",
|
|
23
|
+
PIPE = "PIPE",
|
|
24
|
+
EOF = "EOF"
|
|
25
|
+
}
|
|
26
|
+
export interface HbsToken {
|
|
27
|
+
type: HbsTokenType;
|
|
28
|
+
value: string;
|
|
29
|
+
line: number;
|
|
30
|
+
column: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class HandlebarsLexer {
|
|
33
|
+
private source;
|
|
34
|
+
private pos;
|
|
35
|
+
private line;
|
|
36
|
+
private column;
|
|
37
|
+
private tokens;
|
|
38
|
+
constructor(source: string);
|
|
39
|
+
tokenize(): HbsToken[];
|
|
40
|
+
private scanToken;
|
|
41
|
+
private scanText;
|
|
42
|
+
private scanExpression;
|
|
43
|
+
private scanExpressionToken;
|
|
44
|
+
private scanString;
|
|
45
|
+
private scanNumber;
|
|
46
|
+
private scanIdentifier;
|
|
47
|
+
private scanComment;
|
|
48
|
+
private scanBlockComment;
|
|
49
|
+
private isAtEnd;
|
|
50
|
+
private peek;
|
|
51
|
+
private peekNext;
|
|
52
|
+
private advance;
|
|
53
|
+
private check;
|
|
54
|
+
private match;
|
|
55
|
+
private skipWhitespace;
|
|
56
|
+
private isDigit;
|
|
57
|
+
private isAlpha;
|
|
58
|
+
private isAlphaNumeric;
|
|
59
|
+
private addToken;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=lexer.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handlebars Parser
|
|
3
|
+
* Converts Handlebars tokens to a common AST format
|
|
4
|
+
*/
|
|
5
|
+
import { HbsToken } from './lexer';
|
|
6
|
+
import type { TemplateNode } from '../../parser/nodes';
|
|
7
|
+
export declare class HandlebarsParser {
|
|
8
|
+
private tokens;
|
|
9
|
+
private current;
|
|
10
|
+
private source;
|
|
11
|
+
constructor(tokens: HbsToken[], source?: string);
|
|
12
|
+
parse(): TemplateNode;
|
|
13
|
+
private parseNodes;
|
|
14
|
+
private parseNode;
|
|
15
|
+
private parseText;
|
|
16
|
+
private parseOutput;
|
|
17
|
+
private parseBlock;
|
|
18
|
+
private parseIfBlock;
|
|
19
|
+
private parseUnlessBlock;
|
|
20
|
+
private parseEachBlock;
|
|
21
|
+
private parseWithBlock;
|
|
22
|
+
private parseCustomBlock;
|
|
23
|
+
private parsePartial;
|
|
24
|
+
private parseExpression;
|
|
25
|
+
private parseExpressionAtom;
|
|
26
|
+
private parsePath;
|
|
27
|
+
private checkOpenBlock;
|
|
28
|
+
private consumeElse;
|
|
29
|
+
private consumeEndBlock;
|
|
30
|
+
private skipComment;
|
|
31
|
+
private isAtEnd;
|
|
32
|
+
private peek;
|
|
33
|
+
private advance;
|
|
34
|
+
private check;
|
|
35
|
+
private expect;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Engine Support
|
|
3
|
+
* Unified interface for multiple template engines
|
|
4
|
+
*/
|
|
5
|
+
import * as handlebars from './handlebars';
|
|
6
|
+
import * as liquid from './liquid';
|
|
7
|
+
export { handlebars, liquid };
|
|
8
|
+
/**
|
|
9
|
+
* Engine interface
|
|
10
|
+
*/
|
|
11
|
+
export interface TemplateEngine {
|
|
12
|
+
name: string;
|
|
13
|
+
extensions: string[];
|
|
14
|
+
parse: (source: string) => any;
|
|
15
|
+
compile: (source: string) => (context: Record<string, any>) => Promise<string>;
|
|
16
|
+
render: (source: string, context?: Record<string, any>) => Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Registry of all available engines
|
|
20
|
+
*/
|
|
21
|
+
export declare const engines: Record<string, TemplateEngine>;
|
|
22
|
+
/**
|
|
23
|
+
* Get engine by name or file extension
|
|
24
|
+
*/
|
|
25
|
+
export declare function getEngine(nameOrExt: string): TemplateEngine | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Detect engine from file path
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectEngine(filePath: string): TemplateEngine | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Render a template with auto-detected engine
|
|
32
|
+
*/
|
|
33
|
+
export declare function render(source: string, context?: Record<string, any>, engineName?: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Multi-engine environment for API service
|
|
36
|
+
*/
|
|
37
|
+
export declare class MultiEngine {
|
|
38
|
+
private defaultEngine;
|
|
39
|
+
constructor(defaultEngine?: string);
|
|
40
|
+
/**
|
|
41
|
+
* Render with specified engine
|
|
42
|
+
*/
|
|
43
|
+
render(source: string, context?: Record<string, any>, engineName?: string): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Compile template with specified engine
|
|
46
|
+
*/
|
|
47
|
+
compile(source: string, engineName?: string): (context: Record<string, any>) => Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* List all available engines
|
|
50
|
+
*/
|
|
51
|
+
listEngines(): string[];
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Liquid Engine
|
|
3
|
+
* Converts Liquid (Shopify) templates to binja's common AST format
|
|
4
|
+
*/
|
|
5
|
+
import type { TemplateNode } from '../../parser/nodes';
|
|
6
|
+
export { LiquidLexer, LiquidTokenType, type LiquidToken } from './lexer';
|
|
7
|
+
export { LiquidParser } from './parser';
|
|
8
|
+
/**
|
|
9
|
+
* Parse a Liquid template string into an AST
|
|
10
|
+
*/
|
|
11
|
+
export declare function parse(source: string): TemplateNode;
|
|
12
|
+
/**
|
|
13
|
+
* Compile a Liquid template to a render function
|
|
14
|
+
*/
|
|
15
|
+
export declare function compile(source: string): (context: Record<string, any>) => Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Render a Liquid template with context
|
|
18
|
+
*/
|
|
19
|
+
export declare function render(source: string, context?: Record<string, any>): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Engine interface for multi-engine support
|
|
22
|
+
*/
|
|
23
|
+
export declare const engine: {
|
|
24
|
+
name: string;
|
|
25
|
+
extensions: string[];
|
|
26
|
+
parse: typeof parse;
|
|
27
|
+
compile: typeof compile;
|
|
28
|
+
render: typeof render;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Liquid Lexer
|
|
3
|
+
* Tokenizes Liquid template syntax: {{ output }}, {% tags %}
|
|
4
|
+
* Shopify-compatible implementation
|
|
5
|
+
*/
|
|
6
|
+
export declare enum LiquidTokenType {
|
|
7
|
+
TEXT = "TEXT",
|
|
8
|
+
VAR_START = "VAR_START",// {{
|
|
9
|
+
VAR_END = "VAR_END",// }}
|
|
10
|
+
TAG_START = "TAG_START",// {%
|
|
11
|
+
TAG_END = "TAG_END",// %}
|
|
12
|
+
ID = "ID",
|
|
13
|
+
STRING = "STRING",
|
|
14
|
+
NUMBER = "NUMBER",
|
|
15
|
+
DOT = "DOT",
|
|
16
|
+
PIPE = "PIPE",
|
|
17
|
+
COLON = "COLON",
|
|
18
|
+
COMMA = "COMMA",
|
|
19
|
+
LBRACKET = "LBRACKET",
|
|
20
|
+
RBRACKET = "RBRACKET",
|
|
21
|
+
RANGE = "RANGE",// ..
|
|
22
|
+
EQUALS = "EQUALS",// =
|
|
23
|
+
EQ = "EQ",// ==
|
|
24
|
+
NE = "NE",// != or <>
|
|
25
|
+
LT = "LT",
|
|
26
|
+
LE = "LE",
|
|
27
|
+
GT = "GT",
|
|
28
|
+
GE = "GE",
|
|
29
|
+
CONTAINS = "CONTAINS",
|
|
30
|
+
AND = "AND",
|
|
31
|
+
OR = "OR",
|
|
32
|
+
EOF = "EOF"
|
|
33
|
+
}
|
|
34
|
+
export interface LiquidToken {
|
|
35
|
+
type: LiquidTokenType;
|
|
36
|
+
value: string;
|
|
37
|
+
line: number;
|
|
38
|
+
column: number;
|
|
39
|
+
}
|
|
40
|
+
export declare class LiquidLexer {
|
|
41
|
+
private source;
|
|
42
|
+
private pos;
|
|
43
|
+
private line;
|
|
44
|
+
private column;
|
|
45
|
+
private tokens;
|
|
46
|
+
constructor(source: string);
|
|
47
|
+
tokenize(): LiquidToken[];
|
|
48
|
+
private scanToken;
|
|
49
|
+
private checkRawTag;
|
|
50
|
+
private scanRawBlock;
|
|
51
|
+
private scanText;
|
|
52
|
+
private scanExpression;
|
|
53
|
+
private scanExpressionToken;
|
|
54
|
+
private scanString;
|
|
55
|
+
private scanNumber;
|
|
56
|
+
private scanIdentifier;
|
|
57
|
+
private isAtEnd;
|
|
58
|
+
private peek;
|
|
59
|
+
private peekNext;
|
|
60
|
+
private advance;
|
|
61
|
+
private check;
|
|
62
|
+
private match;
|
|
63
|
+
private skipWhitespace;
|
|
64
|
+
private isDigit;
|
|
65
|
+
private isAlpha;
|
|
66
|
+
private isAlphaNumeric;
|
|
67
|
+
private addToken;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=lexer.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Liquid Parser
|
|
3
|
+
* Converts Liquid tokens to a common AST format
|
|
4
|
+
* Shopify-compatible implementation
|
|
5
|
+
*/
|
|
6
|
+
import { LiquidToken } from './lexer';
|
|
7
|
+
import type { TemplateNode } from '../../parser/nodes';
|
|
8
|
+
export declare class LiquidParser {
|
|
9
|
+
private tokens;
|
|
10
|
+
private current;
|
|
11
|
+
private source;
|
|
12
|
+
constructor(tokens: LiquidToken[], source?: string);
|
|
13
|
+
parse(): TemplateNode;
|
|
14
|
+
private parseNodes;
|
|
15
|
+
private parseNode;
|
|
16
|
+
private parseText;
|
|
17
|
+
private parseOutput;
|
|
18
|
+
private parseTag;
|
|
19
|
+
private parseIfTag;
|
|
20
|
+
private parseUnlessTag;
|
|
21
|
+
private parseCaseTag;
|
|
22
|
+
private parseForTag;
|
|
23
|
+
private parseForIterable;
|
|
24
|
+
private parseAssignTag;
|
|
25
|
+
private parseCaptureTag;
|
|
26
|
+
private parseIncrementTag;
|
|
27
|
+
private parseIncludeTag;
|
|
28
|
+
private parseCommentTag;
|
|
29
|
+
private parseRawTag;
|
|
30
|
+
private parseCondition;
|
|
31
|
+
private parseOr;
|
|
32
|
+
private parseAnd;
|
|
33
|
+
private parseComparison;
|
|
34
|
+
private parseExpression;
|
|
35
|
+
private parseExpressionAtom;
|
|
36
|
+
private parsePath;
|
|
37
|
+
private checkTag;
|
|
38
|
+
private consumeTag;
|
|
39
|
+
private checkKeyword;
|
|
40
|
+
private expectKeyword;
|
|
41
|
+
private isAtEnd;
|
|
42
|
+
private peek;
|
|
43
|
+
private advance;
|
|
44
|
+
private check;
|
|
45
|
+
private expect;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=parser.d.ts.map
|