https://github.com/yzfly/gojinja2
⛩️ 100% Jinja2-compatible template engine for Go — verified character-by-character against CPython | Go 语言 Jinja2 模板引擎, 与 CPython 逐字符对齐验证
https://github.com/yzfly/gojinja2
conformance-testing go golang jinja jinja2 jinja2-templates python template-engine templates templating
Last synced: 24 days ago
JSON representation
⛩️ 100% Jinja2-compatible template engine for Go — verified character-by-character against CPython | Go 语言 Jinja2 模板引擎, 与 CPython 逐字符对齐验证
- Host: GitHub
- URL: https://github.com/yzfly/gojinja2
- Owner: yzfly
- License: other
- Created: 2026-06-13T01:13:06.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-13T02:31:04.000Z (about 1 month ago)
- Last Synced: 2026-06-13T04:17:54.302Z (about 1 month ago)
- Topics: conformance-testing, go, golang, jinja, jinja2, jinja2-templates, python, template-engine, templates, templating
- Language: Go
- Size: 139 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# ⛩️ gojinja2
**A 100% Jinja2-compatible template engine for Go — verified character-by-character against CPython.**
[](https://github.com/yzfly/gojinja2/actions/workflows/ci.yml)
[](https://pkg.go.dev/github.com/yzfly/gojinja2)
[](https://go.dev)
[](#how-conformance-is-verified)
[](LICENSE)
English | [简体中文](README.zh-CN.md)
---
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.
## Highlights
- **Full expression grammar** — chained comparisons, Python slicing, `*args` / `**kwargs`, conditional expressions, filter/test expressions, implicit tuples
- **Every statement** — `if` / `for` (incl. `recursive`, the complete `loop` object, loop filtering) / `set` (incl. block assignments and `namespace()`) / `with` / `macro` (caller, varargs, kwargs, closures, call-time defaults) / `call` / `filter` / `autoescape`
- **Template inheritance, completely** — multi-level `extends`, `block` (`scoped` / `required`), `super()` and `super.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 faithful `textwrap` port), `truncate` with `leeway`, `groupby` with 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 & `Markup` contagion** — 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 with `embed.FS`), `FunctionLoader`, `ChoiceLoader`, `PrefixLoader`
## Quick start
```bash
go get github.com/yzfly/gojinja2
```
```go
package 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": ""})
fmt.Println(html) // <script> is escaped
}
```
### Mapping Go values
Attribute access follows Python semantics (attribute first, then subscript):
- `map[string]any` and 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() }}` calls `GetName()`)
- Numbers normalize to `int64` / `float64`; slices and arrays behave as lists
## How conformance is verified
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):
```bash
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.
## Documented divergences
Honesty over marketing. The complete list:
1. **Integer precision** — Python integers are arbitrary-precision; gojinja2 uses `int64` (a `big.Int` upgrade path is reserved). Literals beyond `int64` raise an error rather than silently truncating.
2. **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.
3. **Out of scope by declaration** — `sandbox`, `async`, bytecode caching: Python-ecosystem mechanisms with no Go equivalent. Not counted against compatibility.
## Architecture
```
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.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). The golden rule: any behavioral claim must come with a CPython-generated fixture.
## License
[CC BY-NC 4.0](LICENSE) (non-commercial). For commercial licensing, contact the author.
## Author
**云中江树 (yzfly)** — WeChat Official Account: 云中江树