mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-28 21:41:35 +00:00
207 lines
5.7 KiB
Python
207 lines
5.7 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from itertools import product, chain, zip_longest, tee
|
|
|
|
|
|
# The amount of branches to generate
|
|
BRANCHES = 2 # branches
|
|
|
|
IF, ELSE_IF, ELSE, END_IF = 0, 1, 2, 3
|
|
|
|
NL = "\\n"
|
|
|
|
|
|
def handle_ws(ws, trim=True):
|
|
return [" ", "-" if trim else "+"][ws]
|
|
|
|
|
|
def trim(s, ws):
|
|
if ws[0]:
|
|
s = s.lstrip()
|
|
if ws[1]:
|
|
s = s.rstrip()
|
|
return s
|
|
|
|
|
|
def cond_kind(i, n):
|
|
i += 1
|
|
if i == 1:
|
|
return IF # if
|
|
elif (i == n) and (i > 1):
|
|
return ELSE # else
|
|
elif i > n:
|
|
return END_IF # endif
|
|
else:
|
|
return ELSE_IF # else if
|
|
|
|
|
|
# From: https://docs.python.org/3/library/itertools.html#itertools-recipes
|
|
def pairwise(iterable):
|
|
a, b = tee(iterable)
|
|
next(b, None)
|
|
return zip(a, b)
|
|
|
|
|
|
def write_cond(conds, active_branch, sign, invert):
|
|
n = len(conds) - 1
|
|
trim = sign == "-"
|
|
|
|
lits = []
|
|
for i in range(1, n + 2 + 1):
|
|
ws1 = "\\n" * i
|
|
ws2 = "\\r\\n" * i
|
|
lits.append((ws1, str(i), ws2))
|
|
|
|
conds = list(conds)
|
|
for i, (pws, nws) in enumerate(conds):
|
|
kind = cond_kind(i, n)
|
|
b = str(i == active_branch).lower()
|
|
cond = [f"if {b}", f"else if {b}", "else", "endif"][kind]
|
|
cond = f"{{%{handle_ws(pws, trim)} {cond} {handle_ws(nws, trim)}%}}"
|
|
conds[i] = cond
|
|
|
|
it = map("".join, lits)
|
|
it = filter(None, chain.from_iterable(zip_longest(it, conds)))
|
|
code = "".join(it)
|
|
|
|
expected = f"{lits[0][0]}{lits[0][1]}"
|
|
for i, (cond, (before, after)) in enumerate(zip(conds, pairwise(lits))):
|
|
kind = cond_kind(i, n)
|
|
pws = cond.startswith(f"{{%{sign}")
|
|
nws = cond.endswith(f"{sign}%}}")
|
|
|
|
if sign == "+":
|
|
pws = not pws
|
|
nws = not nws
|
|
elif invert:
|
|
pws = True
|
|
nws = True
|
|
|
|
cond = i == active_branch
|
|
prev_cond = i == (active_branch + 1)
|
|
|
|
if prev_cond or (kind == IF):
|
|
expected += before[2] * (not pws)
|
|
if cond or (kind == END_IF):
|
|
expected += after[0] * (not nws)
|
|
expected += after[1]
|
|
|
|
# FIXME: Rinja does not include whitespace before eof
|
|
# expected += lits[-1][2]
|
|
|
|
return code, expected
|
|
|
|
|
|
def write_match(contents, arms, match_ws):
|
|
before, expr, after = contents
|
|
code = before
|
|
|
|
pws, nws = match_ws[0]
|
|
code += f"{{%{handle_ws(pws)} match {expr} {handle_ws(nws)}%}}"
|
|
|
|
for (arm, expr), (pws, nws) in zip(arms, match_ws[1:-1]):
|
|
code += f"{{%{handle_ws(pws)} when {arm} {handle_ws(nws)}%}}{expr}"
|
|
|
|
pws, nws = match_ws[-1]
|
|
code += f"{{%{handle_ws(pws)} endmatch {handle_ws(nws)}%}}"
|
|
code += after
|
|
|
|
return code
|
|
|
|
|
|
def write_match_result(active_arm, contents, arms, match_ws):
|
|
before, expr, after = contents
|
|
expected = ""
|
|
|
|
expected += trim(before, (False, match_ws[0][0]))
|
|
expected += trim(arms[active_arm][1], (match_ws[1:][active_arm][1], match_ws[1:][active_arm+1][0]))
|
|
expected += trim(after, (match_ws[-1][1], False))
|
|
return expected
|
|
|
|
|
|
def write_cond_tests(f, name_extra, config):
|
|
f.write("""
|
|
macro_rules! test_template{name_extra} {{
|
|
($source:literal, $rendered:expr) => {{{{
|
|
#[derive(Template)]
|
|
#[template(source = $source, ext = "txt"{config})]
|
|
struct CondWs;
|
|
|
|
assert_eq!(CondWs.render().unwrap(), $rendered);
|
|
}}}};
|
|
}}
|
|
|
|
#[rustfmt::skip]
|
|
#[test]
|
|
fn test_cond_ws{name_extra}() {{
|
|
""".format(name_extra = name_extra, config = config))
|
|
|
|
invert = len(name_extra) > 0
|
|
sign = "+" if invert else "-"
|
|
|
|
for branches in range(1, BRANCHES + 1):
|
|
for x in product([False, True], repeat=(branches+1)*2):
|
|
# it = iter(x)
|
|
# conds = list(zip(it, it))
|
|
conds = list(zip(x[::2], x[1::2]))
|
|
|
|
for i in range(branches):
|
|
code, expected = write_cond(conds, i, sign, invert)
|
|
f.write(f' test_template{name_extra}!("{code}", "{expected}");\n')
|
|
if invert and ("{%+" in code or "+%}" in code):
|
|
# We also check that the minus sign is trimming with suppress_whitespace
|
|
# option enabled.
|
|
code, expected = write_cond(conds, i, "-", invert)
|
|
f.write(f' test_template{name_extra}!("{code}", "{expected}");\n')
|
|
|
|
if branches != BRANCHES:
|
|
f.write("\n")
|
|
f.write("}\n")
|
|
|
|
|
|
def write_match_tests(f):
|
|
f.write("""
|
|
#[rustfmt::skip]
|
|
macro_rules! test_match {
|
|
($source:literal, $some_rendered:expr, $none_rendered:expr) => {{
|
|
#[derive(Template)]
|
|
#[template(source = $source, ext = "txt")]
|
|
struct MatchWs {
|
|
item: Option<&'static str>,
|
|
}
|
|
|
|
assert_eq!(MatchWs { item: Some("foo") }.render().unwrap(), $some_rendered);
|
|
assert_eq!(MatchWs { item: None }.render().unwrap(), $none_rendered);
|
|
}};
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
#[test]
|
|
fn test_match_ws() {
|
|
""")
|
|
|
|
contents = "before ", "item", " after"
|
|
arms = [("Some with (item)", " foo "), ("None", " bar ")]
|
|
|
|
for x in product([False, True], repeat=len(arms)*2+1):
|
|
x = [False, False, *x, False]
|
|
arms_ws = list(zip(x[::2], x[1::2]))
|
|
|
|
code = write_match(contents, arms, arms_ws)
|
|
some_expected = write_match_result(0, contents, arms, arms_ws)
|
|
none_expected = write_match_result(1, contents, arms, arms_ws)
|
|
|
|
f.write(f' test_match!("{code}", "{some_expected}", "{none_expected}");\n')
|
|
|
|
f.write("}\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
with open("ws.rs", "w") as f:
|
|
f.write("// This file is auto generated by gen_ws_tests.py\n\n")
|
|
f.write("use rinja::Template;\n")
|
|
write_cond_tests(f, "", "")
|
|
write_cond_tests(f, "_inverted", ', config = "test_trim.toml"')
|
|
write_match_tests(f)
|