binja 0.7.2 → 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/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/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:
|
|
@@ -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
|