[Zope-Checkins] CVS: Zope3/lib/python/Zope/PageTemplate - TALES.py:1.1.2.9

Shane Hathaway shane@cvs.zope.org
Wed, 13 Mar 2002 22:43:18 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/PageTemplate
In directory cvs.zope.org:/tmp/cvs-serv7754

Modified Files:
      Tag: Zope-3x-branch
	TALES.py 
Log Message:
- Made it so TALESErrors don't mask another exception, which is unfortunately
  brittle.

- Provided a registry of base names, such as "module", which are available
  in all expressions.

- Added setSourceFile() method to the generic TALES engine.

- Replaced getTALESError() in the TALES engine with createErrorInfo().
  "tal:on-error" handlers use ErrorInfo instances.

- Added __traceback_supplement__, which doesn't do anything yet, but will
  soon provide the detailed exception information that TALESErrors tried to
  provide.


=== Zope3/lib/python/Zope/PageTemplate/TALES.py 1.1.2.8 => 1.1.2.9 ===
 
 import re, sys
+from types import StringType
+
 import Zope.ZTUtils
+from SafeMapping import SafeMapping
 
-from types import StringType
 
 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
 _valid_name = re.compile('%s$' % NAME_RE).match
 
+
 class TALESError(Exception):
-    __allow_access_to_unprotected_subobjects__ = 1
-    def __init__(self, expression, info=(None, None, None),
-                 position=(None, None)):
-        self.type, self.value, self.traceback = info
-        self.expression = expression
-        self.setPosition(position)
-    def setPosition(self, position):
-        self.lineno = position[0]
-        self.offset = position[1]
-    def takeTraceback(self):
-        t = self.traceback
-        self.traceback = None
-        return t
-    def __str__(self):
-        if self.type is None:
-            s = self.expression
-        else:
-            s = '%s: %s in %s' % (self.type, self.value,
-                                  `self.expression`)
-        if self.lineno is not None:
-            s = "%s, at line %d" % (s, self.lineno)
-        if self.offset is not None:
-            s = "%s, column %d" % (s, self.offset + 1)
-        return s
-    def __nonzero__(self):
-        return 0
+    """Error during TALES evaluation"""
 
 class Undefined(TALESError):
     '''Exception raised on traversal of an undefined path'''
 
-class RegistrationError(Exception):
-    '''TALES Type Registration Error'''
-
 class CompilerError(Exception):
     '''TALES Compiler Error'''
 
+class RegistrationError(Exception):
+    '''Expression type or base name registration Error'''
+
+
 class Default:
     '''Retain Default'''
     def __nonzero__(self):
         return 0
-Default = Default()
+default_ = Default()
 
-_marker = []
+_marker = object()
 
-from SafeMapping import SafeMapping
 
 class Iterator(Zope.ZTUtils.Iterator):
     def __init__(self, name, seq, context):
-        Zope.ZTUtils.Iterator.__init__(self, seq) # :
+        Zope.ZTUtils.Iterator.__init__(self, seq)
         self.name = name
         self._context = context
 
@@ -80,19 +58,28 @@
         return self
 
     def next(self):
-        try:
-            if Zope.ZTUtils.Iterator.next(self):
-                self._context.setLocal(self.name, self.seq[self.index])
-                return 1
-        except TALESError:
-            raise
-        except:
-            raise TALESError, ('repeat/%s' % self.name,
-                               sys.exc_info()), sys.exc_info()[2]
+        if Zope.ZTUtils.Iterator.next(self):
+            self._context.setLocal(self.name, self.seq[self.index])
+            return 1
         return 0
 
 
-class Engine:
+
+class ErrorInfo:
+    """Information about an exception passed to an on-error handler."""
+    def __init__(self, err, position=(None, None)):
+        if isinstance(err, Exception):
+            self.type = err.__class__
+            self.value = err
+        else:
+            self.type = err
+            self.value = None
+        self.lineno = position[0]
+        self.offset = position[1]
+
+
+
+class ExpressionEngine:
     '''Expression Engine
 
     An instance of this class keeps a mutable collection of expression
@@ -100,16 +87,15 @@
     these handlers.  It can provide an expression Context, which is
     capable of holding state and evaluating compiled expressions.
     '''
-    Iterator = Iterator
-
-    def __init__(self, Iterator=None):
+    def __init__(self):
         self.types = {}
-        if Iterator is not None:
-            self.Iterator = Iterator
+        self.base_names = {}
+        self.iteratorFactory = Iterator
 
     def registerType(self, name, handler):
         if not _valid_name(name):
