How to deal with spacing in Jinja2 templates
It started with a comment in a GitHub issue.
I often find it difficult to wrangle spacing jinja2 templates especially around optional clauses.
I couldn't wrap my head either about this. So I decided to write a script and test every combination of newlines, indentation and dashes or no dashes in {%-
, with the goal being to find combinations that will not have an extra middle line when an optional Jinja clause is false.
Script¤
from jinja2 import Environment
flip = (True, False)
n = "\n"
def get_template(n1, n2, n3, n4, d1, d2, d3, d4, i):
return (
f" a"
f"{(n + (' ' if i else '')) if n1 else ''}"
f"{{%{'-' if d1 else ''} if x {'-' if d2 else ''}%}}"
f"{n if n2 else ''}"
f" b"
f"{(n + (' ' if i else '')) if n3 else ''}"
f"{{%{'-' if d3 else ''} endif {'-' if d4 else ''}%}}"
f"{n if n4 else ''}"
f" c"
)
def get_templates():
for newline1 in flip:
for newline2 in flip:
for newline3 in flip:
for newline4 in flip:
for dash1 in flip:
for dash2 in flip:
for dash3 in flip:
for dash4 in flip:
for indent in flip:
yield get_template(
newline1,
newline2,
newline3,
newline4,
dash1,
dash2,
dash3,
dash4,
indent,
)
if __name__ == "__main__":
good = []
env = Environment()
for n, template_string in enumerate(sorted(set(get_templates())), 1):
template = env.from_string(template_string)
true = template.render(x=True)
false = template.render(x=False)
if true == " a\n b\n c" and false == " a\n c":
good.append(template_string)
print("\n\n---\n\n".join(good))
print(f"\ntested: {n}\nvalid: {len(good)}")
Results¤
Only 19 different combinations are valid on the 448 tested ones:
a
{% if x -%}
b
{% endif -%}
c
---
a
{% if x -%}
b
{% endif -%} c
---
a
{% if x -%} b
{% endif -%}
c
---
a
{% if x -%} b
{% endif -%} c
---
a
{%- if x %}
b
{%- endif %}
c
---
a
{%- if x %}
b{% endif %}
c
---
a
{%- if x %}
b{%- endif %}
c
---
a
{% if x %} b
{% endif %} c
---
a
{%- if x %}
b
{%- endif %}
c
---
a
{%- if x %}
b{% endif %}
c
---
a
{%- if x %}
b{%- endif %}
c
---
a{% if x %}
b
{%- endif %}
c
---
a{% if x %}
b
{%- endif %}
c
---
a{% if x %}
b{% endif %}
c
---
a{% if x %}
b{%- endif %}
c
---
a{%- if x %}
b
{%- endif %}
c
---
a{%- if x %}
b
{%- endif %}
c
---
a{%- if x %}
b{% endif %}
c
---
a{%- if x %}
b{%- endif %}
c
tested: 448
valid: 19
Each one of these will render as
a
b
c
...when the condition is true, and as
a
c
...when the condition is false.
Now you just have to pick the style you prefer!
I find these three particularly readable (the first two are better when b
spans on multiple lines):
a
{%- if x %}
b
{%- endif %}
c
a
{% if x -%}
b
{% endif -%}
c
a{% if x %}
b{% endif %}
c
The third example readability can also be improved:
a {%- if x %}
b {%- endif %}
c
Here is an example with blank lines in b
's contents. The first one collapses contents up, while the second collapses content down.
class Run:
def pause(self):
print("pausing")
{%- if stoppable %}
def stop(self):
print("stopping")
{%- endif %}
def resume(self):
print("resuming")
class Run:
def pause(self):
print("pausing")
{% if stoppable -%}
def stop(self):
print("stopping")
{% endif -%}
def resume(self):
print("resuming")