test_lexnparse.py 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. import pytest
  2. from jinja2 import Environment
  3. from jinja2 import nodes
  4. from jinja2 import Template
  5. from jinja2 import TemplateSyntaxError
  6. from jinja2 import UndefinedError
  7. from jinja2.lexer import Token
  8. from jinja2.lexer import TOKEN_BLOCK_BEGIN
  9. from jinja2.lexer import TOKEN_BLOCK_END
  10. from jinja2.lexer import TOKEN_EOF
  11. from jinja2.lexer import TokenStream
  12. class TestTokenStream:
  13. test_tokens = [
  14. Token(1, TOKEN_BLOCK_BEGIN, ""),
  15. Token(2, TOKEN_BLOCK_END, ""),
  16. ]
  17. def test_simple(self, env):
  18. ts = TokenStream(self.test_tokens, "foo", "bar")
  19. assert ts.current.type is TOKEN_BLOCK_BEGIN
  20. assert bool(ts)
  21. assert not bool(ts.eos)
  22. next(ts)
  23. assert ts.current.type is TOKEN_BLOCK_END
  24. assert bool(ts)
  25. assert not bool(ts.eos)
  26. next(ts)
  27. assert ts.current.type is TOKEN_EOF
  28. assert not bool(ts)
  29. assert bool(ts.eos)
  30. def test_iter(self, env):
  31. token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")]
  32. assert token_types == [
  33. "block_begin",
  34. "block_end",
  35. ]
  36. class TestLexer:
  37. def test_raw1(self, env):
  38. tmpl = env.from_string(
  39. "{% raw %}foo{% endraw %}|"
  40. "{%raw%}{{ bar }}|{% baz %}{% endraw %}"
  41. )
  42. assert tmpl.render() == "foo|{{ bar }}|{% baz %}"
  43. def test_raw2(self, env):
  44. tmpl = env.from_string("1 {%- raw -%} 2 {%- endraw -%} 3")
  45. assert tmpl.render() == "123"
  46. def test_raw3(self, env):
  47. # The second newline after baz exists because it is AFTER the
  48. # {% raw %} and is ignored.
  49. env = Environment(lstrip_blocks=True, trim_blocks=True)
  50. tmpl = env.from_string("bar\n{% raw %}\n {{baz}}2 spaces\n{% endraw %}\nfoo")
  51. assert tmpl.render(baz="test") == "bar\n\n {{baz}}2 spaces\nfoo"
  52. def test_raw4(self, env):
  53. # The trailing dash of the {% raw -%} cleans both the spaces and
  54. # newlines up to the first character of data.
  55. env = Environment(lstrip_blocks=True, trim_blocks=False)
  56. tmpl = env.from_string(
  57. "bar\n{%- raw -%}\n\n \n 2 spaces\n space{%- endraw -%}\nfoo"
  58. )
  59. assert tmpl.render() == "bar2 spaces\n spacefoo"
  60. def test_balancing(self, env):
  61. env = Environment("{%", "%}", "${", "}")
  62. tmpl = env.from_string(
  63. """{% for item in seq
  64. %}${{'foo': item}|upper}{% endfor %}"""
  65. )
  66. assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
  67. def test_comments(self, env):
  68. env = Environment("<!--", "-->", "{", "}")
  69. tmpl = env.from_string(
  70. """\
  71. <ul>
  72. <!--- for item in seq -->
  73. <li>{item}</li>
  74. <!--- endfor -->
  75. </ul>"""
  76. )
  77. assert tmpl.render(seq=list(range(3))) == (
  78. "<ul>\n <li>0</li>\n <li>1</li>\n <li>2</li>\n</ul>"
  79. )
  80. def test_string_escapes(self, env):
  81. for char in "\0", "\u2668", "\xe4", "\t", "\r", "\n":
  82. tmpl = env.from_string(f"{{{{ {char!r} }}}}")
  83. assert tmpl.render() == char
  84. assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == "\u2668"
  85. def test_bytefallback(self, env):
  86. from pprint import pformat
  87. tmpl = env.from_string("""{{ 'foo'|pprint }}|{{ 'bär'|pprint }}""")
  88. assert tmpl.render() == pformat("foo") + "|" + pformat("bär")
  89. def test_operators(self, env):
  90. from jinja2.lexer import operators
  91. for test, expect in operators.items():
  92. if test in "([{}])":
  93. continue
  94. stream = env.lexer.tokenize(f"{{{{ {test} }}}}")
  95. next(stream)
  96. assert stream.current.type == expect
  97. def test_normalizing(self, env):
  98. for seq in "\r", "\r\n", "\n":
  99. env = Environment(newline_sequence=seq)
  100. tmpl = env.from_string("1\n2\r\n3\n4\n")
  101. result = tmpl.render()
  102. assert result.replace(seq, "X") == "1X2X3X4"
  103. def test_trailing_newline(self, env):
  104. for keep in [True, False]:
  105. env = Environment(keep_trailing_newline=keep)
  106. for template, expected in [
  107. ("", {}),
  108. ("no\nnewline", {}),
  109. ("with\nnewline\n", {False: "with\nnewline"}),
  110. ("with\nseveral\n\n\n", {False: "with\nseveral\n\n"}),
  111. ]:
  112. tmpl = env.from_string(template)
  113. expect = expected.get(keep, template)
  114. result = tmpl.render()
  115. assert result == expect, (keep, template, result, expect)
  116. @pytest.mark.parametrize(
  117. ("name", "valid"),
  118. [
  119. ("foo", True),
  120. ("föö", True),
  121. ("き", True),
  122. ("_", True),
  123. ("1a", False), # invalid ascii start
  124. ("a-", False), # invalid ascii continue
  125. ("\U0001f40da", False), # invalid unicode start
  126. ("a🐍\U0001f40d", False), # invalid unicode continue
  127. # start characters not matched by \w
  128. ("\u1885", True),
  129. ("\u1886", True),
  130. ("\u2118", True),
  131. ("\u212e", True),
  132. # continue character not matched by \w
  133. ("\xb7", False),
  134. ("a\xb7", True),
  135. ],
  136. )
  137. def test_name(self, env, name, valid):
  138. t = "{{ " + name + " }}"
  139. if valid:
  140. # valid for version being tested, shouldn't raise
  141. env.from_string(t)
  142. else:
  143. pytest.raises(TemplateSyntaxError, env.from_string, t)
  144. def test_lineno_with_strip(self, env):
  145. tokens = env.lex(
  146. """\
  147. <html>
  148. <body>
  149. {%- block content -%}
  150. <hr>
  151. {{ item }}
  152. {% endblock %}
  153. </body>
  154. </html>"""
  155. )
  156. for tok in tokens:
  157. lineno, token_type, value = tok
  158. if token_type == "name" and value == "item":
  159. assert lineno == 5
  160. break
  161. class TestParser:
  162. def test_php_syntax(self, env):
  163. env = Environment("<?", "?>", "<?=", "?>", "<!--", "-->")
  164. tmpl = env.from_string(
  165. """\
  166. <!-- I'm a comment, I'm not interesting -->\
  167. <? for item in seq -?>
  168. <?= item ?>
  169. <?- endfor ?>"""
  170. )
  171. assert tmpl.render(seq=list(range(5))) == "01234"
  172. def test_erb_syntax(self, env):
  173. env = Environment("<%", "%>", "<%=", "%>", "<%#", "%>")
  174. tmpl = env.from_string(
  175. """\
  176. <%# I'm a comment, I'm not interesting %>\
  177. <% for item in seq -%>
  178. <%= item %>
  179. <%- endfor %>"""
  180. )
  181. assert tmpl.render(seq=list(range(5))) == "01234"
  182. def test_comment_syntax(self, env):
  183. env = Environment("<!--", "-->", "${", "}", "<!--#", "-->")
  184. tmpl = env.from_string(
  185. """\
  186. <!--# I'm a comment, I'm not interesting -->\
  187. <!-- for item in seq --->
  188. ${item}
  189. <!--- endfor -->"""
  190. )
  191. assert tmpl.render(seq=list(range(5))) == "01234"
  192. def test_balancing(self, env):
  193. tmpl = env.from_string("""{{{'foo':'bar'}.foo}}""")
  194. assert tmpl.render() == "bar"
  195. def test_start_comment(self, env):
  196. tmpl = env.from_string(
  197. """{# foo comment
  198. and bar comment #}
  199. {% macro blub() %}foo{% endmacro %}
  200. {{ blub() }}"""
  201. )
  202. assert tmpl.render().strip() == "foo"
  203. def test_line_syntax(self, env):
  204. env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%")
  205. tmpl = env.from_string(
  206. """\
  207. <%# regular comment %>
  208. % for item in seq:
  209. ${item}
  210. % endfor"""
  211. )
  212. assert [
  213. int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()
  214. ] == list(range(5))
  215. env = Environment("<%", "%>", "${", "}", "<%#", "%>", "%", "##")
  216. tmpl = env.from_string(
  217. """\
  218. <%# regular comment %>
  219. % for item in seq:
  220. ${item} ## the rest of the stuff
  221. % endfor"""
  222. )
  223. assert [
  224. int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()
  225. ] == list(range(5))
  226. def test_line_syntax_priority(self, env):
  227. # XXX: why is the whitespace there in front of the newline?
  228. env = Environment("{%", "%}", "${", "}", "/*", "*/", "##", "#")
  229. tmpl = env.from_string(
  230. """\
  231. /* ignore me.
  232. I'm a multiline comment */
  233. ## for item in seq:
  234. * ${item} # this is just extra stuff
  235. ## endfor"""
  236. )
  237. assert tmpl.render(seq=[1, 2]).strip() == "* 1\n* 2"
  238. env = Environment("{%", "%}", "${", "}", "/*", "*/", "#", "##")
  239. tmpl = env.from_string(
  240. """\
  241. /* ignore me.
  242. I'm a multiline comment */
  243. # for item in seq:
  244. * ${item} ## this is just extra stuff
  245. ## extra stuff i just want to ignore
  246. # endfor"""
  247. )
  248. assert tmpl.render(seq=[1, 2]).strip() == "* 1\n\n* 2"
  249. def test_error_messages(self, env):
  250. def assert_error(code, expected):
  251. with pytest.raises(TemplateSyntaxError, match=expected):
  252. Template(code)
  253. assert_error(
  254. "{% for item in seq %}...{% endif %}",
  255. "Encountered unknown tag 'endif'. Jinja was looking "
  256. "for the following tags: 'endfor' or 'else'. The "
  257. "innermost block that needs to be closed is 'for'.",
  258. )
  259. assert_error(
  260. "{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}",
  261. "Encountered unknown tag 'endfor'. Jinja was looking for "
  262. "the following tags: 'elif' or 'else' or 'endif'. The "
  263. "innermost block that needs to be closed is 'if'.",
  264. )
  265. assert_error(
  266. "{% if foo %}",
  267. "Unexpected end of template. Jinja was looking for the "
  268. "following tags: 'elif' or 'else' or 'endif'. The "
  269. "innermost block that needs to be closed is 'if'.",
  270. )
  271. assert_error(
  272. "{% for item in seq %}",
  273. "Unexpected end of template. Jinja was looking for the "
  274. "following tags: 'endfor' or 'else'. The innermost block "
  275. "that needs to be closed is 'for'.",
  276. )
  277. assert_error(
  278. "{% block foo-bar-baz %}",
  279. "Block names in Jinja have to be valid Python identifiers "
  280. "and may not contain hyphens, use an underscore instead.",
  281. )
  282. assert_error("{% unknown_tag %}", "Encountered unknown tag 'unknown_tag'.")
  283. class TestSyntax:
  284. def test_call(self, env):
  285. env = Environment()
  286. env.globals["foo"] = lambda a, b, c, e, g: a + b + c + e + g
  287. tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
  288. assert tmpl.render() == "abdfh"
  289. def test_slicing(self, env):
  290. tmpl = env.from_string("{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}")
  291. assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"
  292. def test_attr(self, env):
  293. tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
  294. assert tmpl.render(foo={"bar": 42}) == "42|42"
  295. def test_subscript(self, env):
  296. tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
  297. assert tmpl.render(foo=[0, 1, 2]) == "0|2"
  298. def test_tuple(self, env):
  299. tmpl = env.from_string("{{ () }}|{{ (1,) }}|{{ (1, 2) }}")
  300. assert tmpl.render() == "()|(1,)|(1, 2)"
  301. def test_math(self, env):
  302. tmpl = env.from_string("{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}")
  303. assert tmpl.render() == "1.5|8"
  304. def test_div(self, env):
  305. tmpl = env.from_string("{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}")
  306. assert tmpl.render() == "1|1.5|1"
  307. def test_unary(self, env):
  308. tmpl = env.from_string("{{ +3 }}|{{ -3 }}")
  309. assert tmpl.render() == "3|-3"
  310. def test_concat(self, env):
  311. tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
  312. assert tmpl.render() == "[1, 2]foo"
  313. @pytest.mark.parametrize(
  314. ("a", "op", "b"),
  315. [
  316. (1, ">", 0),
  317. (1, ">=", 1),
  318. (2, "<", 3),
  319. (3, "<=", 4),
  320. (4, "==", 4),
  321. (4, "!=", 5),
  322. ],
  323. )
  324. def test_compare(self, env, a, op, b):
  325. t = env.from_string(f"{{{{ {a} {op} {b} }}}}")
  326. assert t.render() == "True"
  327. def test_compare_parens(self, env):
  328. t = env.from_string("{{ i * (j < 5) }}")
  329. assert t.render(i=2, j=3) == "2"
  330. @pytest.mark.parametrize(
  331. ("src", "expect"),
  332. [
  333. ("{{ 4 < 2 < 3 }}", "False"),
  334. ("{{ a < b < c }}", "False"),
  335. ("{{ 4 > 2 > 3 }}", "False"),
  336. ("{{ a > b > c }}", "False"),
  337. ("{{ 4 > 2 < 3 }}", "True"),
  338. ("{{ a > b < c }}", "True"),
  339. ],
  340. )
  341. def test_compare_compound(self, env, src, expect):
  342. t = env.from_string(src)
  343. assert t.render(a=4, b=2, c=3) == expect
  344. def test_inop(self, env):
  345. tmpl = env.from_string("{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}")
  346. assert tmpl.render() == "True|False"
  347. @pytest.mark.parametrize("value", ("[]", "{}", "()"))
  348. def test_collection_literal(self, env, value):
  349. t = env.from_string(f"{{{{ {value} }}}}")
  350. assert t.render() == value
  351. @pytest.mark.parametrize(
  352. ("value", "expect"),
  353. (
  354. ("1", "1"),
  355. ("123", "123"),
  356. ("12_34_56", "123456"),
  357. ("1.2", "1.2"),
  358. ("34.56", "34.56"),
  359. ("3_4.5_6", "34.56"),
  360. ("1e0", "1.0"),
  361. ("10e1", "100.0"),
  362. ("2.5e100", "2.5e+100"),
  363. ("2.5e+100", "2.5e+100"),
  364. ("25.6e-10", "2.56e-09"),
  365. ("1_2.3_4e5_6", "1.234e+57"),
  366. ("0", "0"),
  367. ("0_00", "0"),
  368. ("0b1001_1111", "159"),
  369. ("0o123", "83"),
  370. ("0o1_23", "83"),
  371. ("0x123abc", "1194684"),
  372. ("0x12_3abc", "1194684"),
  373. ),
  374. )
  375. def test_numeric_literal(self, env, value, expect):
  376. t = env.from_string(f"{{{{ {value} }}}}")
  377. assert t.render() == expect
  378. def test_bool(self, env):
  379. tmpl = env.from_string(
  380. "{{ true and false }}|{{ false or true }}|{{ not false }}"
  381. )
  382. assert tmpl.render() == "False|True|True"
  383. def test_grouping(self, env):
  384. tmpl = env.from_string(
  385. "{{ (true and false) or (false and true) and not false }}"
  386. )
  387. assert tmpl.render() == "False"
  388. def test_django_attr(self, env):
  389. tmpl = env.from_string("{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}")
  390. assert tmpl.render() == "1|1"
  391. def test_conditional_expression(self, env):
  392. tmpl = env.from_string("""{{ 0 if true else 1 }}""")
  393. assert tmpl.render() == "0"
  394. def test_short_conditional_expression(self, env):
  395. tmpl = env.from_string("<{{ 1 if false }}>")
  396. assert tmpl.render() == "<>"
  397. tmpl = env.from_string("<{{ (1 if false).bar }}>")
  398. pytest.raises(UndefinedError, tmpl.render)
  399. def test_filter_priority(self, env):
  400. tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
  401. assert tmpl.render() == "FOOBAR"
  402. def test_function_calls(self, env):
  403. tests = [
  404. (True, "*foo, bar"),
  405. (True, "*foo, *bar"),
  406. (True, "**foo, *bar"),
  407. (True, "**foo, bar"),
  408. (True, "**foo, **bar"),
  409. (True, "**foo, bar=42"),
  410. (False, "foo, bar"),
  411. (False, "foo, bar=42"),
  412. (False, "foo, bar=23, *args"),
  413. (False, "foo, *args, bar=23"),
  414. (False, "a, b=c, *d, **e"),
  415. (False, "*foo, bar=42"),
  416. (False, "*foo, **bar"),
  417. (False, "*foo, bar=42, **baz"),
  418. (False, "foo, *args, bar=23, **baz"),
  419. ]
  420. for should_fail, sig in tests:
  421. if should_fail:
  422. with pytest.raises(TemplateSyntaxError):
  423. env.from_string(f"{{{{ foo({sig}) }}}}")
  424. else:
  425. env.from_string(f"foo({sig})")
  426. def test_tuple_expr(self, env):
  427. for tmpl in [
  428. "{{ () }}",
  429. "{{ (1, 2) }}",
  430. "{{ (1, 2,) }}",
  431. "{{ 1, }}",
  432. "{{ 1, 2 }}",
  433. "{% for foo, bar in seq %}...{% endfor %}",
  434. "{% for x in foo, bar %}...{% endfor %}",
  435. "{% for x in foo, %}...{% endfor %}",
  436. ]:
  437. assert env.from_string(tmpl)
  438. def test_trailing_comma(self, env):
  439. tmpl = env.from_string("{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}")
  440. assert tmpl.render().lower() == "(1, 2)|[1, 2]|{1: 2}"
  441. def test_block_end_name(self, env):
  442. env.from_string("{% block foo %}...{% endblock foo %}")
  443. pytest.raises(
  444. TemplateSyntaxError, env.from_string, "{% block x %}{% endblock y %}"
  445. )
  446. def test_constant_casing(self, env):
  447. for const in True, False, None:
  448. const = str(const)
  449. tmpl = env.from_string(
  450. f"{{{{ {const} }}}}|{{{{ {const.lower()} }}}}|{{{{ {const.upper()} }}}}"
  451. )
  452. assert tmpl.render() == f"{const}|{const}|"
  453. def test_test_chaining(self, env):
  454. pytest.raises(
  455. TemplateSyntaxError, env.from_string, "{{ foo is string is sequence }}"
  456. )
  457. assert env.from_string("{{ 42 is string or 42 is number }}").render() == "True"
  458. def test_string_concatenation(self, env):
  459. tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
  460. assert tmpl.render() == "foobarbaz"
  461. def test_notin(self, env):
  462. bar = range(100)
  463. tmpl = env.from_string("""{{ not 42 in bar }}""")
  464. assert tmpl.render(bar=bar) == "False"
  465. def test_operator_precedence(self, env):
  466. tmpl = env.from_string("""{{ 2 * 3 + 4 % 2 + 1 - 2 }}""")
  467. assert tmpl.render() == "5"
  468. def test_implicit_subscribed_tuple(self, env):
  469. class Foo:
  470. def __getitem__(self, x):
  471. return x
  472. t = env.from_string("{{ foo[1, 2] }}")
  473. assert t.render(foo=Foo()) == "(1, 2)"
  474. def test_raw2(self, env):
  475. tmpl = env.from_string("{% raw %}{{ FOO }} and {% BAR %}{% endraw %}")
  476. assert tmpl.render() == "{{ FOO }} and {% BAR %}"
  477. def test_const(self, env):
  478. tmpl = env.from_string(
  479. "{{ true }}|{{ false }}|{{ none }}|"
  480. "{{ none is defined }}|{{ missing is defined }}"
  481. )
  482. assert tmpl.render() == "True|False|None|True|False"
  483. def test_neg_filter_priority(self, env):
  484. node = env.parse("{{ -1|foo }}")
  485. assert isinstance(node.body[0].nodes[0], nodes.Filter)
  486. assert isinstance(node.body[0].nodes[0].node, nodes.Neg)
  487. def test_const_assign(self, env):
  488. constass1 = """{% set true = 42 %}"""
  489. constass2 = """{% for none in seq %}{% endfor %}"""
  490. for tmpl in constass1, constass2:
  491. pytest.raises(TemplateSyntaxError, env.from_string, tmpl)
  492. def test_localset(self, env):
  493. tmpl = env.from_string(
  494. """{% set foo = 0 %}\
  495. {% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
  496. {{ foo }}"""
  497. )
  498. assert tmpl.render() == "0"
  499. def test_parse_unary(self, env):
  500. tmpl = env.from_string('{{ -foo["bar"] }}')
  501. assert tmpl.render(foo={"bar": 42}) == "-42"
  502. tmpl = env.from_string('{{ -foo["bar"]|abs }}')
  503. assert tmpl.render(foo={"bar": 42}) == "42"
  504. class TestLstripBlocks:
  505. def test_lstrip(self, env):
  506. env = Environment(lstrip_blocks=True, trim_blocks=False)
  507. tmpl = env.from_string(""" {% if True %}\n {% endif %}""")
  508. assert tmpl.render() == "\n"
  509. def test_lstrip_trim(self, env):
  510. env = Environment(lstrip_blocks=True, trim_blocks=True)
  511. tmpl = env.from_string(""" {% if True %}\n {% endif %}""")
  512. assert tmpl.render() == ""
  513. def test_no_lstrip(self, env):
  514. env = Environment(lstrip_blocks=True, trim_blocks=False)
  515. tmpl = env.from_string(""" {%+ if True %}\n {%+ endif %}""")
  516. assert tmpl.render() == " \n "
  517. def test_lstrip_blocks_false_with_no_lstrip(self, env):
  518. # Test that + is a NOP (but does not cause an error) if lstrip_blocks=False
  519. env = Environment(lstrip_blocks=False, trim_blocks=False)
  520. tmpl = env.from_string(""" {% if True %}\n {% endif %}""")
  521. assert tmpl.render() == " \n "
  522. tmpl = env.from_string(""" {%+ if True %}\n {%+ endif %}""")
  523. assert tmpl.render() == " \n "
  524. def test_lstrip_endline(self, env):
  525. env = Environment(lstrip_blocks=True, trim_blocks=False)
  526. tmpl = env.from_string(""" hello{% if True %}\n goodbye{% endif %}""")
  527. assert tmpl.render() == " hello\n goodbye"
  528. def test_lstrip_inline(self, env):
  529. env = Environment(lstrip_blocks=True, trim_blocks=False)
  530. tmpl = env.from_string(""" {% if True %}hello {% endif %}""")
  531. assert tmpl.render() == "hello "
  532. def test_lstrip_nested(self, env):
  533. env = Environment(lstrip_blocks=True, trim_blocks=False)
  534. tmpl = env.from_string(
  535. """ {% if True %}a {% if True %}b {% endif %}c {% endif %}"""
  536. )
  537. assert tmpl.render() == "a b c "
  538. def test_lstrip_left_chars(self, env):
  539. env = Environment(lstrip_blocks=True, trim_blocks=False)
  540. tmpl = env.from_string(
  541. """ abc {% if True %}
  542. hello{% endif %}"""
  543. )
  544. assert tmpl.render() == " abc \n hello"
  545. def test_lstrip_embeded_strings(self, env):
  546. env = Environment(lstrip_blocks=True, trim_blocks=False)
  547. tmpl = env.from_string(""" {% set x = " {% str %} " %}{{ x }}""")
  548. assert tmpl.render() == " {% str %} "
  549. def test_lstrip_preserve_leading_newlines(self, env):
  550. env = Environment(lstrip_blocks=True, trim_blocks=False)
  551. tmpl = env.from_string("""\n\n\n{% set hello = 1 %}""")
  552. assert tmpl.render() == "\n\n\n"
  553. def test_lstrip_comment(self, env):
  554. env = Environment(lstrip_blocks=True, trim_blocks=False)
  555. tmpl = env.from_string(
  556. """ {# if True #}
  557. hello
  558. {#endif#}"""
  559. )
  560. assert tmpl.render() == "\nhello\n"
  561. def test_lstrip_angle_bracket_simple(self, env):
  562. env = Environment(
  563. "<%",
  564. "%>",
  565. "${",
  566. "}",
  567. "<%#",
  568. "%>",
  569. "%",
  570. "##",
  571. lstrip_blocks=True,
  572. trim_blocks=True,
  573. )
  574. tmpl = env.from_string(""" <% if True %>hello <% endif %>""")
  575. assert tmpl.render() == "hello "
  576. def test_lstrip_angle_bracket_comment(self, env):
  577. env = Environment(
  578. "<%",
  579. "%>",
  580. "${",
  581. "}",
  582. "<%#",
  583. "%>",
  584. "%",
  585. "##",
  586. lstrip_blocks=True,
  587. trim_blocks=True,
  588. )
  589. tmpl = env.from_string(""" <%# if True %>hello <%# endif %>""")
  590. assert tmpl.render() == "hello "
  591. def test_lstrip_angle_bracket(self, env):
  592. env = Environment(
  593. "<%",
  594. "%>",
  595. "${",
  596. "}",
  597. "<%#",
  598. "%>",
  599. "%",
  600. "##",
  601. lstrip_blocks=True,
  602. trim_blocks=True,
  603. )
  604. tmpl = env.from_string(
  605. """\
  606. <%# regular comment %>
  607. <% for item in seq %>
  608. ${item} ## the rest of the stuff
  609. <% endfor %>"""
  610. )
  611. assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5))
  612. def test_lstrip_angle_bracket_compact(self, env):
  613. env = Environment(
  614. "<%",
  615. "%>",
  616. "${",
  617. "}",
  618. "<%#",
  619. "%>",
  620. "%",
  621. "##",
  622. lstrip_blocks=True,
  623. trim_blocks=True,
  624. )
  625. tmpl = env.from_string(
  626. """\
  627. <%#regular comment%>
  628. <%for item in seq%>
  629. ${item} ## the rest of the stuff
  630. <%endfor%>"""
  631. )
  632. assert tmpl.render(seq=range(5)) == "".join(f"{x}\n" for x in range(5))
  633. def test_lstrip_blocks_outside_with_new_line(self):
  634. env = Environment(lstrip_blocks=True, trim_blocks=False)
  635. tmpl = env.from_string(
  636. " {% if kvs %}(\n"
  637. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n"
  638. " ){% endif %}"
  639. )
  640. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  641. assert out == "(\na=1 b=2 \n )"
  642. def test_lstrip_trim_blocks_outside_with_new_line(self):
  643. env = Environment(lstrip_blocks=True, trim_blocks=True)
  644. tmpl = env.from_string(
  645. " {% if kvs %}(\n"
  646. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n"
  647. " ){% endif %}"
  648. )
  649. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  650. assert out == "(\na=1 b=2 )"
  651. def test_lstrip_blocks_inside_with_new_line(self):
  652. env = Environment(lstrip_blocks=True, trim_blocks=False)
  653. tmpl = env.from_string(
  654. " ({% if kvs %}\n"
  655. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n"
  656. " {% endif %})"
  657. )
  658. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  659. assert out == " (\na=1 b=2 \n)"
  660. def test_lstrip_trim_blocks_inside_with_new_line(self):
  661. env = Environment(lstrip_blocks=True, trim_blocks=True)
  662. tmpl = env.from_string(
  663. " ({% if kvs %}\n"
  664. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}\n"
  665. " {% endif %})"
  666. )
  667. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  668. assert out == " (a=1 b=2 )"
  669. def test_lstrip_blocks_without_new_line(self):
  670. env = Environment(lstrip_blocks=True, trim_blocks=False)
  671. tmpl = env.from_string(
  672. " {% if kvs %}"
  673. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}"
  674. " {% endif %}"
  675. )
  676. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  677. assert out == " a=1 b=2 "
  678. def test_lstrip_trim_blocks_without_new_line(self):
  679. env = Environment(lstrip_blocks=True, trim_blocks=True)
  680. tmpl = env.from_string(
  681. " {% if kvs %}"
  682. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor %}"
  683. " {% endif %}"
  684. )
  685. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  686. assert out == " a=1 b=2 "
  687. def test_lstrip_blocks_consume_after_without_new_line(self):
  688. env = Environment(lstrip_blocks=True, trim_blocks=False)
  689. tmpl = env.from_string(
  690. " {% if kvs -%}"
  691. " {% for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}"
  692. " {% endif -%}"
  693. )
  694. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  695. assert out == "a=1 b=2 "
  696. def test_lstrip_trim_blocks_consume_before_without_new_line(self):
  697. env = Environment(lstrip_blocks=False, trim_blocks=False)
  698. tmpl = env.from_string(
  699. " {%- if kvs %}"
  700. " {%- for k, v in kvs %}{{ k }}={{ v }} {% endfor -%}"
  701. " {%- endif %}"
  702. )
  703. out = tmpl.render(kvs=[("a", 1), ("b", 2)])
  704. assert out == "a=1 b=2 "
  705. def test_lstrip_trim_blocks_comment(self):
  706. env = Environment(lstrip_blocks=True, trim_blocks=True)
  707. tmpl = env.from_string(" {# 1 space #}\n {# 2 spaces #} {# 4 spaces #}")
  708. out = tmpl.render()
  709. assert out == " " * 4
  710. def test_lstrip_trim_blocks_raw(self):
  711. env = Environment(lstrip_blocks=True, trim_blocks=True)
  712. tmpl = env.from_string("{{x}}\n{%- raw %} {% endraw -%}\n{{ y }}")
  713. out = tmpl.render(x=1, y=2)
  714. assert out == "1 2"
  715. def test_php_syntax_with_manual(self, env):
  716. env = Environment(
  717. "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True
  718. )
  719. tmpl = env.from_string(
  720. """\
  721. <!-- I'm a comment, I'm not interesting -->
  722. <? for item in seq -?>
  723. <?= item ?>
  724. <?- endfor ?>"""
  725. )
  726. assert tmpl.render(seq=range(5)) == "01234"
  727. def test_php_syntax(self, env):
  728. env = Environment(
  729. "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True
  730. )
  731. tmpl = env.from_string(
  732. """\
  733. <!-- I'm a comment, I'm not interesting -->
  734. <? for item in seq ?>
  735. <?= item ?>
  736. <? endfor ?>"""
  737. )
  738. assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5))
  739. def test_php_syntax_compact(self, env):
  740. env = Environment(
  741. "<?", "?>", "<?=", "?>", "<!--", "-->", lstrip_blocks=True, trim_blocks=True
  742. )
  743. tmpl = env.from_string(
  744. """\
  745. <!-- I'm a comment, I'm not interesting -->
  746. <?for item in seq?>
  747. <?=item?>
  748. <?endfor?>"""
  749. )
  750. assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5))
  751. def test_erb_syntax(self, env):
  752. env = Environment(
  753. "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True
  754. )
  755. tmpl = env.from_string(
  756. """\
  757. <%# I'm a comment, I'm not interesting %>
  758. <% for item in seq %>
  759. <%= item %>
  760. <% endfor %>
  761. """
  762. )
  763. assert tmpl.render(seq=range(5)) == "".join(f" {x}\n" for x in range(5))
  764. def test_erb_syntax_with_manual(self, env):
  765. env = Environment(
  766. "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True
  767. )
  768. tmpl = env.from_string(
  769. """\
  770. <%# I'm a comment, I'm not interesting %>
  771. <% for item in seq -%>
  772. <%= item %>
  773. <%- endfor %>"""
  774. )
  775. assert tmpl.render(seq=range(5)) == "01234"
  776. def test_erb_syntax_no_lstrip(self, env):
  777. env = Environment(
  778. "<%", "%>", "<%=", "%>", "<%#", "%>", lstrip_blocks=True, trim_blocks=True
  779. )
  780. tmpl = env.from_string(
  781. """\
  782. <%# I'm a comment, I'm not interesting %>
  783. <%+ for item in seq -%>
  784. <%= item %>
  785. <%- endfor %>"""
  786. )
  787. assert tmpl.render(seq=range(5)) == " 01234"
  788. def test_comment_syntax(self, env):
  789. env = Environment(
  790. "<!--",
  791. "-->",
  792. "${",
  793. "}",
  794. "<!--#",
  795. "-->",
  796. lstrip_blocks=True,
  797. trim_blocks=True,
  798. )
  799. tmpl = env.from_string(
  800. """\
  801. <!--# I'm a comment, I'm not interesting -->\
  802. <!-- for item in seq --->
  803. ${item}
  804. <!--- endfor -->"""
  805. )
  806. assert tmpl.render(seq=range(5)) == "01234"
  807. class TestTrimBlocks:
  808. def test_trim(self, env):
  809. env = Environment(trim_blocks=True, lstrip_blocks=False)
  810. tmpl = env.from_string(" {% if True %}\n {% endif %}")
  811. assert tmpl.render() == " "
  812. def test_no_trim(self, env):
  813. env = Environment(trim_blocks=True, lstrip_blocks=False)
  814. tmpl = env.from_string(" {% if True +%}\n {% endif %}")
  815. assert tmpl.render() == " \n "
  816. def test_no_trim_outer(self, env):
  817. env = Environment(trim_blocks=True, lstrip_blocks=False)
  818. tmpl = env.from_string("{% if True %}X{% endif +%}\nmore things")
  819. assert tmpl.render() == "X\nmore things"
  820. def test_lstrip_no_trim(self, env):
  821. env = Environment(trim_blocks=True, lstrip_blocks=True)
  822. tmpl = env.from_string(" {% if True +%}\n {% endif %}")
  823. assert tmpl.render() == "\n"
  824. def test_trim_blocks_false_with_no_trim(self, env):
  825. # Test that + is a NOP (but does not cause an error) if trim_blocks=False
  826. env = Environment(trim_blocks=False, lstrip_blocks=False)
  827. tmpl = env.from_string(" {% if True %}\n {% endif %}")
  828. assert tmpl.render() == " \n "
  829. tmpl = env.from_string(" {% if True +%}\n {% endif %}")
  830. assert tmpl.render() == " \n "
  831. tmpl = env.from_string(" {# comment #}\n ")
  832. assert tmpl.render() == " \n "
  833. tmpl = env.from_string(" {# comment +#}\n ")
  834. assert tmpl.render() == " \n "
  835. tmpl = env.from_string(" {% raw %}{% endraw %}\n ")
  836. assert tmpl.render() == " \n "
  837. tmpl = env.from_string(" {% raw %}{% endraw +%}\n ")
  838. assert tmpl.render() == " \n "
  839. def test_trim_nested(self, env):
  840. env = Environment(trim_blocks=True, lstrip_blocks=True)
  841. tmpl = env.from_string(
  842. " {% if True %}\na {% if True %}\nb {% endif %}\nc {% endif %}"
  843. )
  844. assert tmpl.render() == "a b c "
  845. def test_no_trim_nested(self, env):
  846. env = Environment(trim_blocks=True, lstrip_blocks=True)
  847. tmpl = env.from_string(
  848. " {% if True +%}\na {% if True +%}\nb {% endif +%}\nc {% endif %}"
  849. )
  850. assert tmpl.render() == "\na \nb \nc "
  851. def test_comment_trim(self, env):
  852. env = Environment(trim_blocks=True, lstrip_blocks=True)
  853. tmpl = env.from_string(""" {# comment #}\n\n """)
  854. assert tmpl.render() == "\n "
  855. def test_comment_no_trim(self, env):
  856. env = Environment(trim_blocks=True, lstrip_blocks=True)
  857. tmpl = env.from_string(""" {# comment +#}\n\n """)
  858. assert tmpl.render() == "\n\n "
  859. def test_multiple_comment_trim_lstrip(self, env):
  860. env = Environment(trim_blocks=True, lstrip_blocks=True)
  861. tmpl = env.from_string(
  862. " {# comment #}\n\n{# comment2 #}\n \n{# comment3 #}\n\n "
  863. )
  864. assert tmpl.render() == "\n \n\n "
  865. def test_multiple_comment_no_trim_lstrip(self, env):
  866. env = Environment(trim_blocks=True, lstrip_blocks=True)
  867. tmpl = env.from_string(
  868. " {# comment +#}\n\n{# comment2 +#}\n \n{# comment3 +#}\n\n "
  869. )
  870. assert tmpl.render() == "\n\n\n \n\n\n "
  871. def test_raw_trim_lstrip(self, env):
  872. env = Environment(trim_blocks=True, lstrip_blocks=True)
  873. tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw %}\n\n{{ y }}")
  874. assert tmpl.render(x=1, y=2) == "1\n\n\n2"
  875. def test_raw_no_trim_lstrip(self, env):
  876. env = Environment(trim_blocks=False, lstrip_blocks=True)
  877. tmpl = env.from_string("{{x}}{% raw %}\n\n {% endraw +%}\n\n{{ y }}")
  878. assert tmpl.render(x=1, y=2) == "1\n\n\n\n2"
  879. # raw blocks do not process inner text, so start tag cannot ignore trim
  880. with pytest.raises(TemplateSyntaxError):
  881. tmpl = env.from_string("{{x}}{% raw +%}\n\n {% endraw +%}\n\n{{ y }}")
  882. def test_no_trim_angle_bracket(self, env):
  883. env = Environment(
  884. "<%", "%>", "${", "}", "<%#", "%>", lstrip_blocks=True, trim_blocks=True
  885. )
  886. tmpl = env.from_string(" <% if True +%>\n\n <% endif %>")
  887. assert tmpl.render() == "\n\n"
  888. tmpl = env.from_string(" <%# comment +%>\n\n ")
  889. assert tmpl.render() == "\n\n "
  890. def test_no_trim_php_syntax(self, env):
  891. env = Environment(
  892. "<?",
  893. "?>",
  894. "<?=",
  895. "?>",
  896. "<!--",
  897. "-->",
  898. lstrip_blocks=False,
  899. trim_blocks=True,
  900. )
  901. tmpl = env.from_string(" <? if True +?>\n\n <? endif ?>")
  902. assert tmpl.render() == " \n\n "
  903. tmpl = env.from_string(" <!-- comment +-->\n\n ")
  904. assert tmpl.render() == " \n\n "