-            raise RegistrationError, 'Invalid Expression type "%s".' % name
+            raise RegistrationError, (
+                'Invalid expression type name "%s".' % name)
         types = self.types
         if types.has_key(name):
             raise RegistrationError, (
@@ -120,6 +106,18 @@
     def getTypes(self):
         return self.types
 
+    def registerBaseName(self, name, object):
+        if not _valid_name(name):
+            raise RegistrationError, 'Invalid base name "%s".' % name
+        base_names = self.base_names
+        if base_names.has_key(name):
+            raise RegistrationError, (
+                'Multiple registrations for base name "%s".' % name)
+        base_names[name] = object
+
+    def getBaseNames(self):
+        return self.base_names
+
     def compile(self, expression):
         m = _parse_expr(expression)
         if m:
@@ -146,6 +144,7 @@
     def getCompilerError(self):
         return CompilerError
 
+
 class Context:
     '''Expression Context
 
@@ -154,16 +153,14 @@
     '''
 
     _context_class = SafeMapping
-    _nocatch = TALESError
     position = (None, None)
+    source_file = None
 
     def __init__(self, engine, contexts):
         self._engine = engine
-        if hasattr(engine, '_nocatch'):
-            self._nocatch = engine._nocatch
         self.contexts = contexts
         contexts['nothing'] = None
-        contexts['default'] = Default
+        contexts['default'] = default_
 
         self.repeat_vars = rv = {}
         # Wrap this, as it is visible to restricted code
@@ -205,8 +202,8 @@
     def setRepeat(self, name, expr):
         expr = self.evaluate(expr)
         if not expr:
-            return self._engine.Iterator(name, (), self)
-        it = self._engine.Iterator(name, expr, self)
+            return self._engine.iteratorFactory(name, (), self)
+        it = self._engine.iteratorFactory(name, expr, self)
         old_value = self.repeat_vars.get(name)
         self._scope_stack[-1].append((name, old_value))
         self.repeat_vars[name] = it
@@ -216,22 +213,10 @@
                  isinstance=isinstance, StringType=StringType):
         if isinstance(expression, StringType):
             expression = self._engine.compile(expression)
-        try:
-            v = expression(self)
-            if isinstance(v, Exception):
-                if isinstance(v, TALESError):
-                    raise v, None, v.takeTraceback()
-                raise v
-        except TALESError, err:
-            err.setPosition(self.position)
-            raise err, None, sys.exc_info()[2]
-        except self._nocatch:
-            raise
-        except:
-            raise TALESError, (`expression`, sys.exc_info(),
-                               self.position), sys.exc_info()[2]
-        else:
-            return v
+        __traceback_supplement__ = (
+            TALESTracebackSupplement, self, expression)
+        v = expression(self)
+        return v
 
     evaluateValue = evaluate
 
@@ -240,7 +225,7 @@
 
     def evaluateText(self, expr, None=None):
         text = self.evaluate(expr)
-        if text is Default or text is None:
+        if text is default_ or text is None:
             return text
         return str(text)
 
@@ -253,14 +238,54 @@
         return self.evaluate(expr)
     evaluateMacro = evaluate
 
-    def getTALESError(self):
-        return TALESError
+    def createErrorInfo(self, err, position):
+        return ErrorInfo(err, position)
 
     def getDefault(self):
-        return Default
+        return default_
+
+    def setSourceFile(self, source_file):
+        self.source_file = source_file
 
     def setPosition(self, position):
         self.position = position
+
+
+class TALESTracebackSupplement:
+    """Implementation of Zope.Exceptions.ITracebackSupplement"""
+    def __init__(self, context, expression):
+        self.context = context
+        self.expression = repr(expression)
+        self.line = context.position[0]
+        self.column = context.position[1]
+
+##        source_file = context.source_file
+##        if (isinstance(source_file, StringType) and
+##            source_file.startswith('traversal:')):
+##            p = source_file[10:]
+##            # XXX There should be a better way to find the Zope app root.
+##            root = self.context.contexts.get('root', None)
+##            if root is not None:
+##                try:
+##                    object = root.unrestrictedTraverse(p)
+##                except:
+##                    # Hmm, couldn't find the script??
+##                    pass
+##                else:
+##                    self.manageable_object = object
+        
+    def getInfo(self, as_html=0):
+        import pprint
+        data = self.context.contexts.copy()
+        s = pprint.pformat(data)
+        if not as_html:
+            return '   - Names:\n      %s' % s.replace('\n', '\n      ')
+        else:
+            from cgi import escape
+            return '<b>Names:</b><pre>%s</pre>' % (escape(s))
+        return None
+
+
 
 class SimpleExpr:
     '''Simple example of an expression type handler'''