test_lexnparse.py 30 KB

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