[Zope-Checkins] CVS: Zope/lib/python/Products/PageTemplates - Expressions.py:1.32.2.1 PageTemplate.py:1.21.2.1 TALES.py:1.28.2.1 ZopePageTemplate.py:1.30.2.1

Shane Hathaway shane@cvs.zope.org
Fri, 1 Mar 2002 17:22:40 -0500


Update of /cvs-repository/Zope/lib/python/Products/PageTemplates
In directory cvs.zope.org:/tmp/cvs-serv11778

Modified Files:
      Tag: shane-better-tracebacks-branch
	Expressions.py PageTemplate.py TALES.py ZopePageTemplate.py 
Log Message:
Rewired exception handling in page templates so that:

- Tracebacks are never lost.

- Exceptions are never converted to other exception types.

- The ErrorReporter product can report line numbers and show URLs in traceback.

I made sure the tests still pass, so if any functionality is now broken,
we need more tests. ;-)


=== Zope/lib/python/Products/PageTemplates/Expressions.py 1.32 => 1.32.2.1 ===
 import re, sys
 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
-     TALESError, Undefined, Default, _parse_expr
+     Undefined, Default, _parse_expr
 from string import strip, split, join, replace, lstrip
 from Acquisition import aq_base, aq_inner, aq_parent
 
@@ -33,7 +33,6 @@
         from PathIterator import Iterator
         _engine = Engine(Iterator)
         installHandlers(_engine)
-        _engine._nocatch = (TALESError, 'Redirect')
     return _engine
 
 def installHandlers(engine):
@@ -171,7 +170,7 @@
     def _eval(self, econtext,
               isinstance=isinstance, StringType=type(''), render=render):
         for expr in self._subexprs[:-1]:
-            # Try all but the last subexpression, skipping undefined ones
+            # Try all but the last subexpression, skipping undefined ones.
             try:
                 ob = expr(econtext)
             except Undefs:
@@ -179,12 +178,8 @@
             else:
                 break
         else:
-            # On the last subexpression allow exceptions through, but
-            # wrap ones that indicate that the subexpression was undefined
-            try:
-                ob = self._subexprs[-1](econtext)
-            except Undefs[1:]:
-                raise Undefined(self._s, sys.exc_info())
+            # On the last subexpression allow exceptions through.
+            ob = self._subexprs[-1](econtext)
 
         if self._name == 'nocall' or isinstance(ob, StringType):
             return ob
@@ -234,8 +229,9 @@
         vvals = []
         for var in self._vars:
             v = var(econtext)
-            if isinstance(v, Exception):
-                raise v
+            # I hope this isn't in use anymore.
+            ## if isinstance(v, Exception):
+            ##     raise v
             vvals.append(v)
         return self._expr % tuple(vvals)
 
@@ -341,6 +337,7 @@
                 try:
                     o=object[name]
                 except (AttributeError, TypeError):
+                    # Assume the object does not support the item interface.
                     raise AttributeError, name
                 if not validate(object, object, name, o):
                     raise Unauthorized, name


=== Zope/lib/python/Products/PageTemplates/PageTemplate.py 1.21 => 1.21.2.1 ===
 __version__='$Revision$'[11:-2]
 
-import os, sys, traceback, pprint
+import sys, pprint
+from cgi import escape
+
 from TAL.TALParser import TALParser
 from TAL.HTMLTALParser import HTMLTALParser
 from TAL.TALGenerator import TALGenerator
@@ -27,7 +29,6 @@
 from cStringIO import StringIO
 from ExtensionClass import Base
 
-Z_DEBUG_MODE = os.environ.get('Z_DEBUG_MODE') == '1'
 
 class MacroCollection(Base):
     def __of__(self, parent):
@@ -77,8 +78,8 @@
         output = StringIO()
         c = self.pt_getContext()
         c.update(extra_context)
