blob: bdfbaea86a238ba6f7ccdf8fc23adc15916f8f4b [file] [log] [blame]
import re
import unittest
from mako.template import Template
from mako.lookup import TemplateLookup
from mako.codegen import (
_FOR_LOOP, mangle_mako_loop, LoopVariable
)
from mako.runtime import LoopStack, LoopContext
from mako import exceptions
from test import assert_raises_message
from test import TemplateTest, eq_
from test.util import flatten_result, result_lines
class TestLoop(unittest.TestCase):
def test__FOR_LOOP(self):
for statement, target_list, expression_list in (
('for x in y:', 'x', 'y'),
('for x, y in z:', 'x, y', 'z'),
('for (x,y) in z:', '(x,y)', 'z'),
('for ( x, y, z) in a:', '( x, y, z)', 'a'),
('for x in [1, 2, 3]:', 'x', '[1, 2, 3]'),
('for x in "spam":', 'x', '"spam"'),
('for k,v in dict(a=1,b=2).items():', 'k,v',
'dict(a=1,b=2).items()'),
('for x in [y+1 for y in [1, 2, 3]]:', 'x',
'[y+1 for y in [1, 2, 3]]')
):
match = _FOR_LOOP.match(statement)
assert match and match.groups() == (target_list, expression_list)
def test_no_loop(self):
template = Template("""% for x in 'spam':
${x}
% endfor""")
code = template.code
assert not re.match(r"loop = __M_loop._enter\(:", code), "No need to "\
"generate a loop context if the loop variable wasn't accessed"
print(template.render())
def test_loop_demo(self):
template = Template("""x|index|reverse_index|first|last|cycle|even|odd
% for x in 'ham':
${x}|${loop.index}|${loop.reverse_index}|${loop.first}|${loop.last}|${loop.cycle('even', 'odd')}|${loop.even}|${loop.odd}
% endfor""")
expected = [
"x|index|reverse_index|first|last|cycle|even|odd",
"h|0|2|True|False|even|True|False",
"a|1|1|False|False|odd|False|True",
"m|2|0|False|True|even|True|False"
]
code = template.code
assert "loop = __M_loop._enter(" in code, "Generated a loop context since "\
"the loop variable was accessed"
rendered = template.render()
print(rendered)
for line in expected:
assert line in rendered, "Loop variables give information about "\
"the progress of the loop"
def test_nested_loops(self):
template = Template("""% for x in 'ab':
${x} ${loop.index} <- start in outer loop
% for y in [0, 1]:
${y} ${loop.index} <- go to inner loop
% endfor
${x} ${loop.index} <- back to outer loop
% endfor""")
code = template.code
rendered = template.render()
expected = [
"a 0 <- start in outer loop",
"0 0 <- go to inner loop",
"1 1 <- go to inner loop",
"a 0 <- back to outer loop",
"b 1 <- start in outer loop",
"0 0 <- go to inner loop",
"1 1 <- go to inner loop",
"b 1 <- back to outer loop",
]
for line in expected:
assert line in rendered, "The LoopStack allows you to take "\
"advantage of the loop variable even in embedded loops"
def test_parent_loops(self):
template = Template("""% for x in 'ab':
${x} ${loop.index} <- outer loop
% for y in [0, 1]:
${y} ${loop.index} <- inner loop
${x} ${loop.parent.index} <- parent loop
% endfor
${x} ${loop.index} <- outer loop
% endfor""")
code = template.code
rendered = template.render()
expected = [
"a 0 <- outer loop",
"a 0 <- parent loop",
"b 1 <- outer loop",
"b 1 <- parent loop"
]
for line in expected:
print(code)
assert line in rendered, "The parent attribute of a loop gives "\
"you the previous loop context in the stack"
def test_out_of_context_access(self):
template = Template("""${loop.index}""")
assert_raises_message(
exceptions.RuntimeException,
"No loop context is established",
template.render
)
class TestLoopStack(unittest.TestCase):
def setUp(self):
self.stack = LoopStack()
self.bottom = 'spam'
self.stack.stack = [self.bottom]
def test_enter(self):
iterable = 'ham'
s = self.stack._enter(iterable)
assert s is self.stack.stack[-1], "Calling the stack with an iterable returns "\
"the stack"
assert iterable == self.stack.stack[-1]._iterable, "and pushes the "\
"iterable on the top of the stack"
def test__top(self):
assert self.bottom == self.stack._top, "_top returns the last item "\
"on the stack"
def test__pop(self):
assert len(self.stack.stack) == 1
top = self.stack._pop()
assert top == self.bottom
assert len(self.stack.stack) == 0
def test__push(self):
assert len(self.stack.stack) == 1
iterable = 'ham'
self.stack._push(iterable)
assert len(self.stack.stack) == 2
assert iterable is self.stack._top._iterable
def test_exit(self):
iterable = 'ham'
self.stack._enter(iterable)
before = len(self.stack.stack)
self.stack._exit()
after = len(self.stack.stack)
assert before == (after + 1), "Exiting a context pops the stack"
class TestLoopContext(unittest.TestCase):
def setUp(self):
self.iterable = [1, 2, 3]
self.ctx = LoopContext(self.iterable)
def test___len__(self):
assert len(self.iterable) == len(self.ctx), "The LoopContext is the "\
"same length as the iterable"
def test_index(self):
expected = tuple(range(len(self.iterable)))
actual = tuple(self.ctx.index for i in self.ctx)
assert expected == actual, "The index is consistent with the current "\
"iteration count"
def test_reverse_index(self):
length = len(self.iterable)
expected = tuple([length-i-1 for i in range(length)])
actual = tuple(self.ctx.reverse_index for i in self.ctx)
print(expected, actual)
assert expected == actual, "The reverse_index is the number of "\
"iterations until the end"
def test_first(self):
expected = (True, False, False)
actual = tuple(self.ctx.first for i in self.ctx)
assert expected == actual, "first is only true on the first iteration"
def test_last(self):
expected = (False, False, True)
actual = tuple(self.ctx.last for i in self.ctx)
assert expected == actual, "last is only true on the last iteration"
def test_even(self):
expected = (True, False, True)
actual = tuple(self.ctx.even for i in self.ctx)
assert expected == actual, "even is true on even iterations"
def test_odd(self):
expected = (False, True, False)
actual = tuple(self.ctx.odd for i in self.ctx)
assert expected == actual, "odd is true on odd iterations"
def test_cycle(self):
expected = ('a', 'b', 'a')
actual = tuple(self.ctx.cycle('a', 'b') for i in self.ctx)
assert expected == actual, "cycle endlessly cycles through the values"
class TestLoopFlags(TemplateTest):
def test_loop_disabled_template(self):
self._do_memory_test(
"""
the loop: ${loop}
""",
"the loop: hi",
template_args=dict(loop='hi'),
filters=flatten_result,
enable_loop=False
)
def test_loop_disabled_lookup(self):
l = TemplateLookup(enable_loop=False)
l.put_string("x",
"""
the loop: ${loop}
"""
)
self._do_test(
l.get_template("x"),
"the loop: hi",
template_args=dict(loop='hi'),
filters=flatten_result,
)
def test_loop_disabled_override_template(self):
self._do_memory_test(
"""
<%page enable_loop="True" />
% for i in (1, 2, 3):
${i} ${loop.index}
% endfor
""",
"1 0 2 1 3 2",
template_args=dict(loop='hi'),
filters=flatten_result,
enable_loop=False
)
def test_loop_disabled_override_lookup(self):
l = TemplateLookup(enable_loop=False)
l.put_string("x",
"""
<%page enable_loop="True" />
% for i in (1, 2, 3):
${i} ${loop.index}
% endfor
"""
)
self._do_test(
l.get_template("x"),
"1 0 2 1 3 2",
template_args=dict(loop='hi'),
filters=flatten_result,
)
def test_loop_enabled_override_template(self):
self._do_memory_test(
"""
<%page enable_loop="True" />
% for i in (1, 2, 3):
${i} ${loop.index}
% endfor
""",
"1 0 2 1 3 2",
template_args=dict(),
filters=flatten_result,
)
def test_loop_enabled_override_lookup(self):
l = TemplateLookup()
l.put_string("x",
"""
<%page enable_loop="True" />
% for i in (1, 2, 3):
${i} ${loop.index}
% endfor
"""
)
self._do_test(
l.get_template("x"),
"1 0 2 1 3 2",
template_args=dict(),
filters=flatten_result,
)