changeset 586:5413c9d95db1

Fixes for nonlocal variable access in code blocks, as well as nested function and class definitions.
author cmlenz
date Wed, 08 Aug 2007 22:21:21 +0000
parents e0d57ab9b0be
children c079c5c4f6f4
files genshi/template/eval.py genshi/template/tests/eval.py
diffstat 2 files changed, 55 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -638,7 +638,7 @@
     """
 
     def __init__(self):
-        self.locals = [CONSTANTS, set()]
+        self.locals = [CONSTANTS]
 
     def visitConst(self, node):
         if isinstance(node.value, str):
@@ -649,7 +649,7 @@
         return node
 
     def visitAssName(self, node):
-        if self.locals:
+        if len(self.locals) > 1:
             self.locals[-1].add(node.name)
         return node
 
@@ -670,6 +670,8 @@
             return ASTTransformer.visitAugAssign(self, node)
 
     def visitClass(self, node):
+        if len(self.locals) > 1:
+            self.locals[-1].add(node.name)
         self.locals.append(set())
         try:
             return ASTTransformer.visitClass(self, node)
@@ -684,6 +686,8 @@
             self.locals.pop()
 
     def visitFunction(self, node):
+        if len(self.locals) > 1:
+            self.locals[-1].add(node.name)
         self.locals.append(set(node.argnames))
         try:
             return ASTTransformer.visitFunction(self, node)
@@ -714,12 +718,11 @@
     def visitName(self, node):
         # If the name refers to a local inside a lambda, list comprehension, or
         # generator expression, leave it alone
-        for frame in self.locals:
-            if node.name in frame:
-                return node
-        # Otherwise, translate the name ref into a context lookup
-        func_args = [ast.Name('data'), ast.Const(node.name)]
-        return ast.CallFunc(ast.Name('_lookup_name'), func_args)
+        if node.name not in flatten(self.locals):
+            # Otherwise, translate the name ref into a context lookup
+            func_args = [ast.Name('data'), ast.Const(node.name)]
+            node = ast.CallFunc(ast.Name('_lookup_name'), func_args)
+        return node
 
 
 class ExpressionASTTransformer(TemplateASTTransformer):
--- a/genshi/template/tests/eval.py
+++ b/genshi/template/tests/eval.py
@@ -449,7 +449,8 @@
         self.assertEqual(None, data['donothing']())
 
     def test_def_with_multiple_statements(self):
-        suite = Suite("""def donothing():
+        suite = Suite("""
+def donothing():
     if True:
         return foo
 """)
@@ -458,6 +459,35 @@
         assert 'donothing' in data
         self.assertEqual('bar', data['donothing']())
 
+    def test_def_using_nonlocal(self):
+        suite = Suite("""
+values = []
+def add(value):
+    if value not in values:
+        values.append(value)
+add('foo')
+add('bar')
+""")
+        data = {}
+        suite.execute(data)
+        self.assertEqual(['foo', 'bar'], data['values'])
+
+    def test_def_nested(self):
+        suite = Suite("""
+def doit():
+    values = []
+    def add(value):
+        if value not in values:
+            values.append(value)
+    add('foo')
+    add('bar')
+    return values
+x = doit()
+""")
+        data = {}
+        suite.execute(data)
+        self.assertEqual(['foo', 'bar'], data['x'])
+
     def test_delete(self):
         suite = Suite("""foo = 42
 del foo
@@ -472,6 +502,19 @@
         suite.execute(data)
         assert 'plain' in data
 
+    def test_class_in_def(self):
+        suite = Suite("""
+def create():
+    class Foobar(object):
+        def __str__(self):
+            return 'foobar'
+    return Foobar()
+x = create()
+""")
+        data = {}
+        suite.execute(data)
+        self.assertEqual('foobar', str(data['x']))
+
     def test_class_with_methods(self):
         suite = Suite("""class plain(object):
     def donothing():
Copyright (C) 2012-2017 Edgewall Software