changeset 474:b4431a500d0b stable-0.4.x

Ported [573] to 0.4.x.
author cmlenz
date Fri, 11 May 2007 11:34:00 +0000
parents 4504291f0e16
children 3068d554de19
files ChangeLog genshi/template/eval.py genshi/template/tests/eval.py
diffstat 3 files changed, 144 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -19,6 +19,12 @@
    argument.
  * The `HTMLFormFiller` stream filter no longer alters form elements for which
    the data element contains no corresponding item.
+ * Code in `<?python ?>` processing instructions no longer gets the special
+   treatment as Python code in template expressions, i.e. item and attribute
+   access are no longer interchangeable (which was broken in a number of ways
+   anyway, see ticket #113). This change does not affect expressions.
+ * Numerous fixes for the execution of Python code in `<?python ?>` processing
+   instructions (tickets #113 and #114).
 
 
 Version 0.4
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -140,7 +140,7 @@
     """Executes Python statements used in templates.
 
     >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
-    >>> Suite('foo = dict.some').execute(data)
+    >>> Suite("foo = dict['some']").execute(data)
     >>> data['foo']
     'thing'
     """
@@ -361,7 +361,8 @@
     return parse(source, mode)
 
 def _compile(node, source=None, mode='eval', filename=None, lineno=-1):
-    tree = TemplateASTTransformer().visit(node)
+    xform = {'eval': ExpressionASTTransformer}.get(mode, TemplateASTTransformer)
+    tree = xform().visit(node)
     if isinstance(filename, unicode):
         # unicode file names not allowed for code objects
         filename = filename.encode('utf-8', 'replace')
@@ -397,17 +398,15 @@
     Every visitor method can be overridden to return an AST node that has been
     altered or replaced in some way.
     """
-    _visitors = {}
 
     def visit(self, node):
         if node is None:
             return None
-        v = self._visitors.get(node.__class__)
-        if not v:
-            v = getattr(self.__class__, 'visit%s' % node.__class__.__name__,
-                        self.__class__._visitDefault)
-            self._visitors[node.__class__] = v
-        return v(self, node)
+        if type(node) is tuple:
+            return tuple([self.visit(n) for n in node])
+        visitor = getattr(self, 'visit%s' % node.__class__.__name__,
+                          self._visitDefault)
+        return visitor(node)
 
     def _visitDefault(self, node):
         return node
@@ -475,10 +474,25 @@
         node.expr = self.visit(node.expr)
         return node
 
+    def visitAssAttr(self, node):
+        node.expr = self.visit(node.expr)
+        return node
+
+    def visitAugAssign(self, node):
+        node.node = self.visit(node.node)
+        node.expr = self.visit(node.expr)
+        return node
+
     def visitDecorators(self, node):
         node.nodes = [self.visit(x) for x in node.nodes]
         return node
 
+    def visitExec(self, node):
+        node.expr = self.visit(node.expr)
+        node.locals = self.visit(node.locals)
+        node.globals = self.visit(node.globals)
+        return node
+
     def visitFor(self, node):
         node.assign = self.visit(node.assign)
         node.list = self.visit(node.list)
@@ -503,6 +517,10 @@
         node.expr3 = self.visit(node.expr3)
         return node
 
+    def visitReturn(self, node):
+        node.value = self.visit(node.value)
+        return node
+
     def visitTryExcept(self, node):
         node.body = self.visit(node.body)
         node.handlers = self.visit(node.handlers)
@@ -536,7 +554,7 @@
         node.nodes = [self.visit(x) for x in node.nodes]
         return node
     visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp
-    visitAssTuple = _visitBoolOp
+    visitAssTuple = visitAssList = _visitBoolOp
 
     def _visitBinOp(self, node):
         node.left = self.visit(node.left)
@@ -651,6 +669,21 @@
             self.locals[-1].add(node.name)
         return node
 
+    def visitAugAssign(self, node):
+        if isinstance(node.node, ast.Name):
+            name = node.node.name
+            node.node = ast.Subscript(ast.Name('data'), 'OP_APPLY',
+                                      [ast.Const(name)])
+            node.expr = self.visit(node.expr)
+            return ast.If([
+                (ast.Compare(ast.Const(name), [('in', ast.Name('data'))]),
+                 ast.Stmt([node]))],
+                ast.Stmt([ast.Raise(ast.CallFunc(ast.Name('UndefinedError'),
+                                                 [ast.Const(name)]),
+                                    None, None)]))
+        else:
+            return ASTTransformer.visitAugAssign(self, node)
+
     def visitClass(self, node):
         self.locals.append(set())
         node = ASTTransformer.visitClass(self, node)
@@ -675,12 +708,6 @@
         self.locals.pop()
         return node
 
-    def visitGetattr(self, node):
-        return ast.CallFunc(ast.Name('_lookup_attr'), [
-            ast.Name('data'), self.visit(node.expr),
-            ast.Const(node.attrname)
-        ])
-
     def visitLambda(self, node):
         self.locals.append(set(flatten(node.argnames)))
         node = ASTTransformer.visitLambda(self, node)
@@ -703,6 +730,18 @@
         func_args = [ast.Name('data'), ast.Const(node.name)]
         return ast.CallFunc(ast.Name('_lookup_name'), func_args)
 
+
+class ExpressionASTTransformer(TemplateASTTransformer):
+    """Concrete AST transformer that implements the AST transformations needed
+    for code embedded in templates.
+    """
+
+    def visitGetattr(self, node):
+        return ast.CallFunc(ast.Name('_lookup_attr'), [
+            ast.Name('data'), self.visit(node.expr),
+            ast.Const(node.attrname)
+        ])
+
     def visitSubscript(self, node):
         return ast.CallFunc(ast.Name('_lookup_item'), [
             ast.Name('data'), self.visit(node.expr),
--- a/genshi/template/tests/eval.py
+++ b/genshi/template/tests/eval.py
@@ -379,18 +379,19 @@
             expr.evaluate({'something': Something()})
             self.fail('Expected UndefinedError')
         except UndefinedError, e:
+            self.assertEqual('<Something> has no member named "nil"', str(e))
             exc_type, exc_value, exc_traceback = sys.exc_info()
+            search_string = "<Expression 'something.nil'>"
             frame = exc_traceback.tb_next
-            frames = []
             while frame.tb_next:
                 frame = frame.tb_next
-                frames.append(frame)
-            self.assertEqual('<Something> has no member named "nil"', str(e))
-            self.assertEqual("<Expression 'something.nil'>",
-                             frames[-3].tb_frame.f_code.co_name)
-            self.assertEqual('index.html',
-                             frames[-3].tb_frame.f_code.co_filename)
-            self.assertEqual(50, frames[-3].tb_lineno)
+                code = frame.tb_frame.f_code
+                if code.co_name == search_string:
+                    break
+            else:
+                self.fail("never found the frame I was looking for")
+            self.assertEqual('index.html', code.co_filename)
+            self.assertEqual(50, frame.tb_lineno)
 
     def test_error_getitem_undefined_string(self):
         class Something(object):
@@ -402,18 +403,19 @@
             expr.evaluate({'something': Something()})
             self.fail('Expected UndefinedError')
         except UndefinedError, e:
+            self.assertEqual('<Something> has no member named "nil"', str(e))
             exc_type, exc_value, exc_traceback = sys.exc_info()
+            search_string = '''<Expression 'something["nil"]'>'''
             frame = exc_traceback.tb_next
-            frames = []
             while frame.tb_next:
                 frame = frame.tb_next
-                frames.append(frame)
-            self.assertEqual('<Something> has no member named "nil"', str(e))
-            self.assertEqual('''<Expression 'something["nil"]'>''',
-                             frames[-3].tb_frame.f_code.co_name)
-            self.assertEqual('index.html',
-                             frames[-3].tb_frame.f_code.co_filename)
-            self.assertEqual(50, frames[-3].tb_lineno)
+                code = frame.tb_frame.f_code
+                if code.co_name == search_string:
+                    break
+            else:
+                self.fail("never found the frame I was looking for")
+            self.assertEqual('index.html', code.co_filename)
+            self.assertEqual(50, frame.tb_lineno)
 
 
 class SuiteTestCase(unittest.TestCase):
@@ -431,6 +433,16 @@
         assert 'donothing' in data
         self.assertEqual(None, data['donothing']())
 
+    def test_def_with_multiple_statements(self):
+        suite = Suite("""def donothing():
+    if True:
+        return foo
+""")
+        data = {'foo': 'bar'}
+        suite.execute(data)
+        assert 'donothing' in data
+        self.assertEqual('bar', data['donothing']())
+
     def test_delete(self):
         suite = Suite("""foo = 42
 del foo
@@ -504,6 +516,61 @@
         suite.execute(data)
         self.assertEqual(4, data['x'])
 
+    def test_augmented_attribute_assignment(self):
+        suite = Suite("d['k'] += 42")
+        d = {"k": 1}
+        suite.execute({"d": d})
+        self.assertEqual(43, d["k"])
+
+    def test_local_augmented_assign(self):
+        Suite("x = 1; x += 42; assert x == 43").execute({})
+
+    def test_assign_in_list(self):
+        suite = Suite("[d['k']] = 'foo',; assert d['k'] == 'foo'")
+        d = {"k": "bar"}
+        suite.execute({"d": d})
+        self.assertEqual("foo", d["k"])
+
+    def test_exec(self):
+        suite = Suite("x = 1; exec d['k']; assert x == 42, x")
+        suite.execute({"d": {"k": "x = 42"}})
+
+    def test_return(self):
+        suite = Suite("""
+def f():
+    return v
+
+assert f() == 42
+""")
+        suite.execute({"v": 42})
+
+    def test_assign_to_dict_item(self):
+        suite = Suite("d['k'] = 'foo'")
+        data = {'d': {}}
+        suite.execute(data)
+        self.assertEqual('foo', data['d']['k'])
+
+    def test_assign_to_attribute(self):
+        class Something(object): pass
+        something = Something()
+        suite = Suite("obj.attr = 'foo'")
+        data = {"obj": something}
+        suite.execute(data)
+        self.assertEqual('foo', something.attr)
+
+    def test_delattr(self):
+        class Something(object):
+            def __init__(self):
+                self.attr = 'foo'
+        obj = Something()
+        Suite("del obj.attr").execute({'obj': obj})
+        self.failIf(hasattr(obj, 'attr'))
+
+    def test_delitem(self):
+        d = {'k': 'foo'}
+        Suite("del d['k']").execute({'d': d})
+        self.failIf('k' in d, `d`)
+
 
 def suite():
     suite = unittest.TestSuite()
Copyright (C) 2012-2017 Edgewall Software