{"id":51068062,"url":"https://github.com/yzfly/gojinja2","last_synced_at":"2026-06-23T08:03:48.760Z","repository":{"id":364445129,"uuid":"1267916884","full_name":"yzfly/gojinja2","owner":"yzfly","description":"⛩️ 100% Jinja2-compatible template engine for Go — verified character-by-character against CPython | Go 语言 Jinja2 模板引擎, 与 CPython 逐字符对齐验证","archived":false,"fork":false,"pushed_at":"2026-06-13T02:31:04.000Z","size":142,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T04:17:54.302Z","etag":null,"topics":["conformance-testing","go","golang","jinja","jinja2","jinja2-templates","python","template-engine","templates","templating"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yzfly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T01:13:06.000Z","updated_at":"2026-06-13T03:55:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yzfly/gojinja2","commit_stats":null,"previous_names":["yzfly/gojinja2"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/yzfly/gojinja2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yzfly%2Fgojinja2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yzfly%2Fgojinja2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yzfly%2Fgojinja2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yzfly%2Fgojinja2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yzfly","download_url":"https://codeload.github.com/yzfly/gojinja2/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yzfly%2Fgojinja2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34680621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["conformance-testing","go","golang","jinja","jinja2","jinja2-templates","python","template-engine","templates","templating"],"created_at":"2026-06-23T08:03:48.061Z","updated_at":"2026-06-23T08:03:48.751Z","avatar_url":"https://github.com/yzfly.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# ⛩️ gojinja2\n\n**A 100% Jinja2-compatible template engine for Go — verified character-by-character against CPython.**\n\n[![CI](https://github.com/yzfly/gojinja2/actions/workflows/ci.yml/badge.svg)](https://github.com/yzfly/gojinja2/actions/workflows/ci.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/yzfly/gojinja2.svg)](https://pkg.go.dev/github.com/yzfly/gojinja2)\n[![Go Version](https://img.shields.io/badge/go-%3E%3D1.22-00ADD8?logo=go)](https://go.dev)\n[![Conformance](https://img.shields.io/badge/CPython%20conformance-557%2F557-brightgreen)](#how-conformance-is-verified)\n[![License: CC BY-NC 4.0](https://img.shields.io/badge/license-CC%20BY--NC%204.0-lightgrey)](LICENSE)\n\nEnglish | [简体中文](README.zh-CN.md)\n\n\u003c/div\u003e\n\n---\n\nExisting 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.\n\n**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.\n\n## Highlights\n\n- **Full expression grammar** — chained comparisons, Python slicing, `*args` / `**kwargs`, conditional expressions, filter/test expressions, implicit tuples\n- **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`\n- **Template inheritance, completely** — multi-level `extends`, `block` (`scoped` / `required`), `super()` and `super.super()`, `self`, dynamic parent expressions\n- **`include` \u0026 `import`** — `ignore missing`, list fallbacks, `with` / `without context`\n- **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\n- **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\n- **The four Undefined types** — default / Chainable / Debug / Strict, full behavior matrix\n- **Autoescape \u0026 `Markup` contagion** — through `~`, `+`, `%`, `join`, `replace`, every escape path\n- **Whitespace control, token-exact** — `trim_blocks`, `lstrip_blocks`, `{%-`, `{%+`, line statements \u0026 line comments, custom delimiters (PHP / ERB styles)\n- **Extensions** — `do`, `loopcontrols` (`break` / `continue`), `i18n` (`trans` / `pluralize` / `trimmed` / gettext context)\n- **Loaders** — `DictLoader`, `FileSystemLoader`, `FSLoader` (works with `embed.FS`), `FunctionLoader`, `ChoiceLoader`, `PrefixLoader`\n\n## Quick start\n\n```bash\ngo get github.com/yzfly/gojinja2\n```\n\n```go\npackage main\n\nimport (\n    \"embed\"\n    \"fmt\"\n\n    gojinja2 \"github.com/yzfly/gojinja2\"\n)\n\n//go:embed templates\nvar templates embed.FS\n\nfunc main() {\n    // Inline templates\n    env := gojinja2.NewEnvironment()\n    tpl, _ := env.FromString(\"Hello {{ name|title }}! {% for i in range(3) %}{{ i }}{% endfor %}\")\n    out, _ := tpl.Render(map[string]any{\"name\": \"world\"})\n    fmt.Println(out) // Hello World! 012\n\n    // File templates + inheritance + autoescaping\n    env2 := gojinja2.NewEnvironment()\n    env2.Loader = gojinja2.NewFSLoader(templates, \"templates\")\n    env2.Autoescape = true\n    page, _ := env2.GetTemplate(\"child.html\")\n    html, _ := page.Render(map[string]any{\"user\": \"\u003cscript\u003e\"})\n    fmt.Println(html) // \u003cscript\u003e is escaped\n}\n```\n\n### Mapping Go values\n\nAttribute access follows Python semantics (attribute first, then subscript):\n\n- `map[string]any` and nested structures behave as dicts — key access, `.items()`, `.get()` and friends all work\n- Struct fields and methods resolve by exact name first, then `snake_case → CamelCase` (`{{ user.get_name() }}` calls `GetName()`)\n- Numbers normalize to `int64` / `float64`; slices and arrays behave as lists\n\n## How conformance is verified\n\nNo 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:\n\n| Layer | Corpus | What must match |\n|---|---|---|\n| Lexer | 114 cases / 632 tokens | token types, values, line numbers; error messages verbatim |\n| Parser | 110 cases | AST equal to Python `repr(ast)` character-for-character; 18 error cases |\n| Render | 333 cases | rendered output verbatim; runtime error messages verbatim |\n\nCurrent score: **557/557**, plus one *documented* divergence (see below).\n\nRegenerate the corpus yourself (requires CPython and the reference checkout):\n\n```bash\ngit clone --depth 1 --branch 3.1.6 https://github.com/pallets/jinja.git reference/jinja\nPYTHONPATH=reference/jinja/src python3 tools/gen_lexer_fixtures.py  \u003e lexer/testdata/lexer_fixtures.json\nPYTHONPATH=reference/jinja/src python3 tools/gen_parser_fixtures.py \u003e parser/testdata/parser_fixtures.json\nPYTHONPATH=reference/jinja/src python3 tools/gen_render_fixtures.py \u003e rendertest/testdata/render_fixtures.json\ngo test ./...\n```\n\nThis pipeline is the project's real asset: adding coverage is mechanical — add a case, CPython produces the expectation, the Go suite enforces it.\n\n## Documented divergences\n\nHonesty over marketing. The complete list:\n\n1. **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.\n2. **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.\n3. **Out of scope by declaration** — `sandbox`, `async`, bytecode caching: Python-ecosystem mechanisms with no Go equivalent. Not counted against compatibility.\n\n## Architecture\n\n```\ngojinja2/\n├── lexer/        # hand-written scanner replicating the official regex semantics, lazy errors\n├── parser/       # 1:1 port of parser.py\n├── nodes/        # AST, with Python-repr-aligned dumps for conformance\n├── runtime/      # the Python semantic layer: values, operators, Undefined, Markup, ordered dict\n├── exceptions/   # error types mirroring jinja2.exceptions\n├── rendertest/   # render-level conformance suite\n├── tools/        # corpus generators (CPython as ground truth)\n└── *.go          # Environment / Template / interpreter / filters / loaders / extensions\n```\n\nTwo deliberate departures from CPython's implementation, neither observable from templates:\n\n- Jinja2 compiles templates to Python source; gojinja2 uses a tree-walking interpreter (Go cannot `exec`, and the conformance suite proves behavioral equivalence).\n- 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.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). The golden rule: any behavioral claim must come with a CPython-generated fixture.\n\n## License\n\n[CC BY-NC 4.0](LICENSE) (non-commercial). For commercial licensing, contact the author.\n\n## Author\n\n**云中江树 (yzfly)** — WeChat Official Account: 云中江树\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyzfly%2Fgojinja2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyzfly%2Fgojinja2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyzfly%2Fgojinja2/lists"}