# HG changeset patch
# User cmlenz
# Date 1157473993 0
# Node ID f4943d2babda6db25fbbd2f0afbab6b949a11783
# Parent ebceef564b79fde4d12e36c7dbf9569e6ed2f7e5
Fix for #45 and #46: properly support assignment to nested tuples in `py:for` and `py:with` directives.
diff --git a/markup/template.py b/markup/template.py
--- a/markup/template.py
+++ b/markup/template.py
@@ -113,7 +113,7 @@
self.frames[0][key] = value
def _find(self, key, default=None):
- """Retrieve a given variable's value and frame it was found in.
+ """Retrieve a given variable's value and the frame it was found in.
Intented for internal use by directives.
"""
@@ -191,6 +191,23 @@
stream = directives[0](iter(stream), ctxt, directives[1:])
return stream
+def _assignment(ast):
+ """Takes the AST representation of an assignment, and returns a function
+ that applies the assignment of a given value to a dictionary.
+ """
+ def _names(node):
+ if isinstance(node, (compiler.ast.AssTuple, compiler.ast.Tuple)):
+ return tuple([_names(child) for child in node])
+ elif isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
+ return node.name
+ def _assign(data, value, names=_names(ast)):
+ if type(names) is tuple:
+ for idx in range(len(names)):
+ _assign(data, value[idx], names[idx])
+ else:
+ data[names] = value
+ return _assign
+
class AttrsDirective(Directive):
"""Implementation of the `py:attrs` template directive.
@@ -385,7 +402,7 @@
123
"""
- __slots__ = ['targets']
+ __slots__ = ['assign']
ATTRIBUTE = 'each'
@@ -393,8 +410,9 @@
if ' in ' not in value:
raise TemplateSyntaxError('"in" keyword missing in "for" directive',
filename, lineno, offset)
- targets, value = value.split(' in ', 1)
- self.targets = [str(name.strip()) for name in targets.split(',')]
+ assign, value = value.split(' in ', 1)
+ ast = compiler.parse(assign, 'exec')
+ self.assign = _assignment(ast.node.nodes[0].expr)
Directive.__init__(self, value.strip(), filename, lineno, offset)
def __call__(self, stream, ctxt, directives):
@@ -402,16 +420,11 @@
if iterable is None:
return
+ assign = self.assign
scope = {}
stream = list(stream)
- targets = self.targets
- single = len(targets) == 1
for item in iter(iterable):
- if single:
- scope[targets[0]] = item
- else:
- for idx, name in enumerate(targets):
- scope[name] = item[idx]
+ assign(scope, item)
ctxt.push(scope)
for event in _apply_directives(stream, ctxt, directives):
yield event
@@ -691,7 +704,7 @@
raise TemplateSyntaxError('only assignment allowed in '
'value of the "with" directive',
filename, lineno, offset)
- self.vars.append(([n.name for n in node.nodes],
+ self.vars.append(([_assignment(n) for n in node.nodes],
Expression(node.expr, filename, lineno)))
except SyntaxError, err:
err.msg += ' in expression "%s" of "%s" directive' % (value,
@@ -702,9 +715,10 @@
def __call__(self, stream, ctxt, directives):
frame = {}
ctxt.push(frame)
- for names, expr in self.vars:
+ for targets, expr in self.vars:
value = expr.evaluate(ctxt, nocall=True)
- frame.update(dict([(name, value) for name in names]))
+ for assign in targets:
+ assign(frame, value)
for event in _apply_directives(stream, ctxt, directives):
yield event
ctxt.pop()
diff --git a/markup/tests/template.py b/markup/tests/template.py
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -381,6 +381,34 @@
5
""", str(tmpl.generate(items=range(1, 6))))
+ def test_multi_assignment(self):
+ """
+ Verify that assignment to tuples works correctly.
+ """
+ tmpl = Template("""
+
+ key=$k, value=$v
+
+ """)
+ self.assertEqual("""
+ key=a, value=1
+ key=b, value=2
+ """, str(tmpl.generate(items=dict(a=1, b=2).items())))
+
+ def test_nested_assignment(self):
+ """
+ Verify that assignment to nested tuples works correctly.
+ """
+ tmpl = Template("""
+
+ $idx: key=$k, value=$v
+
+ """)
+ self.assertEqual("""
+ 0: key=a, value=1
+ 1: key=b, value=2
+ """, str(tmpl.generate(items=enumerate(dict(a=1, b=2).items()))))
+
class IfDirectiveTestCase(unittest.TestCase):
"""Tests for the `py:if` template directive."""
@@ -710,6 +738,14 @@
1 1 1
""", str(tmpl.generate(x=42)))
+ def test_nested_vars_single_assignment(self):
+ tmpl = Template("""""")
+ self.assertEqual("""
+ 1 2 3
+
""", str(tmpl.generate(x=42)))
+
def test_multiple_vars_trailing_semicolon(self):
tmpl = Template("""