changeset 154:8bd5c8cd33e0 trunk

* Make sure `py:def` macros don't go out of scope if they are defined inside another directive. * Cleaned up the `DefDirective` implementation a bit.
author cmlenz
date Wed, 16 Aug 2006 14:04:30 +0000
parents fc2ff46d1ae3
children 9a5aedda1099
files markup/template.py markup/tests/template.py
diffstat 2 files changed, 43 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/markup/template.py
+++ b/markup/template.py
@@ -290,7 +290,7 @@
       </p>
     </div>
     """
-    __slots__ = ['name', 'args', 'defaults', 'stream', 'directives']
+    __slots__ = ['name', 'args', 'defaults']
 
     ATTRIBUTE = 'function'
 
@@ -309,27 +309,33 @@
                     self.args.append(arg.name)
         else:
             self.name = ast.name
-        self.stream, self.directives = [], []
 
     def __call__(self, stream, ctxt, directives):
-        self.stream = list(stream)
-        self.directives = directives
-        ctxt[self.name] = lambda *args, **kwargs: self._exec(ctxt, *args,
-                                                             **kwargs)
-        return []
+        stream = list(stream)
 
-    def _exec(self, ctxt, *args, **kwargs):
-        scope = {}
-        args = list(args) # make mutable
-        for name in self.args:
-            if args:
-                scope[name] = args.pop(0)
-            else:
-                scope[name] = kwargs.pop(name, self.defaults.get(name))
-        ctxt.push(scope)
-        for event in _apply_directives(self.stream, ctxt, self.directives):
-            yield event
-        ctxt.pop()
+        def function(*args, **kwargs):
+            scope = {}
+            args = list(args) # make mutable
+            for name in self.args:
+                if args:
+                    scope[name] = args.pop(0)
+                else:
+                    scope[name] = kwargs.pop(name, self.defaults.get(name))
+            ctxt.push(scope)
+            for event in _apply_directives(stream, ctxt, directives):
+                yield event
+            ctxt.pop()
+        try:
+            function.__name__ = self.name
+        except TypeError:
+            # Function name can't be set in Python 2.3 
+            pass
+
+        # Store the function reference in the bottom context frame so that it
+        # doesn't get popped off before processing the template has finished
+        ctxt.frames[-1][self.name] = function
+
+        return []
 
 
 class ForDirective(Directive):
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -185,6 +185,24 @@
             <b>foo</b>
         </doc>""", str(tmpl.generate()))
 
+    def test_nested_defs(self):
+        """
+        Verify that a template function defined inside a conditional block can
+        be called from outside that block.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:if test="semantic">
+            <strong py:def="echo(what)">${what}</strong>
+          </py:if>
+          <py:if test="not semantic">
+            <b py:def="echo(what)">${what}</b>
+          </py:if>
+          ${echo('foo')}
+        </doc>""")
+        self.assertEqual("""<doc>
+          <strong>foo</strong>
+        </doc>""", str(tmpl.generate(semantic=True)))
+
 
 class ForDirectiveTestCase(unittest.TestCase):
     """Tests for the `py:for` template directive."""
Copyright (C) 2012-2017 Edgewall Software