diff genshi/template/eval.py @ 934:7c9ec79caedc

Merge r1142 from py3k: add support for python 3 to genshi.template expression evaluator: * add support for python 3 AST: * AST for raise has changed in Python 3. * Python 3 adds AST nodes for individual arguments and Bytes. * use genshi.compat functions for dealing with code objects. * do not coerce byte strings to unicode in Python 3 ASTTransformer. * replace doctests that reply on exception names with uglier but more compatible try:.. except:.. doctest * handle filename preferences of Python 2 and 3 (2 prefers bytes, 3 prefers unicode). * ifilter is gone from itertools in Python 3 so use repeat for tests instead.
author hodgestar
date Fri, 18 Mar 2011 09:15:29 +0000
parents 85e4678337cf
children
line wrap: on
line diff
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -24,6 +24,8 @@
 from genshi.template.base import TemplateRuntimeError
 from genshi.util import flatten
 
+from genshi.compat import get_code_params, build_code_chunk, IS_PYTHON2
+
 __all__ = ['Code', 'Expression', 'Suite', 'LenientLookup', 'StrictLookup',
            'Undefined', 'UndefinedError']
 __docformat__ = 'restructuredtext en'
@@ -98,10 +100,7 @@
     def __getstate__(self):
         state = {'source': self.source, 'ast': self.ast,
                  'lookup': self._globals.im_self}
-        c = self.code
-        state['code'] = (c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code,
-                         c.co_consts, c.co_names, c.co_varnames, c.co_filename,
-                         c.co_name, c.co_firstlineno, c.co_lnotab, (), ())
+        state['code'] = get_code_params(self.code)
         return state
 
     def __setstate__(self, state):
@@ -236,15 +235,17 @@
     of that variable, will raise an exception that includes the name used to
     reference that undefined variable.
     
-    >>> foo('bar')
-    Traceback (most recent call last):
-        ...
-    UndefinedError: "foo" not defined
+    >>> try:
+    ...     foo('bar')
+    ... except UndefinedError, e:
+    ...     print e.msg
+    "foo" not defined
 
-    >>> foo.bar
-    Traceback (most recent call last):
-        ...
-    UndefinedError: "foo" not defined
+    >>> try:
+    ...     foo.bar
+    ... except UndefinedError, e:
+    ...     print e.msg
+    "foo" not defined
     
     :see: `LenientLookup`
     """
@@ -388,19 +389,21 @@
     raise an ``UndefinedError``:
     
     >>> expr = Expression('nothing', lookup='strict')
-    >>> expr.evaluate({})
-    Traceback (most recent call last):
-        ...
-    UndefinedError: "nothing" not defined
+    >>> try:
+    ...     expr.evaluate({})
+    ... except UndefinedError, e:
+    ...     print e.msg
+    "nothing" not defined
     
     The same happens when a non-existing attribute or item is accessed on an
     existing object:
     
     >>> expr = Expression('something.nil', lookup='strict')
-    >>> expr.evaluate({'something': dict()})
-    Traceback (most recent call last):
-        ...
-    UndefinedError: {} has no member named "nil"
+    >>> try:
+    ...     expr.evaluate({'something': dict()})
+    ... except UndefinedError, e:
+    ...     print e.msg
+    {} has no member named "nil"
     """
 
     @classmethod
@@ -421,17 +424,22 @@
                 rest = '\n'.join(['    %s' % line for line in rest.splitlines()])
             source = '\n'.join([first, rest])
     if isinstance(source, unicode):
-        source = '\xef\xbb\xbf' + source.encode('utf-8')
+        source = (u'\ufeff' + source).encode('utf-8')
     return parse(source, mode)
 
 
 def _compile(node, source=None, mode='eval', filename=None, lineno=-1,
              xform=None):
-    if isinstance(filename, unicode):
-        # unicode file names not allowed for code objects
-        filename = filename.encode('utf-8', 'replace')
-    elif not filename:
+    if not filename:
         filename = '<string>'
+    if IS_PYTHON2:
+        # Python 2 requires non-unicode filenames
+        if isinstance(filename, unicode):
+            filename = filename.encode('utf-8', 'replace')
+    else:
+        # Python 3 requires unicode filenames
+        if not isinstance(filename, unicode):
+            filename = filename.decode('utf-8', 'replace')
     if lineno <= 0:
         lineno = 1
 
@@ -458,10 +466,7 @@
     try:
         # We'd like to just set co_firstlineno, but it's readonly. So we need
         # to clone the code object while adjusting the line number
-        return CodeType(0, code.co_nlocals, code.co_stacksize,
-                        code.co_flags | 0x0040, code.co_code, code.co_consts,
-                        code.co_names, code.co_varnames, filename, name,
-                        lineno, code.co_lnotab, (), ())
+        return build_code_chunk(code, filename, name, lineno)
     except RuntimeError:
         return code
 
@@ -493,6 +498,8 @@
     def _extract_names(self, node):
         names = set()
         def _process(node):
+            if not IS_PYTHON2 and isinstance(node, _ast.arg):
+                names.add(node.arg)
             if isinstance(node, _ast.Name):
                 names.add(node.id)
             elif isinstance(node, _ast.alias):
@@ -513,7 +520,7 @@
         return names
 
     def visit_Str(self, node):
-        if isinstance(node.s, str):
+        if not isinstance(node.s, unicode):
             try: # If the string is ASCII, return a `str` object
                 node.s.decode('ascii')
             except ValueError: # Otherwise return a `unicode` object
Copyright (C) 2012-2017 Edgewall Software