A 100% Jinja2-compatible template engine for Go — verified character-by-character against CPython.
English | 简体中文
Existing Go ports of Jinja2 (pongo2, gonja, …) stop at being template parsers. But Jinja2 is not a syntax — it is a language that lives on top of the Python runtime. {{ -7 // 2 }}, {{ 1 == True }}, {{ "日本語"[1:] }}, {{ d.items() }}: getting these right means reimplementing a thin slice of Python's object model, not just its template grammar.
gojinja2 implements that semantic layer, and proves it the only way that means anything: every test fixture is generated by running the official CPython Jinja2 (pallets/jinja 3.1.6) and recording its real output. The Go engine must match it character for character — rendered output, token streams, AST shapes, and even error messages.
- Full expression grammar — chained comparisons, Python slicing,
*args/**kwargs, conditional expressions, filter/test expressions, implicit tuples - Every statement —
if/for(incl.recursive, the completeloopobject, loop filtering) /set(incl. block assignments andnamespace()) /with/macro(caller, varargs, kwargs, closures, call-time defaults) /call/filter/autoescape - Template inheritance, completely — multi-level
extends,block(scoped/required),super()andsuper.super(),self, dynamic parent expressions include&import—ignore missing, list fallbacks,with/without context- All 54 built-in filters, 39 tests, every global (
range,dict,namespace,cycler,joiner,lipsum) — including the long tail:tojson,urlize,wordwrap(a faithfultextwrapport),truncatewithleeway,groupbywith case restoration - Python value semantics — banker's rounding, floor division and modulo sign rules,
1 == 1.0 == True, rune-level string indexing, insertion-ordered dicts with Python key unification - The four Undefined types — default / Chainable / Debug / Strict, full behavior matrix
- Autoescape &
Markupcontagion — through~,+,%,join,replace, every escape path - Whitespace control, token-exact —
trim_blocks,lstrip_blocks,{%-,{%+, line statements & line comments, custom delimiters (PHP / ERB styles) - Extensions —
do,loopcontrols(break/continue),i18n(trans/pluralize/trimmed/ gettext context) - Loaders —
DictLoader,FileSystemLoader,FSLoader(works withembed.FS),FunctionLoader,ChoiceLoader,PrefixLoader
go get github.com/yzfly/gojinja2package main
import (
"embed"
"fmt"
gojinja2 "github.com/yzfly/gojinja2"
)
//go:embed templates
var templates embed.FS
func main() {
// Inline templates
env := gojinja2.NewEnvironment()
tpl, _ := env.FromString("Hello {{ name|title }}! {% for i in range(3) %}{{ i }}{% endfor %}")
out, _ := tpl.Render(map[string]any{"name": "world"})
fmt.Println(out) // Hello World! 012
// File templates + inheritance + autoescaping
env2 := gojinja2.NewEnvironment()
env2.Loader = gojinja2.NewFSLoader(templates, "templates")
env2.Autoescape = true
page, _ := env2.GetTemplate("child.html")
html, _ := page.Render(map[string]any{"user": "<script>"})
fmt.Println(html) // <script> is escaped
}Attribute access follows Python semantics (attribute first, then subscript):
map[string]anyand nested structures behave as dicts — key access,.items(),.get()and friends all work- Struct fields and methods resolve by exact name first, then
snake_case → CamelCase({{ user.get_name() }}callsGetName()) - Numbers normalize to
int64/float64; slices and arrays behave as lists
No hand-written expectations. Generators under tools/ feed each case (template × context × environment config) to the official CPython Jinja2 and record what it actually produces — output or exception. The Go test suites then align, character by character:
| Layer | Corpus | What must match |
|---|---|---|
| Lexer | 114 cases / 632 tokens | token types, values, line numbers; error messages verbatim |
| Parser | 110 cases | AST equal to Python repr(ast) character-for-character; 18 error cases |
| Render | 333 cases | rendered output verbatim; runtime error messages verbatim |
Current score: 557/557, plus one documented divergence (see below).
Regenerate the corpus yourself (requires CPython and the reference checkout):
git clone --depth 1 --branch 3.1.6 https://github.com/pallets/jinja.git reference/jinja
PYTHONPATH=reference/jinja/src python3 tools/gen_lexer_fixtures.py > lexer/testdata/lexer_fixtures.json
PYTHONPATH=reference/jinja/src python3 tools/gen_parser_fixtures.py > parser/testdata/parser_fixtures.json
PYTHONPATH=reference/jinja/src python3 tools/gen_render_fixtures.py > rendertest/testdata/render_fixtures.json
go test ./...This pipeline is the project's real asset: adding coverage is mechanical — add a case, CPython produces the expectation, the Go suite enforces it.
Honesty over marketing. The complete list:
- Integer precision — Python integers are arbitrary-precision; gojinja2 uses
int64(abig.Intupgrade path is reserved). Literals beyondint64raise an error rather than silently truncating. - Native Go map iteration order — user-supplied Go maps iterate in sorted key order (Go maps are unordered by design). Dicts created inside templates are strictly insertion-ordered, matching Python.
- Out of scope by declaration —
sandbox,async, bytecode caching: Python-ecosystem mechanisms with no Go equivalent. Not counted against compatibility.
gojinja2/
├── lexer/ # hand-written scanner replicating the official regex semantics, lazy errors
├── parser/ # 1:1 port of parser.py
├── nodes/ # AST, with Python-repr-aligned dumps for conformance
├── runtime/ # the Python semantic layer: values, operators, Undefined, Markup, ordered dict
├── exceptions/ # error types mirroring jinja2.exceptions
├── rendertest/ # render-level conformance suite
├── tools/ # corpus generators (CPython as ground truth)
└── *.go # Environment / Template / interpreter / filters / loaders / extensions
Two deliberate departures from CPython's implementation, neither observable from templates:
- Jinja2 compiles templates to Python source; gojinja2 uses a tree-walking interpreter (Go cannot
exec, and the conformance suite proves behavioral equivalence). - The official lexer is regex-driven with lookbehind/lookahead, which Go's RE2 cannot express; the scanner is hand-written to replicate those semantics exactly — including lazy error ordering.
See CONTRIBUTING.md. The golden rule: any behavioral claim must come with a CPython-generated fixture.
CC BY-NC 4.0 (non-commercial). For commercial licensing, contact the author.
云中江树 (yzfly) — WeChat Official Account: 云中江树