-        if Z_DEBUG_MODE:
-            __traceback_info__ = pprint.pformat(c)
+
+        __traceback_supplement__ = (PageTemplateTracebackSupplement, self, c)
 
         TALInterpreter(self._v_program, self._v_macros,
                        getEngine().getContext(c),
@@ -174,3 +175,31 @@
 class PTRuntimeError(RuntimeError):
     '''The Page Template has template errors that prevent it from rendering.'''
     pass
+
+
+class PageTemplateTracebackSupplement:
+    """Implementation of Products.ErrorReporter.ITracebackSupplement
+
+    Shows the context in which an expression is executed.
+    """
+
+    def __init__(self, pt, context):
+        c = context.copy()
+        if c.has_key('request'):
+            c['request'] = '(not shown)'
+        self.context = c
+        self.pt = pt
+
+    def getInfo(self, as_html=0):
+        s = pprint.pformat(self.context)
+        if not as_html:
+            return '   - Context:\n      %s' % s.replace('\n', '\n      ')
+        else:
+            return '<b>Context:</b><pre>%s</pre>' % (
+                escape(pprint.pformat(self.context)))
+
+    def getManageableObject(self):
+        # N/A
+        return None
+    getLine = getColumn = getExpression = getManageableObject
+


=== Zope/lib/python/Products/PageTemplates/TALES.py 1.28 => 1.28.2.1 ===
 
 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 on %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 1
+    """Error during TALES expression evaluation"""
 
 class Undefined(TALESError):
     '''Exception raised on traversal of an undefined path'''
-    def __str__(self):
-        if self.type is None:
-            s = self.expression
-        else:
-            s = '%s not found in %s' % (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
 
 class RegistrationError(Exception):
     '''TALES Type Registration Error'''
@@ -107,18 +71,27 @@
         self._context = context
 
     def next(self):
-        try:
-            if ZTUtils.Iterator.next(self):
-                self._context.setLocal(self.name, self.item)
-                return 1
-        except TALESError:
-            raise
-        except:
-            raise TALESError, ('repeat/%s' % self.name,
-                               sys.exc_info()), sys.exc_info()[2]
+        if ZTUtils.Iterator.next(self):
+            self._context.setLocal(self.name, self.item)
+            return 1
         return 0
 
 
+class ErrorInfo:
+    """Information about an exception passed to an on-error handler."""
+    __allow_access_to_unprotected_subobjects__ = 1
+
+    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 Engine:
     '''Expression Engine
 
@@ -181,13 +154,10 @@
     '''
 
     _context_class = SafeMapping
-    _nocatch = TALESError
     position = (None, 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
@@ -243,18 +213,10 @@
                  isinstance=isinstance, StringType=StringType):
         if isinstance(expression, StringType):
             expression = self._engine.compile(expression)
-        try:
-            v = expression(self)
-        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.position, expression)
+        v = expression(self)
+        return v
 
     evaluateValue = evaluate
 
@@ -276,14 +238,38 @@
         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
 
     def setPosition(self, position):
         self.position = position
+
+
+class TALESTracebackSupplement:
+    """Implementation of Products.ErrorReporter.ITracebackSupplement"""
+    def __init__(self, position, expression):
+        self.position = position
+        self.expression = expression
+
+    def getManageableObject(self):
+        return None
+
+    def getLine(self):
+        return self.position[0]
+
+    def getColumn(self):
+        return self.position[0]
+
+    def getExpression(self):
+        return repr(self.expression)
+
+    def getInfo(self, as_html=0):
+        return None
+
+
 
 class SimpleExpr:
     '''Simple example of an expression type handler'''


=== Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py 1.30 => 1.30.2.1 ===
 from OFS.PropertyManager import PropertyManager
 from PageTemplate import PageTemplate
-from TALES import TALESError
 from Expressions import SecureModuleImporter
 from PageTemplateFile import PageTemplateFile
 
@@ -187,7 +186,8 @@
         try:
             self.REQUEST.RESPONSE.setHeader('content-type',
                                             self.content_type)
-        except AttributeError: pass
+        except AttributeError:
+            pass
 
         security=getSecurityManager()
         bound_names['user'] = security.getUser()
@@ -206,15 +206,9 @@
         # Execute the template in a new security context.
         security.addContext(self)
         try:
-            try:
-                result = self.pt_render(extra_context=bound_names)
-            except TALESError, err:
-                if (err.type == Unauthorized or
-                    (isinstance(Unauthorized, Exception) and
-                     isinstance(err.type, Unauthorized))):
-                    raise err.type, err.value, err.takeTraceback()
-                err.takeTraceback()
-                raise
+            __traceback_supplement__ = (
+                ZopePageTemplateTracebackSupplement, self)
+            result = self.pt_render(extra_context=bound_names)
             if keyset is not None:
                 # Store the result in the cache.
                 self.ZCacheable_set(result, keywords=keyset)
@@ -278,6 +272,7 @@
         def wl_isLocked(self):
             return 0
 
+
 class Src(Acquisition.Explicit):
     " "
 
@@ -291,6 +286,24 @@
 d = ZopePageTemplate.__dict__
 d['source.xml'] = d['source.html'] = Src()
 
+
+class ZopePageTemplateTracebackSupplement:
+    """Implementation of Products.ErrorReporter.ITracebackSupplement"""
+    def __init__(self, template):
+        self.template = template
+
+    def getManageableObject(self):
+        return self.template
+
+    def getInfo(self, as_html=0):
+        return None
+
+    def getLine(self):
+        # N/A
+        return None
+    getColumn = getExpression = getLine
+
+
 # Product registration and Add support
 manage_addPageTemplateForm = PageTemplateFile(
     'www/ptAdd', globals(), __name__='manage_addPageTemplateForm')
@@ -318,9 +331,13 @@
             
         self._setObject(id, zpt)
 
-        try: u = self.DestinationURL()
-        except: u = REQUEST['URL1']
-        if submit==" Add and Edit ": u="%s/%s" % (u,quote(id))
+        try:
+            u = self.DestinationURL()
+        except AttributeError:
+            u = REQUEST['URL1']
+
+        if submit == " Add and Edit ":
+            u = "%s/%s" % (u, quote(id))
         REQUEST.RESPONSE.redirect(u+'/manage_main')
     return ''