# 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 @@
  • 1
  • 2
  • 3
  • """ - __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("""
    + ${x} ${y} ${z} +
    """) + self.assertEqual("""
    + 1 2 3 +
    """, str(tmpl.generate(x=42))) + def test_multiple_vars_trailing_semicolon(self): tmpl = Template("""
    ${x} ${y}