changeset 220:f4943d2babda trunk

Fix for #45 and #46: properly support assignment to nested tuples in `py:for` and `py:with` directives.
author cmlenz
date Tue, 05 Sep 2006 16:33:13 +0000
parents ebceef564b79
children 04d260487b9a
files markup/template.py markup/tests/template.py
diffstat 2 files changed, 64 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- 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 @@
       <li>1</li><li>2</li><li>3</li>
     </ul>
     """
-    __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()
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -381,6 +381,34 @@
             <b>5</b>
         </doc>""", str(tmpl.generate(items=range(1, 6))))
 
+    def test_multi_assignment(self):
+        """
+        Verify that assignment to tuples works correctly.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:for each="k, v in items">
+            <p>key=$k, value=$v</p>
+          </py:for>
+        </doc>""")
+        self.assertEqual("""<doc>
+            <p>key=a, value=1</p>
+            <p>key=b, value=2</p>
+        </doc>""", 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("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:for each="idx, (k, v) in items">
+            <p>$idx: key=$k, value=$v</p>
+          </py:for>
+        </doc>""")
+        self.assertEqual("""<doc>
+            <p>0: key=a, value=1</p>
+            <p>1: key=b, value=2</p>
+        </doc>""", 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
         </div>""", str(tmpl.generate(x=42)))
 
+    def test_nested_vars_single_assignment(self):
+        tmpl = Template("""<div xmlns:py="http://markup.edgewall.org/">
+          <py:with vars="x, (y, z) = (1, (2, 3))">${x} ${y} ${z}</py:with>
+        </div>""")
+        self.assertEqual("""<div>
+          1 2 3
+        </div>""", str(tmpl.generate(x=42)))
+
     def test_multiple_vars_trailing_semicolon(self):
         tmpl = Template("""<div xmlns:py="http://markup.edgewall.org/">
           <py:with vars="x = x * 2; y = x / 2;">${x} ${y}</py:with>
Copyright (C) 2012-2017 Edgewall Software