Commit ccd4d15e authored by Shane Hathaway's avatar Shane Hathaway

Merged shane-better-tracebacks-branch. The changes are explained in...

Merged shane-better-tracebacks-branch.  The changes are explained in http://dev.zope.org/Wikis/DevSite/Proposals/BetterTracebacks
parent d4302f00
...@@ -17,8 +17,8 @@ Aqueduct database adapters, etc. ...@@ -17,8 +17,8 @@ Aqueduct database adapters, etc.
This module can also be used as a simple template for implementing new This module can also be used as a simple template for implementing new
item types. item types.
$Id: SimpleItem.py,v 1.94 2002/03/27 10:14:03 htrd Exp $''' $Id: SimpleItem.py,v 1.95 2002/04/03 20:43:52 shane Exp $'''
__version__='$Revision: 1.94 $'[11:-2] __version__='$Revision: 1.95 $'[11:-2]
import re, sys, Globals, App.Management, Acquisition, App.Undo import re, sys, Globals, App.Management, Acquisition, App.Undo
import AccessControl.Role, AccessControl.Owned, App.Common import AccessControl.Role, AccessControl.Owned, App.Common
...@@ -29,9 +29,11 @@ from types import InstanceType, StringType ...@@ -29,9 +29,11 @@ from types import InstanceType, StringType
from ComputedAttribute import ComputedAttribute from ComputedAttribute import ComputedAttribute
from AccessControl import getSecurityManager from AccessControl import getSecurityManager
from Traversable import Traversable from Traversable import Traversable
from Acquisition import aq_base from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
from DocumentTemplate.ustr import ustr from DocumentTemplate.ustr import ustr
from zExceptions.ExceptionFormatter import format_exception
import time import time
from zLOG import LOG, ERROR
import marshal import marshal
import ZDOM import ZDOM
...@@ -146,19 +148,27 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable, ...@@ -146,19 +148,27 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
if error_type is None: error_type =sys.exc_info()[0] if error_type is None: error_type =sys.exc_info()[0]
if error_value is None: error_value=sys.exc_info()[1] if error_value is None: error_value=sys.exc_info()[1]
# turn error_type into a string
if hasattr(error_type, '__name__'):
error_type=error_type.__name__
# allow for a few different traceback options # allow for a few different traceback options
if tb is None and error_tb is None: if tb is None and error_tb is None:
tb=sys.exc_info()[2] tb=sys.exc_info()[2]
if type(tb) is not type('') and (error_tb is None): if type(tb) is not type('') and (error_tb is None):
error_tb=pretty_tb(error_type, error_value, tb) error_tb = pretty_tb(error_type, error_value, tb)
elif type(tb) is type('') and not error_tb: elif type(tb) is type('') and not error_tb:
error_tb=tb error_tb = tb
try:
log = aq_acquire(self, '__error_log__', containment=1)
except AttributeError:
pass
else:
log.raising((error_type, error_value, tb))
# turn error_type into a string
if hasattr(error_type, '__name__'):
error_type=error_type.__name__
if hasattr(self, '_v_eek'): if hasattr(self, '_v_eek'):
# Stop if there is recursion.
raise error_type, error_value, tb raise error_type, error_value, tb
self._v_eek=1 self._v_eek=1
...@@ -198,7 +208,11 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable, ...@@ -198,7 +208,11 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
else: else:
v = HTML.__call__(s, client, REQUEST, **kwargs) v = HTML.__call__(s, client, REQUEST, **kwargs)
except: except:
v = error_value or "Sorry, an error occurred" LOG('OFS', ERROR, 'Exception while rendering an error message',
error=sys.exc_info())
v = repr(error_value) + (
" (Also, an error occurred while attempting "
"to render the standard error message.)")
raise error_type, v, tb raise error_type, v, tb
finally: finally:
if hasattr(self, '_v_eek'): del self._v_eek if hasattr(self, '_v_eek'): del self._v_eek
...@@ -311,45 +325,19 @@ class Item_w__name__(Item): ...@@ -311,45 +325,19 @@ class Item_w__name__(Item):
''' '''
path = (self.__name__,) path = (self.__name__,)
p = getattr(self,'aq_inner', None) p = aq_parent(aq_inner(self))
if p is not None: if p is not None:
path = p.aq_parent.getPhysicalPath() + path path = p.getPhysicalPath() + path
return path return path
def format_exception(etype,value,tb,limit=None): def pretty_tb(t, v, tb, as_html=1):
import traceback tb = format_exception(t, v, tb, as_html=as_html)
result=['Traceback (innermost last):'] tb = '\n'.join(tb)
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals=f.f_locals
result.append(' File %s, line %d, in %s'
% (filename,lineno,name))
try: result.append(' (Object: %s)' %
locals[co.co_varnames[0]].__name__)
except: pass
try: result.append(' (Info: %s)' %
str(locals['__traceback_info__']))
except: pass
tb = tb.tb_next
n = n+1
result.append(' '.join(traceback.format_exception_only(etype, value)))
return result
def pretty_tb(t,v,tb):
tb=format_exception(t,v,tb,200)
tb='\n'.join(tb)
return tb return tb
class SimpleItem(Item, Globals.Persistent, class SimpleItem(Item, Globals.Persistent,
Acquisition.Implicit, Acquisition.Implicit,
AccessControl.Role.RoleManager, AccessControl.Role.RoleManager,
...@@ -365,3 +353,29 @@ class SimpleItem(Item, Globals.Persistent, ...@@ -365,3 +353,29 @@ class SimpleItem(Item, Globals.Persistent,
) )
__ac_permissions__=(('View', ()),) __ac_permissions__=(('View', ()),)
def __repr__(self):
"""Show the physical path of the object and its context if available.
"""
try:
path = '/'.join(self.getPhysicalPath())
except:
path = None
context_path = None
context = aq_parent(self)
container = aq_parent(aq_inner(self))
if aq_base(context) is not aq_base(container):
try:
context_path = '/'.join(context.getPhysicalPath())
except:
context_path = None
res = '<%s' % self.__class__.__name__
if path:
res += ' at %s' % path
else:
res += ' at 0x%x' % id(self)
if context_path:
res += ' used for %s' % context_path
res += '>'
return res
...@@ -17,11 +17,11 @@ Page Template-specific implementation of TALES, with handlers ...@@ -17,11 +17,11 @@ Page Template-specific implementation of TALES, with handlers
for Python expressions, string literals, and paths. for Python expressions, string literals, and paths.
""" """
__version__='$Revision: 1.33 $'[11:-2] __version__='$Revision: 1.34 $'[11:-2]
import re, sys import re, sys
from TALES import Engine, CompilerError, _valid_name, NAME_RE, \ 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 string import strip, split, join, replace, lstrip
from Acquisition import aq_base, aq_inner, aq_parent from Acquisition import aq_base, aq_inner, aq_parent
...@@ -33,7 +33,6 @@ def getEngine(): ...@@ -33,7 +33,6 @@ def getEngine():
from PathIterator import Iterator from PathIterator import Iterator
_engine = Engine(Iterator) _engine = Engine(Iterator)
installHandlers(_engine) installHandlers(_engine)
_engine._nocatch = (TALESError, 'Redirect')
return _engine return _engine
def installHandlers(engine): def installHandlers(engine):
...@@ -171,7 +170,7 @@ class PathExpr: ...@@ -171,7 +170,7 @@ class PathExpr:
def _eval(self, econtext, def _eval(self, econtext,
isinstance=isinstance, StringType=type(''), render=render): isinstance=isinstance, StringType=type(''), render=render):
for expr in self._subexprs[:-1]: 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: try:
ob = expr(econtext) ob = expr(econtext)
except Undefs: except Undefs:
...@@ -179,12 +178,8 @@ class PathExpr: ...@@ -179,12 +178,8 @@ class PathExpr:
else: else:
break break
else: else:
# On the last subexpression allow exceptions through, but # On the last subexpression allow exceptions through.
# wrap ones that indicate that the subexpression was undefined ob = self._subexprs[-1](econtext)
try:
ob = self._subexprs[-1](econtext)
except Undefs[1:]:
raise Undefined(self._s, sys.exc_info())
if self._name == 'nocall' or isinstance(ob, StringType): if self._name == 'nocall' or isinstance(ob, StringType):
return ob return ob
...@@ -234,8 +229,9 @@ class StringExpr: ...@@ -234,8 +229,9 @@ class StringExpr:
vvals = [] vvals = []
for var in self._vars: for var in self._vars:
v = var(econtext) v = var(econtext)
if isinstance(v, Exception): # I hope this isn't in use anymore.
raise v ## if isinstance(v, Exception):
## raise v
vvals.append(v) vvals.append(v)
return self._expr % tuple(vvals) return self._expr % tuple(vvals)
...@@ -328,9 +324,10 @@ def restrictedTraverse(self, path, securityManager, ...@@ -328,9 +324,10 @@ def restrictedTraverse(self, path, securityManager,
if not validate(object, container, name, o): if not validate(object, container, name, o):
raise Unauthorized, name raise Unauthorized, name
else: else:
o=get(object, name, M) # Try an attribute.
o = get(object, name, M)
if o is not M: if o is not M:
# Check security. # Check access to the attribute.
if has(object, 'aq_acquire'): if has(object, 'aq_acquire'):
object.aq_acquire( object.aq_acquire(
name, validate2, validate) name, validate2, validate)
...@@ -338,12 +335,31 @@ def restrictedTraverse(self, path, securityManager, ...@@ -338,12 +335,31 @@ def restrictedTraverse(self, path, securityManager,
if not validate(object, object, name, o): if not validate(object, object, name, o):
raise Unauthorized, name raise Unauthorized, name
else: else:
# Try an item.
try: try:
o=object[name] # XXX maybe in Python 2.2 we can just check whether
except (AttributeError, TypeError): # the object has the attribute "__getitem__"
raise AttributeError, name # instead of blindly catching exceptions.
if not validate(object, object, name, o): o = object[name]
raise Unauthorized, name except AttributeError, exc:
if str(exc).find('__getitem__') >= 0:
# The object does not support the item interface.
# Try to re-raise the original attribute error.
# XXX I think this only happens with
# ExtensionClass instances.
get(object, name)
raise
except TypeError, exc:
if str(exc).find('unsubscriptable') >= 0:
# The object does not support the item interface.
# Try to re-raise the original attribute error.
# XXX This is sooooo ugly.
get(object, name)
raise
else:
# Check access to the item.
if not validate(object, object, name, o):
raise Unauthorized, name
object = o object = o
return object return object
......
...@@ -15,9 +15,10 @@ ...@@ -15,9 +15,10 @@
HTML- and XML-based template objects using TAL, TALES, and METAL. HTML- and XML-based template objects using TAL, TALES, and METAL.
""" """
__version__='$Revision: 1.22 $'[11:-2] __version__='$Revision: 1.23 $'[11:-2]
import sys
import os, sys, traceback, pprint
from TAL.TALParser import TALParser from TAL.TALParser import TALParser
from TAL.HTMLTALParser import HTMLTALParser from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALGenerator import TALGenerator from TAL.TALGenerator import TALGenerator
...@@ -26,12 +27,8 @@ from Expressions import getEngine ...@@ -26,12 +27,8 @@ from Expressions import getEngine
from string import join, strip, rstrip, split, replace, lower, find from string import join, strip, rstrip, split, replace, lower, find
from cStringIO import StringIO from cStringIO import StringIO
from ExtensionClass import Base from ExtensionClass import Base
from ComputedAttribute import ComputedAttribute
Z_DEBUG_MODE = os.environ.get('Z_DEBUG_MODE') == '1'
class MacroCollection(Base):
def __of__(self, parent):
return parent.pt_macros()
class PageTemplate(Base): class PageTemplate(Base):
"Page Templates using TAL, TALES, and METAL" "Page Templates using TAL, TALES, and METAL"
...@@ -40,11 +37,16 @@ class PageTemplate(Base): ...@@ -40,11 +37,16 @@ class PageTemplate(Base):
expand = 0 expand = 0
_v_errors = () _v_errors = ()
_v_warnings = () _v_warnings = ()
_v_program = None
_v_macros = None
_v_cooked = 0
id = '(unknown)' id = '(unknown)'
_text = '' _text = ''
_error_start = '<!-- Page Template Diagnostics' _error_start = '<!-- Page Template Diagnostics'
macros = MacroCollection() def macros(self):
return self.pt_macros()
macros = ComputedAttribute(macros, 1)
def pt_edit(self, text, content_type): def pt_edit(self, text, content_type):
if content_type: if content_type:
...@@ -72,13 +74,16 @@ class PageTemplate(Base): ...@@ -72,13 +74,16 @@ class PageTemplate(Base):
def pt_render(self, source=0, extra_context={}): def pt_render(self, source=0, extra_context={}):
"""Render this Page Template""" """Render this Page Template"""
if not self._v_cooked:
self._cook()
__traceback_supplement__ = (PageTemplateTracebackSupplement, self)
if self._v_errors: if self._v_errors:
raise PTRuntimeError, 'Page Template %s has errors.' % self.id raise PTRuntimeError, 'Page Template %s has errors.' % self.id
output = StringIO() output = StringIO()
c = self.pt_getContext() c = self.pt_getContext()
c.update(extra_context) c.update(extra_context)
if Z_DEBUG_MODE:
__traceback_info__ = pprint.pformat(c)
TALInterpreter(self._v_program, self._v_macros, TALInterpreter(self._v_program, self._v_macros,
getEngine().getContext(c), getEngine().getContext(c),
...@@ -92,6 +97,8 @@ class PageTemplate(Base): ...@@ -92,6 +97,8 @@ class PageTemplate(Base):
return self.pt_render(extra_context={'options': kwargs}) return self.pt_render(extra_context={'options': kwargs})
def pt_errors(self): def pt_errors(self):
if not self._v_cooked:
self._cook()
err = self._v_errors err = self._v_errors
if err: if err:
return err return err
...@@ -102,13 +109,21 @@ class PageTemplate(Base): ...@@ -102,13 +109,21 @@ class PageTemplate(Base):
return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2]) return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
def pt_warnings(self): def pt_warnings(self):
if not self._v_cooked:
self._cook()
return self._v_warnings return self._v_warnings
def pt_macros(self): def pt_macros(self):
if not self._v_cooked:
self._cook()
if self._v_errors: if self._v_errors:
__traceback_supplement__ = (PageTemplateTracebackSupplement, self)
raise PTRuntimeError, 'Page Template %s has errors.' % self.id raise PTRuntimeError, 'Page Template %s has errors.' % self.id
return self._v_macros return self._v_macros
def pt_source_file(self):
return None # Unknown.
def write(self, text): def write(self, text):
assert type(text) is type('') assert type(text) is type('')
if text[:len(self._error_start)] == self._error_start: if text[:len(self._error_start)] == self._error_start:
...@@ -120,6 +135,8 @@ class PageTemplate(Base): ...@@ -120,6 +135,8 @@ class PageTemplate(Base):
self._cook() self._cook()
def read(self): def read(self):
if not self._v_cooked:
self._cook()
if not self._v_errors: if not self._v_errors:
if not self.expand: if not self.expand:
return self._text return self._text
...@@ -137,14 +154,14 @@ class PageTemplate(Base): ...@@ -137,14 +154,14 @@ class PageTemplate(Base):
def _cook(self): def _cook(self):
"""Compile the TAL and METAL statments. """Compile the TAL and METAL statments.
A Page Template must always be cooked, and cooking must not Cooking must not fail due to compilation errors in templates.
fail due to user input.
""" """
source_file = self.pt_source_file()
if self.html(): if self.html():
gen = TALGenerator(getEngine(), xml=0) gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
parser = HTMLTALParser(gen) parser = HTMLTALParser(gen)
else: else:
gen = TALGenerator(getEngine()) gen = TALGenerator(getEngine(), source_file=source_file)
parser = TALParser(gen) parser = TALParser(gen)
self._v_errors = () self._v_errors = ()
...@@ -155,6 +172,7 @@ class PageTemplate(Base): ...@@ -155,6 +172,7 @@ class PageTemplate(Base):
self._v_errors = ["Compilation failed", self._v_errors = ["Compilation failed",
"%s: %s" % sys.exc_info()[:2]] "%s: %s" % sys.exc_info()[:2]]
self._v_warnings = parser.getWarnings() self._v_warnings = parser.getWarnings()
self._v_cooked = 1
def html(self): def html(self):
if not hasattr(getattr(self, 'aq_base', self), 'is_html'): if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
...@@ -174,3 +192,16 @@ ModuleImporter = _ModuleImporter() ...@@ -174,3 +192,16 @@ ModuleImporter = _ModuleImporter()
class PTRuntimeError(RuntimeError): class PTRuntimeError(RuntimeError):
'''The Page Template has template errors that prevent it from rendering.''' '''The Page Template has template errors that prevent it from rendering.'''
pass pass
class PageTemplateTracebackSupplement:
#__implements__ = ITracebackSupplement
def __init__(self, pt):
self.object = pt
w = pt.pt_warnings()
e = pt.pt_errors()
if e:
w = list(w) + list(e)
self.warnings = w
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
Zope object encapsulating a Page Template from the filesystem. Zope object encapsulating a Page Template from the filesystem.
""" """
__version__='$Revision: 1.12 $'[11:-2] __version__='$Revision: 1.13 $'[11:-2]
import os, AccessControl, Acquisition, sys import os, AccessControl, Acquisition, sys
from Globals import package_home, DevelopmentMode from Globals import package_home, DevelopmentMode
...@@ -100,6 +100,10 @@ class PageTemplateFile(Script, PageTemplate, Traversable): ...@@ -100,6 +100,10 @@ class PageTemplateFile(Script, PageTemplate, Traversable):
self._cook_check() self._cook_check()
return PageTemplate.pt_macros(self) return PageTemplate.pt_macros(self)
def pt_source_file(self):
"""Returns a file name to be compiled into the TAL code."""
return self.__name__ # Don't reveal filesystem paths
def _cook_check(self): def _cook_check(self):
if self._v_last_read and not DevelopmentMode: if self._v_last_read and not DevelopmentMode:
return return
...@@ -132,6 +136,6 @@ class PageTemplateFile(Script, PageTemplate, Traversable): ...@@ -132,6 +136,6 @@ class PageTemplateFile(Script, PageTemplate, Traversable):
__roles__ = ComputedAttribute(_get__roles__, 1) __roles__ = ComputedAttribute(_get__roles__, 1)
def __setstate__(self, state): def __getstate__(self):
raise StorageError, ("Instance of AntiPersistent class %s " raise StorageError, ("Instance of AntiPersistent class %s "
"cannot be stored." % self.__class__.__name__) "cannot be stored." % self.__class__.__name__)
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
An implementation of a generic TALES engine An implementation of a generic TALES engine
""" """
__version__='$Revision: 1.28 $'[11:-2] __version__='$Revision: 1.29 $'[11:-2]
import re, sys, ZTUtils import re, sys, ZTUtils
from MultiMapping import MultiMapping from MultiMapping import MultiMapping
...@@ -27,46 +27,10 @@ _parse_expr = re.compile(r"(%s):" % NAME_RE).match ...@@ -27,46 +27,10 @@ _parse_expr = re.compile(r"(%s):" % NAME_RE).match
_valid_name = re.compile('%s$' % NAME_RE).match _valid_name = re.compile('%s$' % NAME_RE).match
class TALESError(Exception): class TALESError(Exception):
__allow_access_to_unprotected_subobjects__ = 1 """Error during TALES expression evaluation"""
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
class Undefined(TALESError): class Undefined(TALESError):
'''Exception raised on traversal of an undefined path''' '''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): class RegistrationError(Exception):
'''TALES Type Registration Error''' '''TALES Type Registration Error'''
...@@ -107,18 +71,27 @@ class Iterator(ZTUtils.Iterator): ...@@ -107,18 +71,27 @@ class Iterator(ZTUtils.Iterator):
self._context = context self._context = context
def next(self): def next(self):
try: if ZTUtils.Iterator.next(self):
if ZTUtils.Iterator.next(self): self._context.setLocal(self.name, self.item)
self._context.setLocal(self.name, self.item) return 1
return 1
except TALESError:
raise
except:
raise TALESError, ('repeat/%s' % self.name,
sys.exc_info()), sys.exc_info()[2]
return 0 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: class Engine:
'''Expression Engine '''Expression Engine
...@@ -181,13 +154,11 @@ class Context: ...@@ -181,13 +154,11 @@ class Context:
''' '''
_context_class = SafeMapping _context_class = SafeMapping
_nocatch = TALESError
position = (None, None) position = (None, None)
source_file = None
def __init__(self, engine, contexts): def __init__(self, engine, contexts):
self._engine = engine self._engine = engine
if hasattr(engine, '_nocatch'):
self._nocatch = engine._nocatch
self.contexts = contexts self.contexts = contexts
contexts['nothing'] = None contexts['nothing'] = None
contexts['default'] = Default contexts['default'] = Default
...@@ -243,18 +214,10 @@ class Context: ...@@ -243,18 +214,10 @@ class Context:
isinstance=isinstance, StringType=StringType): isinstance=isinstance, StringType=StringType):
if isinstance(expression, StringType): if isinstance(expression, StringType):
expression = self._engine.compile(expression) expression = self._engine.compile(expression)
try: __traceback_supplement__ = (
v = expression(self) TALESTracebackSupplement, self, expression)
except TALESError, err: v = expression(self)
err.setPosition(self.position) return v
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
evaluateValue = evaluate evaluateValue = evaluate
...@@ -276,15 +239,42 @@ class Context: ...@@ -276,15 +239,42 @@ class Context:
return self.evaluate(expr) return self.evaluate(expr)
evaluateMacro = evaluate evaluateMacro = evaluate
def getTALESError(self): def createErrorInfo(self, err, position):
return TALESError return ErrorInfo(err, position)
def getDefault(self): def getDefault(self):
return Default return Default
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position): def setPosition(self, position):
self.position = position self.position = position
class TALESTracebackSupplement:
"""Implementation of ITracebackSupplement"""
def __init__(self, context, expression):
self.context = context
self.source_url = context.source_file
self.line = context.position[0]
self.column = context.position[1]
self.expression = repr(expression)
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: class SimpleExpr:
'''Simple example of an expression type handler''' '''Simple example of an expression type handler'''
def __init__(self, name, expr, engine): def __init__(self, name, expr, engine):
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
Zope object encapsulating a Page Template. Zope object encapsulating a Page Template.
""" """
__version__='$Revision: 1.31 $'[11:-2] __version__='$Revision: 1.32 $'[11:-2]
import os, AccessControl, Acquisition, sys import os, AccessControl, Acquisition, sys
from Globals import DTMLFile, ImageFile, MessageDialog, package_home from Globals import DTMLFile, ImageFile, MessageDialog, package_home
...@@ -35,7 +35,6 @@ from OFS.Cache import Cacheable ...@@ -35,7 +35,6 @@ from OFS.Cache import Cacheable
from OFS.Traversable import Traversable from OFS.Traversable import Traversable
from OFS.PropertyManager import PropertyManager from OFS.PropertyManager import PropertyManager
from PageTemplate import PageTemplate from PageTemplate import PageTemplate
from TALES import TALESError
from Expressions import SecureModuleImporter from Expressions import SecureModuleImporter
from PageTemplateFile import PageTemplateFile from PageTemplateFile import PageTemplateFile
...@@ -187,7 +186,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable, ...@@ -187,7 +186,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
try: try:
self.REQUEST.RESPONSE.setHeader('content-type', self.REQUEST.RESPONSE.setHeader('content-type',
self.content_type) self.content_type)
except AttributeError: pass except AttributeError:
pass
security=getSecurityManager() security=getSecurityManager()
bound_names['user'] = security.getUser() bound_names['user'] = security.getUser()
...@@ -206,15 +206,7 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable, ...@@ -206,15 +206,7 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
# Execute the template in a new security context. # Execute the template in a new security context.
security.addContext(self) security.addContext(self)
try: try:
try: result = self.pt_render(extra_context=bound_names)
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
if keyset is not None: if keyset is not None:
# Store the result in the cache. # Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset) self.ZCacheable_set(result, keywords=keyset)
...@@ -264,6 +256,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable, ...@@ -264,6 +256,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
"""Return a list of icon URLs to be displayed by an ObjectManager""" """Return a list of icon URLs to be displayed by an ObjectManager"""
icons = ({'path': 'misc_/PageTemplates/zpt.gif', icons = ({'path': 'misc_/PageTemplates/zpt.gif',
'alt': self.meta_type, 'title': self.meta_type},) 'alt': self.meta_type, 'title': self.meta_type},)
if not self._v_cooked:
self._cook()
if self._v_errors: if self._v_errors:
icons = icons + ({'path': 'misc_/PageTemplates/exclamation.gif', icons = icons + ({'path': 'misc_/PageTemplates/exclamation.gif',
'alt': 'Error', 'alt': 'Error',
...@@ -271,13 +265,23 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable, ...@@ -271,13 +265,23 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
return icons return icons
def __setstate__(self, state): def __setstate__(self, state):
# This is here for backward compatibility. :-(
ZopePageTemplate.inheritedAttribute('__setstate__')(self, state) ZopePageTemplate.inheritedAttribute('__setstate__')(self, state)
self._cook()
def pt_source_file(self):
"""Returns a file name to be compiled into the TAL code."""
try:
return '/'.join(self.getPhysicalPath())
except:
# This page template is being compiled without an
# acquisition context, so we don't know where it is. :-(
return None
if not SUPPORTS_WEBDAV_LOCKS: if not SUPPORTS_WEBDAV_LOCKS:
def wl_isLocked(self): def wl_isLocked(self):
return 0 return 0
class Src(Acquisition.Explicit): class Src(Acquisition.Explicit):
" " " "
...@@ -295,6 +299,7 @@ class Src(Acquisition.Explicit): ...@@ -295,6 +299,7 @@ class Src(Acquisition.Explicit):
d = ZopePageTemplate.__dict__ d = ZopePageTemplate.__dict__
d['source.xml'] = d['source.html'] = Src() d['source.xml'] = d['source.html'] = Src()
# Product registration and Add support # Product registration and Add support
manage_addPageTemplateForm = PageTemplateFile( manage_addPageTemplateForm = PageTemplateFile(
'www/ptAdd', globals(), __name__='manage_addPageTemplateForm') 'www/ptAdd', globals(), __name__='manage_addPageTemplateForm')
...@@ -322,9 +327,13 @@ def manage_addPageTemplate(self, id, title=None, text=None, ...@@ -322,9 +327,13 @@ def manage_addPageTemplate(self, id, title=None, text=None,
self._setObject(id, zpt) self._setObject(id, zpt)
try: u = self.DestinationURL() try:
except: u = REQUEST['URL1'] u = self.DestinationURL()
if submit==" Add and Edit ": u="%s/%s" % (u,quote(id)) except AttributeError:
u = REQUEST['URL1']
if submit == " Add and Edit ":
u = "%s/%s" % (u, quote(id))
REQUEST.RESPONSE.redirect(u+'/manage_main') REQUEST.RESPONSE.redirect(u+'/manage_main')
return '' return ''
......
...@@ -17,7 +17,7 @@ This product provides support for Script objects containing restricted ...@@ -17,7 +17,7 @@ This product provides support for Script objects containing restricted
Python code. Python code.
""" """
__version__='$Revision: 1.40 $'[11:-2] __version__='$Revision: 1.41 $'[11:-2]
import sys, os, traceback, re, marshal import sys, os, traceback, re, marshal
from Globals import DTMLFile, MessageDialog, package_home from Globals import DTMLFile, MessageDialog, package_home
...@@ -247,7 +247,7 @@ class PythonScript(Script, Historical, Cacheable): ...@@ -247,7 +247,7 @@ class PythonScript(Script, Historical, Cacheable):
'_getattr_': guarded_getattr, '_getattr_': guarded_getattr,
'_getitem_': guarded_getitem, '_getitem_': guarded_getitem,
'_write_': full_write_guard, '_write_': full_write_guard,
'_print_': RestrictedPython.PrintCollector '_print_': RestrictedPython.PrintCollector,
} }
l = {} l = {}
exec code in g, l exec code in g, l
...@@ -286,15 +286,24 @@ class PythonScript(Script, Historical, Cacheable): ...@@ -286,15 +286,24 @@ class PythonScript(Script, Historical, Cacheable):
# Got a cached value. # Got a cached value.
return result return result
__traceback_info__ = bound_names, args, kw, self.func_defaults #__traceback_info__ = bound_names, args, kw, self.func_defaults
f = self._v_f f = self._v_f
if f is None: if f is None:
__traceback_supplement__ = (
PythonScriptTracebackSupplement, self)
raise RuntimeError, '%s %s has errors.' % (self.meta_type, self.id) raise RuntimeError, '%s %s has errors.' % (self.meta_type, self.id)
if bound_names is not None: if bound_names is not None:
# Updating func_globals directly *should* be thread-safe. # XXX This causes the whole acquisition chain
# to be held by self._v_f. I think we really should
# use new.function() instead, similar to
# CMFCore.FSPythonScript. new.function() takes
# about 8 microseconds on a 1 GHz Athlon. - Shane
f.func_globals.update(bound_names) f.func_globals.update(bound_names)
f.func_globals['__traceback_supplement__'] = (
PythonScriptTracebackSupplement, self, -1)
# Execute the function in a new security context. # Execute the function in a new security context.
security=getSecurityManager() security=getSecurityManager()
security.addContext(self) security.addContext(self)
...@@ -467,6 +476,15 @@ class PythonScript(Script, Historical, Cacheable): ...@@ -467,6 +476,15 @@ class PythonScript(Script, Historical, Cacheable):
RESPONSE.setHeader('Content-Type', 'text/plain') RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read() return self.read()
class PythonScriptTracebackSupplement:
"""Implementation of ITracebackSupplement"""
def __init__(self, script, line=0):
self.object = script
# If line is set to -1, it means to use tb_lineno.
self.line = line
_first_indent = re.compile('(?m)^ *(?! |$)') _first_indent = re.compile('(?m)^ *(?! |$)')
_nonempty_line = re.compile('(?m)^(.*\S.*)$') _nonempty_line = re.compile('(?m)^(.*\S.*)$')
......
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Site error log module.
$Id: SiteErrorLog.py,v 1.2 2002/04/03 20:43:55 shane Exp $
"""
import os
import sys
import time
from random import random
from thread import allocate_lock
from types import StringType, UnicodeType
import Globals
from Acquisition import aq_base
from AccessControl import ClassSecurityInfo, getSecurityManager, Unauthorized
from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from zExceptions.ExceptionFormatter import format_exception
from zLOG import LOG, ERROR
use_error_logging = 'Log Site Errors'
log_to_event_log = 'Log to the Event Log'
_www = os.path.join(os.path.dirname(__file__), 'www')
# temp_logs holds the logs.
temp_logs = {} # { oid -> [ traceback string ] }
cleanup_lock = allocate_lock()
class SiteErrorLog (SimpleItem):
"""Site error log class. You can put an error log anywhere in the tree
and exceptions in that area will be posted to the site error log.
"""
meta_type = 'Site Error Log'
id = 'error_log'
keep_entries = 20
copy_to_zlog = 0
security = ClassSecurityInfo()
manage_options = (
{'label': 'Log', 'action': 'manage_main'},
) + SimpleItem.manage_options
security.declareProtected(use_error_logging, 'getProperties')
manage_main = PageTemplateFile('main.pt', _www)
security.declareProtected(use_error_logging, 'showEntry')
showEntry = PageTemplateFile('showEntry.pt', _www)
security.declarePrivate('manage_beforeDelete')
def manage_beforeDelete(self, item, container):
if item is self:
try:
del container.__error_log__
except AttributeError:
pass
security.declarePrivate('manage_afterAdd')
def manage_afterAdd(self, item, container):
if item is self:
container.__error_log__ = aq_base(self)
def _setId(self, id):
if id != self.id:
raise Globals.MessageDialog(
title='Invalid Id',
message='Cannot change the id of a SiteErrorLog',
action ='./manage_main',)
def _getLog(self):
"""Returns the log for this object.
Careful, the log is shared between threads.
"""
log = temp_logs.get(self._p_oid, None)
if log is None:
log = []
temp_logs[self._p_oid] = log
return log
security.declarePrivate('raising')
def raising(self, info):
"""Log an exception.
Called by SimpleItem's exception handler.
"""
try:
now = time.time()
try:
tb_text = None
tb_html = None
if not isinstance(info[2], StringType) and not isinstance(
info[2], UnicodeType):
tb_text = ''.join(
format_exception(*info, **{'as_html': 0}))
tb_html = ''.join(
format_exception(*info, **{'as_html': 1}))
else:
tb_text = info[2]
request = getattr(self, 'REQUEST', None)
url = None
username = None
req_html = None
if request:
url = request['URL']
username = getSecurityManager().getUser().getUserName()
try:
req_html = str(request)
except:
pass
log = self._getLog()
log.append({
'type': str(getattr(info[0], '__name__', info[0])),
'value': str(info[1]),
'time': now,
'id': str(now) + str(random()), # Low chance of collision
'tb_text': tb_text,
'tb_html': tb_html,
'username': username,
'url': url,
'req_html': req_html,
})
cleanup_lock.acquire()
try:
if len(log) >= self.keep_entries:
del log[:-self.keep_entries]
finally:
cleanup_lock.release()
except:
LOG('SiteError', ERROR, 'Error while logging',
error=sys.exc_info())
else:
if self.copy_to_zlog:
LOG('SiteError', ERROR, str(url), error=info)
finally:
info = None
security.declareProtected(use_error_logging, 'getProperties')
def getProperties(self):
return {'keep_entries': self.keep_entries,
'copy_to_zlog': self.copy_to_zlog}
security.declareProtected(log_to_event_log, 'checkEventLogPermission')
def checkEventLogPermission(self):
if not getSecurityManager().checkPermission(log_to_event_log, self):
raise Unauthorized, ('You do not have the "%s" permission.' %
log_to_event_log)
return 1
security.declareProtected(use_error_logging, 'setProperties')
def setProperties(self, keep_entries, copy_to_zlog=0, RESPONSE=None):
"""Sets the properties of this site error log.
"""
copy_to_zlog = not not copy_to_zlog
if copy_to_zlog and not self.copy_to_zlog:
# Before turning on event logging, check the permission.
self.checkEventLogPermission()
self.keep_entries = int(keep_entries)
self.copy_to_zlog = copy_to_zlog
if RESPONSE is not None:
RESPONSE.redirect(
'%s/manage_main?manage_tabs_message=Changed+properties.' %
self.absolute_url())
security.declareProtected(use_error_logging, 'getLogEntries')
def getLogEntries(self):
"""Returns the entries in the log.
Makes a copy to prevent changes.
"""
# List incomprehension ;-)
return [entry.copy() for entry in self._getLog()]
security.declareProtected(use_error_logging, 'getLogEntryById')
def getLogEntryById(self, id):
"""Returns the specified log entry.
Makes a copy to prevent changes. Returns None if not found.
"""
for entry in self._getLog():
if entry['id'] == id:
return entry.copy()
return None
Globals.InitializeClass(SiteErrorLog)
def manage_addErrorLog(dispatcher, RESPONSE=None):
"""Add a site error log to a container."""
log = SiteErrorLog()
dispatcher._setObject(log.id, log)
if RESPONSE is not None:
RESPONSE.redirect(
dispatcher.DestinationURL() +
'/manage_main?manage_tabs_message=Error+Log+Added.' )
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Site error log product.
$Id: __init__.py,v 1.2 2002/04/03 20:43:55 shane Exp $
"""
import SiteErrorLog
def initialize(context):
context.registerClass(SiteErrorLog.SiteErrorLog,
constructors=(SiteErrorLog.manage_addErrorLog,),
permission=SiteErrorLog.use_error_logging,
icon='www/error.gif')
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h1 tal:replace="structure here/manage_tabs">Tabs</h1>
<p class="form-help">
This page lists the exceptions that have occurred in this site
recently. You can configure how many exceptions should be kept
and whether the exceptions should be copied to Zope's event log
file(s).
</p>
<form action="setProperties" method="post">
<table tal:define="props container/getProperties">
<tr>
<td align="left" valign="top">
<div class="form-label">
Number of exceptions to keep
</div>
</td>
<td align="left" valign="top">
<input type="text" name="keep_entries" size="40"
tal:attributes="value props/keep_entries" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Copy exceptions to the event log
</div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="copy_to_zlog"
tal:attributes="checked props/copy_to_zlog;
disabled not:container/checkEventLogPermission|nothing" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Save Changes " />
</div>
</td>
</tr>
</table>
<h3>Exception Log</h3>
<div tal:define="entries container/getLogEntries">
<em tal:condition="not:entries">
No exceptions logged.
</em>
<table tal:condition="entries">
<tr>
<th align="left">Time</th>
<th align="left">User</th>
<th align="left">Exception</th>
</tr>
<tr tal:repeat="entry entries">
<td valign="top" nowrap="nowrap">
<span tal:content="python: DateTime(entry['time']).Time()">13:04:41</span>
</td>
<td>
<span tal:content="entry/username">joe</span>
</td>
<td valign="top">
<a href="showEntry" tal:attributes="href string:showEntry?id=${entry/id}"
>
<span tal:content="entry/type">AttributeError</span>:
<span tal:define="value entry/value"
tal:content="python: len(value) < 70 and value or value[:70] + '...'">
Application object has no attribute "zzope"</span>
</a>
</td>
</tr>
</table>
</div>
</form>
<p>
<form action="manage_main" method="GET">
<input type="submit" name="submit" value=" Refresh " />
</form>
</p>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h1 tal:replace="structure here/manage_tabs">Tabs</h1>
<h3>Exception traceback</h3>
<div tal:define="entry python:container.getLogEntryById(request.get('id'))">
<em tal:condition="not:entry">
The specified log entry was not found. It may have expired.
</em>
<div tal:condition="entry">
<table>
<tr>
<th align="left" valign="top">Time</th>
<td tal:content="python: DateTime(entry['time'])"></td>
</tr>
<tr>
<th align="left" valign="top">User</th>
<td tal:content="entry/username">joe</td>
</tr>
<tr>
<th align="left" valign="top">Request URL</th>
<td tal:content="entry/url">http://example.com</td>
</tr>
<tr>
<th align="left" valign="top">Exception Type</th>
<td tal:content="entry/type">AttributeError</td>
</tr>
<tr>
<th align="left" valign="top">Exception Value</th>
<td tal:content="entry/value">zzope</td>
</tr>
</table>
<div tal:condition="python: entry['tb_html'] and
not request.get('show_entry_as_text')">
<div tal:content="structure entry/tb_html">
Traceback
</div>
<p tal:condition="entry/tb_text"><a href="" tal:attributes="href
string:showEntry?id=${entry/id}&show_entry_as_text=1">Display
traceback as text</a></p>
</div>
<div tal:condition="python: not entry['tb_html'] or
request.get('show_entry_as_text')">
<pre tal:content="entry/tb_text">
Traceback
</pre>
<p tal:condition="entry/tb_html"><a href="" tal:attributes="href
string:showEntry?id=${entry/id}">Display
traceback as HTML</a></p>
</div>
<div tal:condition="entry/req_html">
<h3>REQUEST</h3>
<div tal:replace="structure entry/req_html"></div>
</div>
</div>
<p>
<form action="manage_main" method="GET">
<input type="submit" name="submit" value=" Return to log " />
</form>
</p>
</div>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -16,11 +17,10 @@ Dummy TALES engine so that I can test out the TAL implementation. ...@@ -16,11 +17,10 @@ Dummy TALES engine so that I can test out the TAL implementation.
import re import re
import sys import sys
from string import rfind, strip
import driver import driver
from TALDefs import NAME_RE, TALError, TALESError from TALDefs import NAME_RE, TALESError, ErrorInfo
Default = [] Default = []
...@@ -32,6 +32,7 @@ class CompilerError(Exception): ...@@ -32,6 +32,7 @@ class CompilerError(Exception):
class DummyEngine: class DummyEngine:
position = None position = None
source_file = None
def __init__(self, macros=None): def __init__(self, macros=None):
if macros is None: if macros is None:
...@@ -44,6 +45,9 @@ class DummyEngine: ...@@ -44,6 +45,9 @@ class DummyEngine:
def getCompilerError(self): def getCompilerError(self):
return CompilerError return CompilerError
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position): def setPosition(self, position):
self.position = position self.position = position
...@@ -51,7 +55,8 @@ class DummyEngine: ...@@ -51,7 +55,8 @@ class DummyEngine:
return "$%s$" % expr return "$%s$" % expr
def uncompile(self, expression): def uncompile(self, expression):
assert expression[:1] == "$" == expression[-1:], expression assert (expression.startswith("$") and expression.endswith("$"),
expression)
return expression[1:-1] return expression[1:-1]
def beginScope(self): def beginScope(self):
...@@ -71,7 +76,8 @@ class DummyEngine: ...@@ -71,7 +76,8 @@ class DummyEngine:
self.globals[name] = value self.globals[name] = value
def evaluate(self, expression): def evaluate(self, expression):
assert expression[:1] == "$" == expression[-1:], expression assert (expression.startswith("$") and expression.endswith("$"),
expression)
expression = expression[1:-1] expression = expression[1:-1]
m = name_match(expression) m = name_match(expression)
if m: if m:
...@@ -82,7 +88,7 @@ class DummyEngine: ...@@ -82,7 +88,7 @@ class DummyEngine:
if type in ("string", "str"): if type in ("string", "str"):
return expr return expr
if type in ("path", "var", "global", "local"): if type in ("path", "var", "global", "local"):
expr = strip(expr) expr = expr.strip()
if self.locals.has_key(expr): if self.locals.has_key(expr):
return self.locals[expr] return self.locals[expr]
elif self.globals.has_key(expr): elif self.globals.has_key(expr):
...@@ -97,8 +103,15 @@ class DummyEngine: ...@@ -97,8 +103,15 @@ class DummyEngine:
try: try:
return eval(expr, self.globals, self.locals) return eval(expr, self.globals, self.locals)
except: except:
raise TALESError("evaluation error in %s" % `expr`, raise TALESError("evaluation error in %s" % `expr`)
info=sys.exc_info()) if type == "position":
# Insert the current source file name, line number,
# and column offset.
if self.position:
lineno, offset = self.position
else:
lineno, offset = None, None
return '%s (%s,%s)' % (self.source_file, lineno, offset)
raise TALESError("unrecognized expression: " + `expression`) raise TALESError("unrecognized expression: " + `expression`)
def evaluateValue(self, expr): def evaluateValue(self, expr):
...@@ -122,7 +135,8 @@ class DummyEngine: ...@@ -122,7 +135,8 @@ class DummyEngine:
return self.evaluate(expr) return self.evaluate(expr)
def evaluateMacro(self, macroName): def evaluateMacro(self, macroName):
assert macroName[:1] == "$" == macroName[-1:], macroName assert (macroName.startswith("$") and macroName.endswith("$"),
macroName)
macroName = macroName[1:-1] macroName = macroName[1:-1]
file, localName = self.findMacroFile(macroName) file, localName = self.findMacroFile(macroName)
if not file: if not file:
...@@ -147,7 +161,7 @@ class DummyEngine: ...@@ -147,7 +161,7 @@ class DummyEngine:
def findMacroFile(self, macroName): def findMacroFile(self, macroName):
if not macroName: if not macroName:
raise TALESError("empty macro name") raise TALESError("empty macro name")
i = rfind(macroName, '/') i = macroName.rfind('/')
if i < 0: if i < 0:
# No slash -- must be a locally defined macro # No slash -- must be a locally defined macro
return None, macroName return None, macroName
...@@ -161,8 +175,8 @@ class DummyEngine: ...@@ -161,8 +175,8 @@ class DummyEngine:
seq = self.evaluateSequence(expr) seq = self.evaluateSequence(expr)
return Iterator(name, seq, self) return Iterator(name, seq, self)
def getTALESError(self): def createErrorInfo(self, err, position):
return TALESError return ErrorInfo(err, position)
def getDefault(self): def getDefault(self):
return Default return Default
......
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -15,7 +16,6 @@ Parse HTML and compile to TALInterpreter intermediate code. ...@@ -15,7 +16,6 @@ Parse HTML and compile to TALInterpreter intermediate code.
""" """
import sys import sys
import string
from TALGenerator import TALGenerator from TALGenerator import TALGenerator
from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError
...@@ -74,7 +74,7 @@ class NestingError(HTMLParseError): ...@@ -74,7 +74,7 @@ class NestingError(HTMLParseError):
% (tagstack[0], endtag)) % (tagstack[0], endtag))
else: else:
msg = ('Open tags <%s> do not match close tag </%s>' msg = ('Open tags <%s> do not match close tag </%s>'
% (string.join(tagstack, '>, <'), endtag)) % ('>, <'.join(tagstack), endtag))
else: else:
msg = 'No tags are open to match </%s>' % endtag msg = 'No tags are open to match </%s>' % endtag
HTMLParseError.__init__(self, msg, position) HTMLParseError.__init__(self, msg, position)
...@@ -235,7 +235,7 @@ class HTMLTALParser(HTMLParser): ...@@ -235,7 +235,7 @@ class HTMLTALParser(HTMLParser):
def scan_xmlns(self, attrs): def scan_xmlns(self, attrs):
nsnew = {} nsnew = {}
for key, value in attrs: for key, value in attrs:
if key[:6] == "xmlns:": if key.startswith("xmlns:"):
nsnew[key[6:]] = value nsnew[key[6:]] = value
if nsnew: if nsnew:
self.nsstack.append(self.nsdict) self.nsstack.append(self.nsdict)
...@@ -249,7 +249,7 @@ class HTMLTALParser(HTMLParser): ...@@ -249,7 +249,7 @@ class HTMLTALParser(HTMLParser):
def fixname(self, name): def fixname(self, name):
if ':' in name: if ':' in name:
prefix, suffix = string.split(name, ':', 1) prefix, suffix = name.split(':', 1)
if prefix == 'xmlns': if prefix == 'xmlns':
nsuri = self.nsdict.get(suffix) nsuri = self.nsdict.get(suffix)
if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS): if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS):
......
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -66,27 +67,22 @@ class METALError(TALError): ...@@ -66,27 +67,22 @@ class METALError(TALError):
pass pass
class TALESError(TALError): class TALESError(TALError):
pass
class ErrorInfo:
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]
# This exception can carry around another exception + traceback
def takeTraceback(self):
t = self.info[2]
self.info = self.info[:2] + (None,)
return t
def __init__(self, msg, position=(None, None), info=(None, None, None)):
t, v, tb = info
if t:
if issubclass(t, Exception) and t.__module__ == "exceptions":
err = t.__name__
else:
err = str(t)
v = v is not None and str(v)
if v:
err = "%s: %s" % (err, v)
msg = "%s: %s" % (msg, err)
TALError.__init__(self, msg, position)
self.info = info
import re import re
_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S) _attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
...@@ -117,11 +113,10 @@ def parseSubstitution(arg, position=(None, None)): ...@@ -117,11 +113,10 @@ def parseSubstitution(arg, position=(None, None)):
def splitParts(arg): def splitParts(arg):
# Break in pieces at undoubled semicolons and # Break in pieces at undoubled semicolons and
# change double semicolons to singles: # change double semicolons to singles:
import string arg = arg.replace(";;", "\0")
arg = string.replace(arg, ";;", "\0") parts = arg.split(';')
parts = string.split(arg, ';') parts = [p.replace("\0", ";") for p in parts]
parts = map(lambda s, repl=string.replace: repl(s, "\0", ";"), parts) if len(parts) > 1 and not parts[-1].strip():
if len(parts) > 1 and not string.strip(parts[-1]):
del parts[-1] # It ended in a semicolon del parts[-1] # It ended in a semicolon
return parts return parts
...@@ -139,7 +134,7 @@ def getProgramMode(program): ...@@ -139,7 +134,7 @@ def getProgramMode(program):
return None return None
def getProgramVersion(program): def getProgramVersion(program):
if (isinstance(program, ListType) and len(program) >= 2 and if (len(program) >= 2 and
isinstance(program[0], TupleType) and len(program[0]) == 2): isinstance(program[0], TupleType) and len(program[0]) == 2):
opcode, version = program[0] opcode, version = program[0]
if opcode == "version": if opcode == "version":
......
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -14,7 +15,6 @@ ...@@ -14,7 +15,6 @@
Code generator for TALInterpreter intermediate code. Code generator for TALInterpreter intermediate code.
""" """
import string
import re import re
import cgi import cgi
...@@ -24,8 +24,9 @@ class TALGenerator: ...@@ -24,8 +24,9 @@ class TALGenerator:
inMacroUse = 0 inMacroUse = 0
inMacroDef = 0 inMacroDef = 0
source_file = None
def __init__(self, expressionCompiler=None, xml=1): def __init__(self, expressionCompiler=None, xml=1, source_file=None):
if not expressionCompiler: if not expressionCompiler:
from DummyEngine import DummyEngine from DummyEngine import DummyEngine
expressionCompiler = DummyEngine() expressionCompiler = DummyEngine()
...@@ -40,6 +41,9 @@ class TALGenerator: ...@@ -40,6 +41,9 @@ class TALGenerator:
self.xml = xml self.xml = xml
self.emit("version", TAL_VERSION) self.emit("version", TAL_VERSION)
self.emit("mode", xml and "xml" or "html") self.emit("mode", xml and "xml" or "html")
if source_file is not None:
self.source_file = source_file
self.emit("setSourceFile", source_file)
def getCode(self): def getCode(self):
assert not self.stack assert not self.stack
...@@ -78,9 +82,9 @@ class TALGenerator: ...@@ -78,9 +82,9 @@ class TALGenerator:
# instructions to be joined together. # instructions to be joined together.
output.append(self.optimizeArgsList(item)) output.append(self.optimizeArgsList(item))
continue continue
text = string.join(collect, "") text = "".join(collect)
if text: if text:
i = string.rfind(text, "\n") i = text.rfind("\n")
if i >= 0: if i >= 0:
i = len(text) - (i + 1) i = len(text) - (i + 1)
output.append(("rawtextColumn", (text, i))) output.append(("rawtextColumn", (text, i)))
...@@ -272,7 +276,7 @@ class TALGenerator: ...@@ -272,7 +276,7 @@ class TALGenerator:
def emitDefineMacro(self, macroName): def emitDefineMacro(self, macroName):
program = self.popProgram() program = self.popProgram()
macroName = string.strip(macroName) macroName = macroName.strip()
if self.macros.has_key(macroName): if self.macros.has_key(macroName):
raise METALError("duplicate macro definition: %s" % `macroName`, raise METALError("duplicate macro definition: %s" % `macroName`,
self.position) self.position)
...@@ -291,7 +295,7 @@ class TALGenerator: ...@@ -291,7 +295,7 @@ class TALGenerator:
def emitDefineSlot(self, slotName): def emitDefineSlot(self, slotName):
program = self.popProgram() program = self.popProgram()
slotName = string.strip(slotName) slotName = slotName.strip()
if not re.match('%s$' % NAME_RE, slotName): if not re.match('%s$' % NAME_RE, slotName):
raise METALError("invalid slot name: %s" % `slotName`, raise METALError("invalid slot name: %s" % `slotName`,
self.position) self.position)
...@@ -299,7 +303,7 @@ class TALGenerator: ...@@ -299,7 +303,7 @@ class TALGenerator:
def emitFillSlot(self, slotName): def emitFillSlot(self, slotName):
program = self.popProgram() program = self.popProgram()
slotName = string.strip(slotName) slotName = slotName.strip()
if self.slots.has_key(slotName): if self.slots.has_key(slotName):
raise METALError("duplicate fill-slot name: %s" % `slotName`, raise METALError("duplicate fill-slot name: %s" % `slotName`,
self.position) self.position)
...@@ -330,7 +334,7 @@ class TALGenerator: ...@@ -330,7 +334,7 @@ class TALGenerator:
self.program[i] = ("rawtext", text[:m.start()]) self.program[i] = ("rawtext", text[:m.start()])
collect.append(m.group()) collect.append(m.group())
collect.reverse() collect.reverse()
return string.join(collect, "") return "".join(collect)
def unEmitNewlineWhitespace(self): def unEmitNewlineWhitespace(self):
collect = [] collect = []
...@@ -349,7 +353,7 @@ class TALGenerator: ...@@ -349,7 +353,7 @@ class TALGenerator:
break break
text, rest = m.group(1, 2) text, rest = m.group(1, 2)
collect.reverse() collect.reverse()
rest = rest + string.join(collect, "") rest = rest + "".join(collect)
del self.program[i:] del self.program[i:]
if text: if text:
self.emit("rawtext", text) self.emit("rawtext", text)
...@@ -427,6 +431,8 @@ class TALGenerator: ...@@ -427,6 +431,8 @@ class TALGenerator:
if self.inMacroUse: if self.inMacroUse:
if fillSlot: if fillSlot:
self.pushProgram() self.pushProgram()
if self.source_file is not None:
self.emit("setSourceFile", self.source_file)
todo["fillSlot"] = fillSlot todo["fillSlot"] = fillSlot
self.inMacroUse = 0 self.inMacroUse = 0
else: else:
...@@ -438,6 +444,8 @@ class TALGenerator: ...@@ -438,6 +444,8 @@ class TALGenerator:
self.pushProgram() self.pushProgram()
self.emit("version", TAL_VERSION) self.emit("version", TAL_VERSION)
self.emit("mode", self.xml and "xml" or "html") self.emit("mode", self.xml and "xml" or "html")
if self.source_file is not None:
self.emit("setSourceFile", self.source_file)
todo["defineMacro"] = defineMacro todo["defineMacro"] = defineMacro
self.inMacroDef = self.inMacroDef + 1 self.inMacroDef = self.inMacroDef + 1
if useMacro: if useMacro:
......
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -85,7 +86,6 @@ class TALInterpreter: ...@@ -85,7 +86,6 @@ class TALInterpreter:
self.program = program self.program = program
self.macros = macros self.macros = macros
self.engine = engine self.engine = engine
self.TALESError = engine.getTALESError()
self.Default = engine.getDefault() self.Default = engine.getDefault()
self.stream = stream or sys.stdout self.stream = stream or sys.stdout
self._stream_write = self.stream.write self._stream_write = self.stream.write
...@@ -112,6 +112,7 @@ class TALInterpreter: ...@@ -112,6 +112,7 @@ class TALInterpreter:
self.col = 0 self.col = 0
self.level = 0 self.level = 0
self.scopeLevel = 0 self.scopeLevel = 0
self.sourceFile = None
def saveState(self): def saveState(self):
return (self.position, self.col, self.stream, return (self.position, self.col, self.stream,
...@@ -201,6 +202,11 @@ class TALInterpreter: ...@@ -201,6 +202,11 @@ class TALInterpreter:
self.endlen = len(self.endsep) self.endlen = len(self.endsep)
bytecode_handlers["mode"] = do_mode bytecode_handlers["mode"] = do_mode
def do_setSourceFile(self, source_file):
self.sourceFile = source_file
self.engine.setSourceFile(source_file)
bytecode_handlers["setSourceFile"] = do_setSourceFile
def do_setPosition(self, position): def do_setPosition(self, position):
self.position = position self.position = position
self.engine.setPosition(position) self.engine.setPosition(position)
...@@ -515,7 +521,12 @@ class TALInterpreter: ...@@ -515,7 +521,12 @@ class TALInterpreter:
raise METALError("macro %s has incompatible mode %s" % raise METALError("macro %s has incompatible mode %s" %
(`macroName`, `mode`), self.position) (`macroName`, `mode`), self.position)
self.pushMacro(macroName, compiledSlots) self.pushMacro(macroName, compiledSlots)
saved_source = self.sourceFile
saved_position = self.position # Used by Boa Constructor
self.interpret(macro) self.interpret(macro)
if self.sourceFile != saved_source:
self.engine.setSourceFile(saved_source)
self.sourceFile = saved_source
self.popMacro() self.popMacro()
bytecode_handlers["useMacro"] = do_useMacro bytecode_handlers["useMacro"] = do_useMacro
...@@ -531,10 +542,15 @@ class TALInterpreter: ...@@ -531,10 +542,15 @@ class TALInterpreter:
return return
macs = self.macroStack macs = self.macroStack
if macs and macs[-1] is not None: if macs and macs[-1] is not None:
saved_source = self.sourceFile
saved_position = self.position # Used by Boa Constructor
macroName, slots = self.popMacro()[:2] macroName, slots = self.popMacro()[:2]
slot = slots.get(slotName) slot = slots.get(slotName)
if slot is not None: if slot is not None:
self.interpret(slot) self.interpret(slot)
if self.sourceFile != saved_source:
self.engine.setSourceFile(saved_source)
self.sourceFile = saved_source
self.pushMacro(macroName, slots, entering=0) self.pushMacro(macroName, slots, entering=0)
return return
self.pushMacro(macroName, slots) self.pushMacro(macroName, slots)
...@@ -553,16 +569,16 @@ class TALInterpreter: ...@@ -553,16 +569,16 @@ class TALInterpreter:
self._stream_write = stream.write self._stream_write = stream.write
try: try:
self.interpret(block) self.interpret(block)
except self.TALESError, err: except:
exc = sys.exc_info()[1]
self.restoreState(state) self.restoreState(state)
engine = self.engine engine = self.engine
engine.beginScope() engine.beginScope()
err.lineno, err.offset = self.position error = engine.createErrorInfo(exc, self.position)
engine.setLocal('error', err) engine.setLocal('error', error)
try: try:
self.interpret(handler) self.interpret(handler)
finally: finally:
err.takeTraceback()
engine.endScope() engine.endScope()
else: else:
self.restoreOutputState(state) self.restoreOutputState(state)
......
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -14,7 +15,6 @@ ...@@ -14,7 +15,6 @@
Parse XML and compile to TALInterpreter intermediate code. Parse XML and compile to TALInterpreter intermediate code.
""" """
import string
from XMLParser import XMLParser from XMLParser import XMLParser
from TALDefs import * from TALDefs import *
from TALGenerator import TALGenerator from TALGenerator import TALGenerator
...@@ -99,7 +99,7 @@ class TALParser(XMLParser): ...@@ -99,7 +99,7 @@ class TALParser(XMLParser):
def fixname(self, name): def fixname(self, name):
if ' ' in name: if ' ' in name:
uri, name = string.split(name, ' ') uri, name = name.split(' ')
prefix = self.nsDict[uri] prefix = self.nsDict[uri]
prefixed = name prefixed = name
if prefix: if prefix:
......
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
......
"""Empty file to make this directory a Python package.""" ##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
""" Template Attribute Language package """
#! /usr/bin/env python1.5 #!/usr/bin/env python
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -17,7 +18,6 @@ Driver program to test METAL and TAL implementation. ...@@ -17,7 +18,6 @@ Driver program to test METAL and TAL implementation.
import os import os
import sys import sys
import string
import getopt import getopt
...@@ -93,7 +93,7 @@ def compilefile(file, mode=None): ...@@ -93,7 +93,7 @@ def compilefile(file, mode=None):
assert mode in ("html", "xml", None) assert mode in ("html", "xml", None)
if mode is None: if mode is None:
ext = os.path.splitext(file)[1] ext = os.path.splitext(file)[1]
if string.lower(ext) in (".html", ".htm"): if ext.lower() in (".html", ".htm"):
mode = "html" mode = "html"
else: else:
mode = "xml" mode = "xml"
......
#! /usr/bin/env python1.5 #! /usr/bin/env python
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
'''Run benchmarks of TAL vs. DTML''' '''Run benchmarks of TAL vs. DTML'''
......
...@@ -97,7 +97,6 @@ __version__ = 1, 5, 0 ...@@ -97,7 +97,6 @@ __version__ = 1, 5, 0
# is sent to stdout. Or you can call main(args), passing what would # is sent to stdout. Or you can call main(args), passing what would
# have been in sys.argv[1:] had the cmd-line form been used. # have been in sys.argv[1:] had the cmd-line form been used.
import string
TRACE = 0 TRACE = 0
# define what "junk" means # define what "junk" means
......
#! /usr/bin/env python1.5 #! /usr/bin/env python
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
...@@ -17,7 +18,6 @@ Driver program to run METAL and TAL regression tests. ...@@ -17,7 +18,6 @@ Driver program to run METAL and TAL regression tests.
import sys import sys
import os import os
import string
from cStringIO import StringIO from cStringIO import StringIO
import glob import glob
import traceback import traceback
...@@ -57,7 +57,7 @@ def main(): ...@@ -57,7 +57,7 @@ def main():
if args and args[0] == "-Q": if args and args[0] == "-Q":
unittesting = 1 unittesting = 1
del args[0] del args[0]
while args and args[0][:1] == '-': while args and args[0].startswith('-'):
opts.append(args[0]) opts.append(args[0])
del args[0] del args[0]
if not args: if not args:
...@@ -76,12 +76,12 @@ def main(): ...@@ -76,12 +76,12 @@ def main():
errors = 0 errors = 0
for arg in args: for arg in args:
locopts = [] locopts = []
if string.find(arg, "metal") >= 0 and "-m" not in opts: if arg.find("metal") >= 0 and "-m" not in opts:
locopts.append("-m") locopts.append("-m")
if not unittesting: if not unittesting:
print arg, print arg,
sys.stdout.flush() sys.stdout.flush()
if tests.utils.skipxml and arg[-4:] == ".xml": if tests.utils.skipxml and arg.endswith(".xml"):
print "SKIPPED (XML parser not available)" print "SKIPPED (XML parser not available)"
continue continue
save = sys.stdout, sys.argv save = sys.stdout, sys.argv
...@@ -109,7 +109,7 @@ def main(): ...@@ -109,7 +109,7 @@ def main():
continue continue
head, tail = os.path.split(arg) head, tail = os.path.split(arg)
outfile = os.path.join( outfile = os.path.join(
string.replace(head, "input", "output"), head.replace("input", "output"),
tail) tail)
try: try:
f = open(outfile) f = open(outfile)
......
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
""" """
Read a module search path from .path. Read a module search path from .path.
""" """
import os import os
import sys import sys
import string
dir = os.path.dirname(__file__) dir = os.path.dirname(__file__)
path = os.path.join(dir, ".path") path = os.path.join(dir, ".path")
...@@ -13,7 +19,7 @@ except IOError: ...@@ -13,7 +19,7 @@ except IOError:
raise IOError, "Please edit .path to point to <Zope2/lib/python>" raise IOError, "Please edit .path to point to <Zope2/lib/python>"
else: else:
for line in f.readlines(): for line in f.readlines():
line = string.strip(line) line = line.strip()
if line and line[0] != '#': if line and line[0] != '#':
for dir in string.split(line, os.pathsep): for dir in string.split(line, os.pathsep):
dir = os.path.expanduser(os.path.expandvars(dir)) dir = os.path.expanduser(os.path.expandvars(dir))
......
...@@ -8,6 +8,7 @@ import test_htmlparser ...@@ -8,6 +8,7 @@ import test_htmlparser
import test_htmltalparser import test_htmltalparser
import test_talinterpreter import test_talinterpreter
import test_files import test_files
import test_sourcepos
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
...@@ -18,6 +19,7 @@ def test_suite(): ...@@ -18,6 +19,7 @@ def test_suite():
suite.addTest(test_xmlparser.test_suite()) suite.addTest(test_xmlparser.test_suite())
suite.addTest(test_talinterpreter.test_suite()) suite.addTest(test_talinterpreter.test_suite())
suite.addTest(test_files.test_suite()) suite.addTest(test_files.test_suite())
suite.addTest(test_sourcepos.test_suite())
return suite return suite
def main(): def main():
......
#! /usr/bin/env python
"""Tests for TALInterpreter."""
import sys
import unittest
from StringIO import StringIO
from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALInterpreter import TALInterpreter
from TAL.TALGenerator import TALGenerator
from TAL.DummyEngine import DummyEngine
page1 = '''<html metal:use-macro="main"><body>
<div metal:fill-slot="body">
page1=<span tal:replace="position:" />
</div>
</body></html>'''
main_template = '''<html metal:define-macro="main"><body>
main_template=<span tal:replace="position:" />
<div metal:define-slot="body" />
main_template=<span tal:replace="position:" />
<div metal:use-macro="foot" />
main_template=<span tal:replace="position:" />
</body></html>'''
footer = '''<div metal:define-macro="foot">
footer=<span tal:replace="position:" />
</div>'''
expected = '''<html><body>
main_template=main_template (2,14)
<div>
page1=page1 (3,6)
</div>
main_template=main_template (4,14)
<div>
footer=footer (2,7)
</div>
main_template=main_template (6,14)
</body></html>'''
class Tests(unittest.TestCase):
def parse(self, eng, s, fn):
gen = TALGenerator(expressionCompiler=eng, xml=0, source_file=fn)
parser = HTMLTALParser(gen)
parser.parseString(s)
program, macros = parser.getCode()
return program, macros
def testSourcePositions(self):
"""Ensure source file and position are set correctly by TAL"""
macros = {}
eng = DummyEngine(macros)
page1_program, page1_macros = self.parse(eng, page1, 'page1')
main_template_program, main_template_macros = self.parse(
eng, main_template, 'main_template')
footer_program, footer_macros = self.parse(eng, footer, 'footer')
macros['main'] = main_template_macros['main']
macros['foot'] = footer_macros['foot']
stream = StringIO()
interp = TALInterpreter(page1_program, macros, eng, stream)
interp()
self.assertEqual(stream.getvalue().strip(), expected.strip(),
stream.getvalue())
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Tests))
return suite
if __name__ == "__main__":
unittest.main()
#! /usr/bin/env python1.5 #! /usr/bin/env python
############################################################################## ##############################################################################
# #
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
......
...@@ -92,9 +92,9 @@ def stupid_log_write(subsystem, severity, summary, detail, error): ...@@ -92,9 +92,9 @@ def stupid_log_write(subsystem, severity, summary, detail, error):
if error: if error:
try: try:
_stupid_dest.write(format_exception( _stupid_dest.write(''.join(format_exception(
error[0], error[1], error[2], error[0], error[1], error[2],
trailer='\n', limit=100)) limit=100)))
except: except:
_stupid_dest.write("%s: %s\n" % error[:2]) _stupid_dest.write("%s: %s\n" % error[:2])
_stupid_dest.flush() _stupid_dest.flush()
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
############################################################################## ##############################################################################
__version__='$Revision: 1.63 $'[11:-2] __version__='$Revision: 1.64 $'[11:-2]
import re, sys, os, urllib, time, whrandom, cgi, codecs import re, sys, os, urllib, time, whrandom, cgi, codecs
from BaseRequest import BaseRequest from BaseRequest import BaseRequest
...@@ -934,7 +934,8 @@ class HTTPRequest(BaseRequest): ...@@ -934,7 +934,8 @@ class HTTPRequest(BaseRequest):
result=result + row % (escape(k), escape(repr(v))) result=result + row % (escape(k), escape(repr(v)))
return result+"</table>" return result+"</table>"
__repr__=__str__ def __repr__(self):
return "<%s, URL=%s>" % (self.__class__.__name__, self['URL'])
def text(self): def text(self):
result="FORM\n\n" result="FORM\n\n"
......
...@@ -12,17 +12,24 @@ ...@@ -12,17 +12,24 @@
############################################################################## ##############################################################################
'''CGI Response Output formatter '''CGI Response Output formatter
$Id: HTTPResponse.py,v 1.55 2002/03/27 10:14:04 htrd Exp $''' $Id: HTTPResponse.py,v 1.56 2002/04/03 20:43:59 shane Exp $'''
__version__='$Revision: 1.55 $'[11:-2] __version__='$Revision: 1.56 $'[11:-2]
import types, os, sys, re import types, os, sys, re
from string import translate, maketrans from string import translate, maketrans
from types import StringType, InstanceType, LongType, UnicodeType from types import StringType, InstanceType, LongType, UnicodeType
from BaseResponse import BaseResponse from BaseResponse import BaseResponse
from zExceptions import Unauthorized from zExceptions import Unauthorized
from zExceptions.ExceptionFormatter import format_exception
nl2sp=maketrans('\n',' ') nl2sp=maketrans('\n',' ')
# Enable APPEND_TRACEBACKS to make Zope append tracebacks like it used to,
# but a better solution is to make standard_error_message display error_tb.
APPEND_TRACEBACKS = 0
status_reasons={ status_reasons={
100: 'Continue', 100: 'Continue',
101: 'Switching Protocols', 101: 'Switching Protocols',
...@@ -92,16 +99,6 @@ start_of_header_search=re.compile('(<head[^>]*>)', re.IGNORECASE).search ...@@ -92,16 +99,6 @@ start_of_header_search=re.compile('(<head[^>]*>)', re.IGNORECASE).search
accumulate_header={'set-cookie': 1}.has_key accumulate_header={'set-cookie': 1}.has_key
tb_style = os.environ.get('HTTP_TRACEBACK_STYLE', '').lower()
if tb_style == 'none':
tb_delims = None, None
elif tb_style == 'js':
tb_delims = ('''<pre onclick="this.firstChild.data=this.lastChild.data">
&sect;<!--''', '--></pre>')
elif tb_style == 'plain':
tb_delims = '<pre>', '</pre>'
else:
tb_delims = '<!--', '-->'
class HTTPResponse(BaseResponse): class HTTPResponse(BaseResponse):
"""\ """\
...@@ -384,8 +381,15 @@ class HTTPResponse(BaseResponse): ...@@ -384,8 +381,15 @@ class HTTPResponse(BaseResponse):
else: h=value else: h=value
self.setHeader(name,h) self.setHeader(name,h)
def isHTML(self,str): def isHTML(self, s):
return str.strip().lower()[:6] == '<html>' or str.find('</') > 0 s = s.lstrip()
# Note that the string can be big, so s.lower().startswith() is more
# expensive than s[:n].lower().
if (s[:6].lower() == '<html>' or s[:14].lower() == '<!doctype html'):
return 1
if s.find('</') > 0:
return 1
return 0
def quoteHTML(self,text, def quoteHTML(self,text,
subs={'&':'&amp;', "<":'&lt;', ">":'&gt;', '\"':'&quot;'} subs={'&':'&amp;', "<":'&lt;', ">":'&gt;', '\"':'&quot;'}
...@@ -397,42 +401,9 @@ class HTTPResponse(BaseResponse): ...@@ -397,42 +401,9 @@ class HTTPResponse(BaseResponse):
return text return text
def format_exception(self, etype, value, tb, limit=None): def _traceback(self, t, v, tb, as_html=1):
import traceback tb = format_exception(t, v, tb, as_html=as_html)
result=['Traceback (innermost last):'] return '\n'.join(tb)
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals=f.f_locals
result.append(' File %s, line %d, in %s'
% (filename,lineno,name))
try: result.append(' (Object: %s)' %
locals[co.co_varnames[0]].__name__)
except: pass
try: result.append(' (Info: %s)' %
str(locals['__traceback_info__']))
except: pass
tb = tb.tb_next
n = n + 1
result.append(' '.join(traceback.format_exception_only(etype, value)))
return result
def _traceback(self, t, v, tb):
tb = self.format_exception(t, v, tb, 200)
tb = '\n'.join(tb)
tb = self.quoteHTML(tb)
if self.debug_mode: _tbopen, _tbclose = '<PRE>', '</PRE>'
else: _tbopen, _tbclose = tb_delims
if _tbopen is None:
return ''
return "\n%s\n%s\n%s" % (_tbopen, tb, _tbclose)
def redirect(self, location, status=302, lock=0): def redirect(self, location, status=302, lock=0):
"""Cause a redirection without raising an error""" """Cause a redirection without raising an error"""
...@@ -621,20 +592,22 @@ class HTTPResponse(BaseResponse): ...@@ -621,20 +592,22 @@ class HTTPResponse(BaseResponse):
if match is None: if match is None:
body = self.setBody( body = self.setBody(
(str(t), (str(t),
'Sorry, a site error occurred.<p>' 'Sorry, a site error occurred.<p>'
+ self._traceback(t, v, tb)), + self._traceback(t, v, tb)),
is_error=1) is_error=1)
elif b.strip().lower()[:6]=='<html>' or \ elif self.isHTML(b):
b.strip().lower()[:14]=='<!doctype html':
# error is an HTML document, not just a snippet of html # error is an HTML document, not just a snippet of html
body = self.setBody(b + self._traceback(t, '(see above)', tb), if APPEND_TRACEBACKS:
is_error=1) body = self.setBody(b + self._traceback(
t, '(see above)', tb), is_error=1)
else:
body = self.setBody(b, is_error=1)
else: else:
body = self.setBody((str(t), body = self.setBody(
b + self._traceback(t,'(see above)', tb)), (str(t), b + self._traceback(t,'(see above)', tb, 0)),
is_error=1) is_error=1)
del tb del tb
return body return body
_wrote=None _wrote=None
......
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""An exception formatter that shows traceback supplements and traceback info,
optionally in HTML.
$Id: ExceptionFormatter.py,v 1.2 2002/04/03 20:43:59 shane Exp $
"""
import sys
import cgi
DEBUG_EXCEPTION_FORMATTER = 1
class TextExceptionFormatter:
line_sep = '\n'
show_revisions = 0
def __init__(self, limit=None):
self.limit = limit
def escape(self, s):
return s
def getPrefix(self):
return 'Traceback (innermost last):'
def getLimit(self):
limit = self.limit
if limit is None:
limit = getattr(sys, 'tracebacklimit', None)
return limit
def getRevision(self, globals):
if not self.show_revisions:
return None
revision = globals.get('__revision__', None)
if revision is None:
# Incorrect but commonly used spelling
revision = globals.get('__version__', None)
if revision is not None:
try:
revision = str(revision).strip()
except:
revision = '???'
return revision
def formatSupplementLine(self, line):
return ' - %s' % line
def formatObject(self, object):
return [self.formatSupplementLine(repr(object))]
def formatSourceURL(self, url):
return [self.formatSupplementLine('URL: %s' % url)]
def formatSupplement(self, supplement, tb):
result = []
fmtLine = self.formatSupplementLine
object = getattr(supplement, 'object', None)
if object is not None:
result.extend(self.formatObject(object))
url = getattr(supplement, 'source_url', None)
if url is not None:
result.extend(self.formatSourceURL(url))
line = getattr(supplement, 'line', 0)
if line == -1:
line = tb.tb_lineno
col = getattr(supplement, 'column', -1)
if line:
if col is not None and col >= 0:
result.append(fmtLine('Line %s, Column %s' % (
line, col)))
else:
result.append(fmtLine('Line %s' % line))
elif col is not None and col >= 0:
result.append(fmtLine('Column %s' % col))
expr = getattr(supplement, 'expression', None)
if expr:
result.append(fmtLine('Expression: %s' % expr))
warnings = getattr(supplement, 'warnings', None)
if warnings:
for warning in warnings:
result.append(fmtLine('Warning: %s' % warning))
extra = self.formatExtraInfo(supplement)
if extra:
result.append(extra)
return result
def formatExtraInfo(self, supplement):
getInfo = getattr(supplement, 'getInfo', None)
if getInfo is not None:
extra = getInfo()
if extra:
return extra
return None
def formatTracebackInfo(self, tbi):
return self.formatSupplementLine('__traceback_info__: %s' % tbi)
def formatLine(self, tb):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals = f.f_locals
globals = f.f_globals
modname = globals.get('__name__', filename)
s = ' Module %s, line %d' % (modname, lineno)
revision = self.getRevision(globals)
if revision:
s = s + ', rev. %s' % revision
s = s + ', in %s' % name
result = []
result.append(self.escape(s))
# Output a traceback supplement, if any.
if locals.has_key('__traceback_supplement__'):
# Use the supplement defined in the function.
tbs = locals['__traceback_supplement__']
elif globals.has_key('__traceback_supplement__'):
# Use the supplement defined in the module.
# This is used by Scripts (Python).
tbs = globals['__traceback_supplement__']
else:
tbs = None
if tbs is not None:
factory = tbs[0]
args = tbs[1:]
try:
supp = factory(*args)
result.extend(self.formatSupplement(supp, tb))
except:
if DEBUG_EXCEPTION_FORMATTER:
import traceback
traceback.print_exc()
# else just swallow the exception.
try:
tbi = locals.get('__traceback_info__', None)
if tbi is not None:
result.append(self.formatTracebackInfo(tbi))
except:
pass
return self.line_sep.join(result)
def formatExceptionOnly(self, etype, value):
import traceback
return self.line_sep.join(
traceback.format_exception_only(etype, value))
def formatLastLine(self, exc_line):
return self.escape(exc_line)
def formatException(self, etype, value, tb, limit=None):
# The next line provides a way to detect recursion.
__exception_formatter__ = 1
result = [self.getPrefix() + '\n']
if limit is None:
limit = self.getLimit()
n = 0
while tb is not None and (limit is None or n < limit):
if tb.tb_frame.f_locals.get('__exception_formatter__'):
# Stop recursion.
result.append('(Recursive formatException() stopped)\n')
break
line = self.formatLine(tb)
result.append(line + '\n')
tb = tb.tb_next
n = n + 1
exc_line = self.formatExceptionOnly(etype, value)
result.append(self.formatLastLine(exc_line))
return result
class HTMLExceptionFormatter (TextExceptionFormatter):
line_sep = '<br />\r\n'
def escape(self, s):
return cgi.escape(s)
def getPrefix(self):
return '<p>Traceback (innermost last):\r\n<ul>'
def formatSupplementLine(self, line):
return '<b>%s</b>' % self.escape(str(line))
def formatTracebackInfo(self, tbi):
s = self.escape(str(tbi))
s = s.replace('\n', self.line_sep)
return '__traceback_info__: %s' % s
def formatLine(self, tb):
line = TextExceptionFormatter.formatLine(self, tb)
return '<li>%s</li>' % line
def formatLastLine(self, exc_line):
return '</ul>%s</p>' % self.escape(exc_line)
def formatExtraInfo(self, supplement):
getInfo = getattr(supplement, 'getInfo', None)
if getInfo is not None:
extra = getInfo(1)
if extra:
return extra
return None
limit = 200
if hasattr(sys, 'tracebacklimit'):
limit = min(limit, sys.tracebacklimit)
text_formatter = TextExceptionFormatter(limit)
html_formatter = HTMLExceptionFormatter(limit)
def format_exception(t, v, tb, limit=None, as_html=0):
if as_html:
fmt = html_formatter
else:
fmt = text_formatter
return fmt.formatException(t, v, tb, limit=limit)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ITracebackSupplement interface definition.
$Id: ITracebackSupplement.py,v 1.2 2002/04/03 20:44:00 shane Exp $
"""
from Interface import Interface
from Interface.Attribute import Attribute
class ITracebackSupplement(Interface):
"""Provides valuable information to supplement an exception traceback.
The interface is geared toward providing meaningful feedback when
exceptions occur in user code written in mini-languages like
Zope page templates and restricted Python scripts.
"""
source_url = Attribute(
'source_url',
"""Optional. Set to URL of the script where the exception occurred.
Normally this generates a URL in the traceback that the user
can visit to manage the object. Set to None if unknown or
not available.
"""
)
object = Attribute(
'object',
"""Optional. Set to the script or template where the exception
occurred.
Set to None if unknown or not available.
"""
)
line = Attribute(
'line',
"""Optional. Set to the line number (>=1) where the exception
occurred.
Set to 0 or None if the line number is unknown.
"""
)
column = Attribute(
'column',
"""Optional. Set to the column offset (>=0) where the exception
occurred.
Set to None if the column number is unknown.
"""
)
expression = Attribute(
'expression',
"""Optional. Set to the expression that was being evaluated.
Set to None if not available or not applicable.
"""
)
warnings = Attribute(
'warnings',
"""Optional. Set to a sequence of warning messages.
Set to None if not available, not applicable, or if the exception
itself provides enough information.
"""
)
def getInfo(as_html=0):
"""Optional. Returns a string containing any other useful info.
If as_html is set, the implementation must HTML-quote the result
(normally using cgi.escape()). Returns None to provide no
extra info.
"""
"""Package for zExceptions tests"""
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
ExceptionFormatter tests.
Revision information:
$Id: testExceptionFormatter.py,v 1.2 2002/04/03 20:44:00 shane Exp $
"""
from __future__ import nested_scopes
from unittest import TestCase, TestSuite, main, makeSuite
try:
from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup
except ImportError:
class CleanUp:
pass
import sys
from zExceptions.ExceptionFormatter import format_exception
def tb(as_html=0):
t, v, b = sys.exc_info()
try:
return ''.join(format_exception(t, v, b, as_html=as_html))
finally:
del b
class ExceptionForTesting (Exception):
pass
class TestingTracebackSupplement:
source_url = '/somepath'
line = 634
column = 57
warnings = ['Repent, for the end is nigh']
def __init__(self, expression):
self.expression = expression
class Test(CleanUp, TestCase):
def testBasicNamesText(self, as_html=0):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = tb(as_html)
# The traceback should include the name of this function.
self.assert_(s.find('testBasicNamesText') >= 0)
# The traceback should include the name of the exception.
self.assert_(s.find('ExceptionForTesting') >= 0)
else:
self.fail('no exception occurred')
def testBasicNamesHTML(self):
self.testBasicNamesText(1)
def testSupplement(self, as_html=0):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = tb(as_html)
# The source URL
self.assert_(s.find('/somepath') >= 0, s)
# The line number
self.assert_(s.find('634') >= 0, s)
# The column number
self.assert_(s.find('57') >= 0, s)
# The expression
self.assert_(s.find("You're one in a million") >= 0, s)
# The warning
self.assert_(s.find("Repent, for the end is nigh") >= 0, s)
else:
self.fail('no exception occurred')
def testSupplementHTML(self):
self.testSupplement(1)
def testTracebackInfo(self, as_html=0):
try:
__traceback_info__ = "Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = tb(as_html)
if as_html:
# Be sure quoting is happening.
self.assert_(s.find('Adam &amp; Eve') >= 0, s)
else:
self.assert_(s.find('Adam & Eve') >= 0, s)
else:
self.fail('no exception occurred')
def testTracebackInfoHTML(self):
self.testTracebackInfo(1)
def testMultipleLevels(self):
# Makes sure many levels are shown in a traceback.
def f(n):
"""Produces a (n + 1)-level traceback."""
__traceback_info__ = 'level%d' % n
if n > 0:
f(n - 1)
else:
raise ExceptionForTesting
try:
f(10)
except ExceptionForTesting:
s = tb()
for n in range(11):
self.assert_(s.find('level%d' % n) >= 0, s)
else:
self.fail('no exception occurred')
def testQuoteLastLine(self):
class C: pass
try: raise TypeError, C()
except:
s = tb(1)
else:
self.fail('no exception occurred')
self.assert_(s.find('&lt;') >= 0, s)
self.assert_(s.find('&gt;') >= 0, s)
def test_suite():
return TestSuite((
makeSuite(Test),
))
if __name__=='__main__':
main(defaultTest='test_suite')
...@@ -11,43 +11,11 @@ ...@@ -11,43 +11,11 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
__version__='$Revision: 1.5 $'[11:-2] __version__='$Revision: 1.6 $'[11:-2]
import sys try:
# Use the exception formatter in zExceptions
format_exception_only = None from zExceptions.ExceptionFormatter import format_exception
except ImportError:
def format_exception(etype, value, tb, limit=None, delimiter='\n', # Not available. Use the basic formatter.
header='', trailer=''): from traceback import format_exception
global format_exception_only
if format_exception_only is None:
import traceback
format_exception_only = traceback.format_exception_only
result=['Traceback (innermost last):']
if header: result.insert(0, header)
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals = f.f_locals
result.append(' File %s, line %d, in %s'
% (filename, lineno, name))
try: result.append(' (Object: %s)' %
locals[co.co_varnames[0]].__name__)
except: pass
try: result.append(' (Info: %s)' %
str(locals['__traceback_info__']))
except: pass
tb = tb.tb_next
n = n+1
result.append(' '.join(format_exception_only(etype, value)))
if trailer: result.append(trailer)
return delimiter.join(result)
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
__version__='$Revision: 1.7 $'[11:-2] __version__='$Revision: 1.8 $'[11:-2]
import os, sys, time import os, sys, time
...@@ -87,8 +87,8 @@ class stupid_log_write: ...@@ -87,8 +87,8 @@ class stupid_log_write:
if error: if error:
try: try:
lines = format_exception(error[0], error[1], error[2], lines = format_exception(error[0], error[1], error[2],
trailer="\n", limit=100) limit=100)
print >> _log_dest, lines print >> _log_dest, ''.join(lines)
except: except:
print >> _log_dest, "%s: %s" % error[:2] print >> _log_dest, "%s: %s" % error[:2]
_log_dest.flush() _log_dest.flush()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment