Skip to content

Commit e920e8f

Browse files
Add documentation for Jinja2TT2 Perl transpiler
Document the Jinja2TT2 Perl transpiler, including usage, features, and installation instructions.
1 parent 8b36725 commit e920e8f

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
---
2+
title: "Jinja2TT2: A Perl Transpiler"
3+
date: 2026-01-28
4+
comments: true
5+
---
6+
7+
**Jinja2::TT2**: A Perl transpiler that converts Jinja2 templates to Template Toolkit 2 (TT2) syntax. Now available on CPAN.
8+
9+
## The Migration Problem
10+
11+
Template migration is one of those tasks that nobody wants to do. You inherit a Python project with hundreds of Jinja2 templates, but your infrastructure runs on Perl. Or you're consolidating two codebases and need everything in one templating language. The manual approach means opening each file, mentally parsing the Jinja2 syntax, and rewriting it in TT2. It's tedious, error-prone, and soul-crushing.
12+
13+
I faced this exact situation. Rather than spend days doing mechanical find-and-replace operations, I wrote a transpiler to automate the conversion.
14+
15+
## Why This Works
16+
17+
At first glance, converting between template languages seems like it would require understanding the host language deeply. Jinja2 is Python's templating engine; Template Toolkit is Perl's. Different ecosystems, different philosophies.
18+
19+
But look closer at the syntax:
20+
21+
```
22+
Jinja2: {{ user.name|upper }}
23+
TT2: [% user.name.upper %]
24+
```
25+
26+
```
27+
Jinja2: {% for item in items %}
28+
TT2: [% FOREACH item IN items %]
29+
```
30+
31+
```
32+
Jinja2: {% if logged_in %}Welcome{% endif %}
33+
TT2: [% IF logged_in %]Welcome[% END %]
34+
```
35+
36+
The patterns are remarkably similar. Both languages use delimiters to separate template code from literal text. Both support variables, loops, conditionals, filters, includes, blocks, and macros. The concepts map almost one-to-one; only the spelling differs.
37+
38+
This isn't a coincidence. Template Toolkit predates Jinja2 by several years, and Jinja2's creator Armin Ronacher was clearly influenced by existing template engines. The result is two languages that share enough DNA to make mechanical translation feasible.
39+
40+
## The Approach
41+
42+
Jinja2::TT2 follows a classic three-stage compiler architecture:
43+
44+
**Tokenization** breaks the input into meaningful chunks. A Jinja2 template is a mix of literal text and template directives. The tokenizer identifies each `{{ }}` variable block, each `{% %}` statement, each `{# #}` comment, and the raw text between them.
45+
46+
**Parsing** builds structure from the token stream. A `{% for %}` token isn't just text—it opens a loop that must eventually close with `{% endfor %}`. The parser constructs an Abstract Syntax Tree that represents these relationships. Nested loops, conditionals inside loops, macros containing blocks—all become nodes in the tree.
47+
48+
**Emission** walks the tree and generates TT2 code. Each node type has a corresponding output format. A for-loop node becomes `[% FOREACH ... %]`. A variable with filters becomes a chain of method calls. The tree structure ensures that nesting is preserved correctly.
49+
50+
This design makes the transpiler easy to extend. Adding support for a new Jinja2 construct means adding a token pattern, a parser rule, and an emitter method. The stages are independent.
51+
52+
## Practical Usage
53+
54+
The command-line interface handles the common cases:
55+
56+
```bash
57+
# Convert a single file
58+
jinja2tt2 template.j2 -o template.tt
59+
60+
# Convert in place (creates .tt alongside .j2)
61+
jinja2tt2 -i template.j2
62+
63+
# Pipe from stdin
64+
echo '{{ name|upper }}' | jinja2tt2
65+
```
66+
67+
For batch conversion, combine with find:
68+
69+
```bash
70+
find templates/ -name "*.j2" -exec jinja2tt2 -i {} \;
71+
```
72+
73+
The programmatic API gives you more control:
74+
75+
```perl
76+
use Jinja2::TT2;
77+
78+
my $transpiler = Jinja2::TT2->new();
79+
my $tt2_code = $transpiler->transpile($jinja2_source);
80+
```
81+
82+
Debug mode (`--debug`) dumps the token stream and AST, useful for understanding how a particular template gets parsed or for diagnosing conversion issues.
83+
84+
## What Converts Cleanly
85+
86+
Most Jinja2 templates convert without any manual intervention:
87+
88+
**Variables** translate directly. Dot notation, bracket notation, and nested access all work. `{{ user.profile.avatar }}` becomes `[% user.profile.avatar %]`.
89+
90+
**Filters** map to TT2 vmethods or operators. `{{ name|upper|trim }}` becomes `[% name.upper.trim %]`. Filter chaining is preserved. Filters with arguments like `{{ items|join(", ") }}` work correctly.
91+
92+
**Control structures** convert with keyword changes. `if`/`elif`/`else`/`endif` becomes `IF`/`ELSIF`/`ELSE`/`END`. `for`/`endfor` becomes `FOREACH`/`END`. The loop variable mappings (`loop.index``loop.count`, `loop.index0``loop.index`) are handled automatically.
93+
94+
**Macros and blocks** have direct equivalents. `{% macro button(text) %}` becomes `[% MACRO button(text) BLOCK %]`. Block definitions convert cleanly.
95+
96+
**Comments and whitespace control** work as expected. `{# comment #}` becomes `[%# comment %]`. The whitespace-stripping markers `{{-` and `-}}` become `[%-` and `-%]`.
97+
98+
## Where Manual Review Helps
99+
100+
Some Jinja2 features don't have direct TT2 equivalents:
101+
102+
**Template inheritance** with `{% extends %}` and `{% block %}` works differently in TT2. Jinja2 uses child-extends-parent; TT2 uses `WRAPPER` for similar effects. The transpiler converts the block definitions, but the extends relationship needs manual restructuring.
103+
104+
**Autoescape** is a Jinja2 concept without a TT2 equivalent. If your templates rely on automatic HTML escaping, you'll need to add explicit `| html_entity` filters where needed.
105+
106+
**Complex expressions** embedded in templates may need review. Jinja2 allows Python expressions; TT2 has its own expression syntax. Simple comparisons and boolean logic convert fine, but anything that relies on Python-specific behavior should be checked.
107+
108+
**Custom filters** in your Jinja2 environment won't exist in TT2. The transpiler converts the filter syntax, but you'll need to implement matching vmethods or plugins on the TT2 side.
109+
110+
## Installation
111+
112+
From CPAN:
113+
114+
```bash
115+
cpanm Jinja2::TT2
116+
```
117+
118+
No external dependencies beyond core Perl 5.20+. The distribution includes a test suite covering the tokenizer, parser, and emitter.
119+
120+
## The Perl Choice
121+
122+
A template transpiler is fundamentally a text processing tool. It reads text, transforms it according to rules, and writes text. This is Perl's home territory.
123+
124+
The implementation uses only core modules. No CPAN dependencies to install, no version conflicts to resolve. Copy the script to a server from 2010 and it runs. That kind of portability matters for system tools.
125+
126+
The three-stage architecture also fits Perl's strengths. Regular expressions handle tokenization elegantly. Recursive descent parsing is natural in Perl. String interpolation makes the emitter readable.
127+
128+
Could this be written in Python? Sure. But then you'd need Python installed to convert away from Python templates. There's a certain elegance in using Perl to migrate toward Perl.
129+
130+
## Links
131+
132+
- **CPAN**: [Jinja2::TT2](https://metacpan.org/pod/Jinja2::TT2)
133+
- **GitHub**: [lucianofedericopereira/jinja2tt2](https://github.com/lucianofedericopereira/jinja2tt2)
134+
135+
## License
136+
137+
LGPL-2.1

0 commit comments

Comments
 (0)