changeset 49:cf52f97a5857

merge moin--main--1.3--patch-928 Patches applied: * arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-928 Fix error handling on failure, improved error display * nirs@freeshell.org--2005/moin--fix--1.3--patch-28 integrate new cgitb error handling * nirs@freeshell.org--2005/moin--fix--1.3--patch-29 huge merge from main, fixing strange conflicts in error.py and request.py * nirs@freeshell.org--2005/moin--fix--1.3--patch-30 cgitb refactoring * nirs@freeshell.org--2005/moin--fix--1.3--patch-31 fixed cgitb import * nirs@freeshell.org--2005/moin--fix--1.3--patch-32 merge from main * nirs@freeshell.org--2005/moin--fix--1.3--patch-33 adding MOIN_DEBUG to server scripts * nirs@freeshell.org--2005/moin--fix--1.3--patch-34 refacrot cgitb for eaiser customisation, use one view class in error with new simpler design, better error handling in multiconfig. * nirs@freeshell.org--2005/moin--fix--1.3--patch-35 merge from main * nirs@freeshell.org--2005/moin--fix--1.3--patch-36 Print simple text exception when cgitb fail from any reason * nirs@freeshell.org--2005/moin--fix--1.3--patch-37 remove changes not related to error handling * nirs@freeshell.org--2005/moin--fix--1.3--patch-38 raise more helpful error when there is no wikis list in farmconfig.py * nirs@freeshell.org--2005/moin--fix--1.3--patch-39 refactor getConfig, remove duplicate code, more correct error handling, use either farmconfig or wikiconfig mtime * nirs@freeshell.org--2005/moin--fix--1.3--patch-40 more clear error message when data or underlay missing or have wrong privilages * nirs@freeshell.org--2005/moin--fix--1.3--patch-41 update cgitb docstring * nirs@freeshell.org--2005/moin--fix--1.3--patch-42 make cgitb easiter to customize, simplify error.ErrorView, print cgitb traceback after the original traceback. * nirs@freeshell.org--2005/moin--fix--1.3--patch-43 refactoring multiconfig, make return values more clear, improving docstrings * nirs@freeshell.org--2005/moin--fix--1.3--patch-44 use only preformatted text in printException * nirs@freeshell.org--2005/moin--fix--1.3--patch-45 cgitb: improve code readability * nirs@freeshell.org--2005/moin--fix--1.3--patch-46 merge from main * nirs@freeshell.org--2005/moin--fix--1.3--patch-47 simplify the simple text traceback output * nirs@freeshell.org--2005/moin--fix--1.3--patch-48 moved failure handling to new failure module (save imports for cgi) * nirs@freeshell.org--2005/moin--fix--1.3--patch-49 spelling, docstring cleanup imported from: moin--main--1.5--patch-50
author Nir Soffer <nirs@freeshell.org>
date Thu, 29 Sep 2005 00:07:04 +0000
parents 8a37de5a6b79
children 69dfbae549ac
files ChangeLog MoinMoin/error.py MoinMoin/failure.py MoinMoin/multiconfig.py MoinMoin/request.py MoinMoin/support/cgitb.py docs/CHANGES wiki/server/moin.cgi wiki/server/moin.fcg wiki/server/moin.py wiki/server/moinmodpy.py wiki/server/mointwisted.py
diffstat 12 files changed, 1008 insertions(+), 450 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Sep 28 23:09:56 2005 +0000
+++ b/ChangeLog	Thu Sep 29 00:07:04 2005 +0000
@@ -36,6 +36,88 @@
      MoinMoin/wikiaction.py
 
 
+2005-09-02 16:09:21 GMT	Nir Soffer <nirs@freeshell.org>	patch-928
+
+    Summary:
+      Fix error handling on failure, improved error display
+    Revision:
+      moin--main--1.3--patch-928
+
+    Changes by module:
+    
+    == request ==
+     * Fix error handling - ALL errors are now handled by fail()
+     (previously some error slipped).
+     * Simplify error handling in request.run()
+     * Moved failure handling code to `failure` module
+    
+    == error ==
+     * New CompositeError that can wrap other saving the previous traceback
+     * error.FatalError is now a CompositeError
+     * Moved failure handling code to `failure` module
+    
+    == failure ==
+     * Create new `failure` module, used only when we fail, save imports in cgi
+     * View class extend cgitb.View to add specific moin features
+     * Multiple tracebacks support
+     * Debugging information is shown only in debug mode
+     * Moin application information
+     * General help text and links
+     * Handle multiple paragraphs in exception messages
+     * New streamlined design for error view
+    
+    == cgitb ==
+     * Refactor html and text functions to View class, HTMLFormatter and TextFormatter. No more duplicate formating code.
+     * Layout is done with minimal html and css, in a way it can't be effected by surrounding code.
+     * Built to be easy to subclass and modify without duplicating code
+     * Change layout, important details come first.
+     * Factor frame analyzing and formatting into separate class
+     * Add debug argument, can be used to change error display e.g user error view, developer error view
+     * Add viewClass argument, make it easy to customize the traceback view
+     * Easy to customize system details and application details
+    
+    == multiconfig ==
+     * Refactoring to have clear and correct error handling 
+     * New error message for missing 'wikis' in farmconfig
+     * All errors in farmconfig are handled in the same way as in wiki config by same _importConfigModule function.
+    
+    == wiki/server ==
+     * Add MOIN_DEBUG setup code to all moin server scripts
+
+    new files:
+     MoinMoin/.arch-ids/failure.py.id MoinMoin/failure.py
+
+    modified files:
+     ChangeLog MoinMoin/error.py MoinMoin/multiconfig.py
+     MoinMoin/request.py MoinMoin/support/cgitb.py docs/CHANGES
+     wiki/server/moin.cgi wiki/server/moin.fcg wiki/server/moin.py
+     wiki/server/moinmodpy.py wiki/server/mointwisted.py
+
+    new patches:
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-28
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-29
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-30
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-31
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-32
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-33
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-34
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-35
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-36
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-37
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-38
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-39
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-40
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-41
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-42
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-43
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-44
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-45
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-46
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-47
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-48
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-49
+
+
 2005-08-28 14:24:18 GMT	Thomas Waldmann <tw@waldmann-edv.de>	patch-927
 
     Summary:
--- a/MoinMoin/error.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/MoinMoin/error.py	Thu Sep 29 00:07:04 2005 +0000
@@ -1,19 +1,20 @@
 # -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - supply common error classes.
-    
-    TODO: translate strings?
+""" MoinMoin errors
 
-    @copyright: 2004 by Nir Soffer
-    @license: GNU GPL, see COPYING for details.
+Supply Error class and sub classes used to raise various errors
+
+@copyright: 2004, 2005 by Nir Soffer <nirs@freeshell.org>
+@license: GNU GPL, see COPYING for details.
 """
+import sys
+from MoinMoin import config
 
-import sys, os
-
-from MoinMoin import config, version
 
 class Error(Exception):
     """ Base class for moin moin errors
+
+    Use this class when you raise errors or create sub classes that
+    may be used to display non ASCII error message.
     
     Standard errors work safely only with strings using ascii or
     unicode. This class can be used safely with both strings using
@@ -25,6 +26,7 @@
             
     When you want to render an error, use unicode() or str() as needed.
     
+    TODO: translate strings?
     """
 
     def __init__(self, message):
@@ -53,112 +55,54 @@
         """ Make it possible to access attributes like a dict """
         return getattr(self, item)
     
-    
-class FatalError(Error):
-    """ Raise only when we must exit now 
-    
-    This error is handled at very high level and display a user friendly
-    message with the error message you supply.
-    
-    Using this class will hide the python traceback which is very useful
-    to developers. So use it only for known fatal errors, when we know
-    what is the error, but still can't continue.
+
+class CompositeError(Error):
+    ''' Base class for exceptions containing an exception
+
+    Do not use this class but its more specific sub classes.
+
+    Useful for hiding low level error inside high level user error,
+    while keeping the inner error information for debugging.
+
+    Example::
+
+        class InternalError(CompositeError):
+            """ Raise for internal errors """
+        
+        try:
+            # code that might fail...
+        except HairyLowLevelError:
+            raise InternalError("Sorry, internal error occurred")
+            
+    When showing a traceback, both InternalError traceback and
+    HairyLowLevelError traceback are available.
+    '''
+
+    def __init__(self, message):
+        """ Save system exception info before this exception is raised """
+        Error.__init__(self, message)
+        self.innerException = sys.exc_info()
+   
+    def exceptions(self):
+        """ Return a list of all inner exceptions """
+        all = [self.innerException]
+        while 1:
+            lastException = all[-1][1]
+            try:
+                all.append(lastException.innerException)
+            except AttributeError:
+                break
+        return all
+
+   
+class FatalError(CompositeError):
+    """ Base class for fatal error we can't handle
 
     Do not use this class but its more specific sub classes.
     """
-    name = 'MoinMoin Fatal Error'
-
-    # Links on project site that might help to resolve the problem
-    baseurl = 'http://moinmoin.wikiwikiweb.de/'
-    links = ['HelpOnInstalling', 'HelpOnConfiguration', 'MoinMoinBugs']
-    
-    def __init__(self, message):
-        """ Extend Error with environment details """
-        Error.__init__(self, message)
-        # Get environment details
-        self.system = '%s (%s)' % (sys.platform, os.name)
-        self.python = 'Python %s (%s)' % (sys.version.split()[0], sys.executable)
-        self.moin = 'MoinMoin release %s (revision %s)' % (
-            version.release, version.revision)
-        
-    def asHTML(self):
-        """ Render error as html
-
-        We may not have any style sheets at this stage, using our own
-        styles.
-
-        @rtype: unicode
-        @return: error formatted as html
-        """
-        # Make html paragraphs from text paragraphs separated by empty lines
-        message = [u'<p>%s</p>' % p for p in unicode(self).split(u'\n\n')]
-        message = '\n'.join(message)
-
-        # Make links list in the project site
-        links = [u'<li><a href="%s%s">%s</a></li>' % (self.baseurl, link, link)
-                 for link in self.links]
-        links = u'<ul class="links">\n%s\n</ul>\n' % '\n'.join(links)
-
-        html = [
-            u'''
-<html>
-    <head>
-        <title>%(name)s</title>
-        <style type="text/css">
-            h1 {font-size: 1.3em; margin: 0}
-            .message {border: 1px solid gray; background: #f7f7f7; margin:20px;}
-            .content {padding: 15px;}
-            .info {font-size: 0.85em; color: gray;}
-            ul.info {margin: 0; padding: 0;}
-            ul.links {margin: 5px 15px; padding: 0;}
-            ul.info li, ul.links li {display: inline; margin: 0 5px;}
-            hr {background: none; height: 0; border: none; border-top: 1px dotted gray;
-                margin: 0; padding: 0;}
-        </style>
-    </head>
-    <body>
-        <div class="message">
-            <div class="content">
-                <h1>%(name)s</h1>''' % self,
-            message,
-            u'''
-                <ul class="info">
-                    <li>%(system)s</li>
-                    <li>%(python)s</li>
-                    <li>%(moin)s</li>
-                </ul>
-            </div>
-            <hr>''' % self,
-            links,
-            u'''
-        </div>
-    </body>
-</html>
-''',]
-        html = '\n'.join(html)
-        return html
-
-    def asLog(self):
-        """ Render error for logs
-
-        For log we don't need all the 'nice' stuff we show for a
-        user. Just the plain error.
-
-        @rtype: string
-        @return: error formatted for log
-        """
-        return '%s: %s' % (self.name, str(self))
-
 
 class ConfigurationError(FatalError):
-    """ Raise when fatal misconfiguration is found
-    """
-    name = 'MoinMoin Configuration Error'
-        
+    """ Raise when fatal misconfiguration is found """
 
 class InternalError(FatalError):
-    """ Raise when internal fatal error is found
-    """
-    name = 'MoinMoin Internal Error'
-
-
+    """ Raise when internal fatal error is found """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/failure.py	Thu Sep 29 00:07:04 2005 +0000
@@ -0,0 +1,171 @@
+# -*- coding: iso-8859-1 -*-
+""" MoinMoin failure
+
+Handle fatal errors by showing a message and debugging information.
+
+@copyright: 2004, 2005 by Nir Soffer <nirs@freeshell.org>
+@license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+from MoinMoin.support import cgitb
+
+
+class View(cgitb.View):
+    """ Display an error message and debugging information
+    
+    Additions to cgitb.View:
+     - Multiple tracebacks support
+     - Debugging information is shown only in debug mode
+     - Moin application information
+     - General help text and links
+     - Handle multiple paragraphs in exception message
+     
+    cgitb is heavily modified cgitb, but fully backward compatible with
+    the standard cgitb. It should not contain any moin specific code.  
+
+    cgitb was refactored to be easy to customize by applications
+    developers. This moin specific subclass is an example.
+    """
+    debugInfoID = 'debug-info'
+    
+    def formatContent(self):
+        content = (
+            self.script(),
+            self.formatStylesheet(),
+            self.formatTitle(),
+            self.formatMessage(),
+            self.formatButtons(),
+            self.formatDebugInfo(),
+            self.debugInfoHideScript(),
+            self.formatTextTraceback()
+            )
+        return ''.join(content)
+    
+    def script(self):
+        return '''
+<script type="text/javascript">
+function toggleDebugInfo() {
+    var tb = document.getElementById('%s');
+    if (tb == null) return;
+    tb.style.display = tb.style.display ? '' : 'none';
+}
+</script>
+''' % self.debugInfoID
+
+    def stylesheet(self):
+        return cgitb.View.stylesheet(self) + """
+.cgitb .buttons {margin: 0.5em 0; padding: 5px 10px;}        
+.cgitb .buttons li {display: inline; margin: 0; padding: 0 0.25em;}
+"""
+
+    def formatMessage(self):
+        """ handle multiple paragraphs messages and add general help """
+        f = self.formatter
+        text = [self.formatExceptionMessage(self.info),
+                f.paragraph("If you want to report a bug, please save "
+                            "this page and  attach it to your bug report."),]
+        return ''.join(text)
+
+    def formatButtons(self):
+        """ Add 'buttons' to the error dialog """
+        f = self.formatter
+        buttons = [f.link('javascript:toggleDebugInfo()', 
+                          'Show debugging information'),
+                   f.link('http://moinmoin.wikiwikiweb.de/MoinMoinBugs', 
+                          'Report bug'),
+                   f.link('http://moinmoin.wikiwikiweb.de/FrontPage', 
+                          'Visit MoinMoin wiki'),]
+        return f.list(buttons, {'class': 'buttons'})
+    
+    def formatDebugInfo(self):
+        """ Put debugging information in a hidden div """
+        attributes = {'id': self.debugInfoID}
+        info = [self.formatTraceback(),
+                self.formatSystemDetails(),]
+        return self.formatter.section(''.join(info), attributes)
+
+    def debugInfoHideScript(self):
+        """ Hide debug info for javascript enabled browsers """
+        if self.debug:
+            return ''
+        return """
+<script type="text/javascript">
+toggleDebugInfo()
+</script>
+"""
+
+    def formatTraceback(self):
+        return self.formatAllTracebacks(self.formatOneTraceback)
+        
+    def formatTextTraceback(self):
+        template = self.textTracebackTemplate()
+        return template % self.formatAllTracebacks(self.formatOneTextTraceback)        
+
+    def formatAllTracebacks(self, formatFuction):
+        """ Format multiple tracebacks using formatFunction """
+        tracebacks = []
+        for type, value, tb in self.exceptions():
+            if type is None: 
+                break
+            tracebacks.append(formatFuction((type, value, tb)))
+            del tb
+        return ''.join(tracebacks)
+
+    def exceptions(self):
+        """ Return a list of exceptions info, starting at self.info """
+        try:
+            return [self.info] + self.info[1].exceptions()
+        except AttributeError:
+            return [self.info]
+
+    def applicationDetails(self):
+        """ Add MoinMoin details to system details """
+        from MoinMoin import version
+        return ['MoinMoin: Release %s (%s)' % (version.release,
+                                              version.revision)]
+
+    def formatExceptionMessage(self, info):
+        """ Handle multiple paragraphs in exception message """
+        text = cgitb.View.exceptionMessage(self, info)
+        text = text.split('\n\n')
+        text = ''.join([self.formatter.paragraph(item) for item in text])
+        return text
+
+
+def handle(request):
+    """ Handle failures
+    
+    Display fancy error view, or fallback to simple text traceback
+    """
+    savedError = sys.exc_info()
+    try:
+        debug = ('debug' in getattr(request, 'form', {}) or
+                 'MOIN_DEBUG' in os.environ)
+        handler = cgitb.Hook(file=request, viewClass=View, debug=debug)
+        handler.handle()
+    except:
+        request.write('<pre>\n')
+        printTextException(request, savedError)
+        request.write('\nAdditionally cgitb raised this exception:\n')
+        printTextException(request)        
+        request.write('</pre>\n')
+
+        
+def printTextException(request, info=None):
+    """ Simple text exception that should never fail
+    
+    Print all exceptions in a composite error.
+    """
+    import traceback
+    from MoinMoin import wikiutil
+    if info is None:
+        info = sys.exc_info()
+    try:
+        exceptions = [info] + info[1].exceptions()
+    except AttributeError:
+        exceptions = [info]
+    for info in exceptions:
+        text = ''.join(traceback.format_exception(*info))
+        text = wikiutil.escape(text)
+        request.write(text)
+
--- a/MoinMoin/multiconfig.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/MoinMoin/multiconfig.py	Thu Sep 29 00:07:04 2005 +0000
@@ -1,6 +1,6 @@
 # -*- coding: iso-8859-1 -*-
 """
-    MoinMoin - Multiple config handler and Configuration defaults class
+    MoinMoin - Multiple configuration handler and Configuration defaults class
 
     @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>
     @license: GNU GPL, see COPYING for details.
@@ -10,81 +10,104 @@
 from MoinMoin import error
 import MoinMoin.auth as authmodule
 
-_url_re = None
-config = {}
+_url_re_cache = None
+_farmconfig_mtime = None
+_config_cache = {}
 
-def url_re():
+
+def _importConfigModule(name):
+    """ Import and return configuration module and its modification time
+    
+    Handle all errors except ImportError, because missing file is not
+    always an error.
+    
+    @param name: module name
+    @rtype: tuple
+    @return: module, modification time
+    """
+    try:
+        module = __import__(name, globals(), {})
+        mtime = os.path.getmtime(module.__file__)
+    except ImportError:
+        raise
+    except IndentationError, err:
+        msg = 'IndentationError: %s\n' % str(err) + '''
+
+The configuration files are python modules. Therefore, whitespace is
+important. Make sure that you use only spaces, no tabs are allowed here!
+You have to use four spaces at the beginning of the line mostly.
+'''
+        raise error.ConfigurationError(msg)
+    except Exception, err:
+        msg = '%s: %s' % (err.__class__.__name__, str(err))
+        raise error.ConfigurationError(msg)
+    return module, mtime
+
+
+def _url_re():
     """ Return url matching regular expression
 
-    Using this regular expression, we find the config_module for each
-    url.
-    
-    Import wikis from 'farmconfig' on the first time, compile and cache url_re
-    regular expression, and return it.
-
-    Note: You must restart a long running process when you edit
-    farmconfig.py config file.
+    Import wikis list from farmconfig on the first call and compile a
+    regex. Later then return the cached regex.
 
     @rtype: compiled re object
     @return: url to wiki config  matching re
     """
-    global _url_re
-    if not _url_re:
+    global _url_re_cache, _farmconfig_mtime
+    if _url_re_cache is None:
         try:
-            farmconfig = __import__('farmconfig', globals(), {})
-            pattern = '|'.join([r'(?P<%s>%s)' % (name, regex)
-                                for name, regex in farmconfig.wikis])
-            _url_re = re.compile(pattern)
-        except (ImportError, AttributeError):
-            # It is not there, so we maybe have only one config. Fall back to
-            # old config file name and use it for all urls we get.
-            # Or we have a farmconfig file, but it does not contain a wikis
-            # attribute (because of typo or everything commented out as in
-            # sample config).
-            _url_re = re.compile(r'(?P<wikiconfig>.)')
-    return _url_re
+            farmconfig, _farmconfig_mtime = _importConfigModule('farmconfig')
+        except ImportError:
+            # Default to wikiconfig for all urls.
+            _farmconfig_mtime = 0
+            _url_re_cache = re.compile(r'(?P<wikiconfig>.)')
+        else:
+            try:
+                pattern = '|'.join([r'(?P<%s>%s)' % (name, regex)
+                                    for name, regex in farmconfig.wikis])
+                _url_re_cache = re.compile(pattern)
+            except AttributeError:
+                msg = """
+Missing required 'wikis' list in 'farmconfig.py'.
+
+If you run a single wiki you do not need farmconfig.py. Delete it and
+use wikiconfig.py.
+"""
+                raise error.ConfigurationError(msg)    
+    return _url_re_cache
 
 
-def getConfig(url):
-    """ Make and return config object, or raise an error
-
-    If the config file is not found or broken, either because of a typo
-    in farmconfig or deleted file or some other error, we raise a
-    ConfigurationError which is handled by our client.
-    
-    @param url: the url from request, possibly matching specific wiki
-    @rtype: DefaultConfig subclass instance
-    @return: config object for specific wiki
-    """
-    match = url_re().match(url)
-    if match and match.groups():
-        # Get config module name from match
-        for name, value in match.groupdict().items():
-            if value: break
+def _makeConfig(name):
+    """ Create and return a config instance 
 
-        try:
-            return config[name]
-        except KeyError:
-            pass
-        
-        try:
-            module =  __import__(name, globals(), {})
-            Config = getattr(module, 'Config', None)
-            if Config:
-                # Config found, return config instance using name as
-                # site identifier (name must be unique of our url_re).
-                # NOTE that this may reevaluate the Config and spawn
-                # multiple configs for one wiki in a farm.
-                cfg = config.setdefault(name, Config(name))
-                # we can use this to invalidate the cache on cfg change:
-                try:
-                    cfg.cfg_mtime = os.path.getmtime(module.__file__)
-                except:
-                    cfg.cfg_mtime = 0
-                return cfg
-            else:
-                # Broken config file, probably old config from 1.2
-                msg = '''
+    Timestamp config with either module mtime or farmconfig mtime. This
+    mtime can be used later to invalidate older caches.
+
+    @param name: module name
+    @rtype: DefaultConfig sub class instance
+    @return: new configuration instance
+    """
+    global _farmconfig_mtime
+    try:
+        module, mtime = _importConfigModule(name)
+        configClass = getattr(module, 'Config')
+        cfg = configClass(name)
+        cfg.cfg_mtime = max(mtime, _farmconfig_mtime)
+    except ImportError, err:
+        msg = 'ImportError: %s\n' % str(err) + '''
+
+Check that the file is in the same directory as the server script. If
+it is not, you must add the path of the directory where the file is
+located to the python path in the server script. See the comments at
+the top of the server script.
+
+Check that the configuration file name is either "wikiconfig.py" or the
+module name specified in the wikis list in farmconfig.py. Note that the
+module name does not include the ".py" suffix.
+'''
+        raise error.ConfigurationError(msg)
+    except AttributeError:
+        msg = '''
 Could not find required "Config" class in "%(name)s.py". This might
 happen if you are trying to use a pre 1.3 configuration file, or made a
 syntax or spelling error.
@@ -92,60 +115,44 @@
 Please check your configuration file. As an example for correct syntax,
 use the wikiconfig.py file from the distribution.
 ''' % {'name': name}
-
-        # We don't handle fatal errors here
-        except error.FatalError, err:
-            raise err
-
-        # These errors will not be big surprise:       
-        except ImportError, err:
-            msg = '''
-Import of configuration file "%(name)s.py" failed because of
-ImportError: %(err)s.
-
-Check that the file is in the same directory as the server script. If
-it is not, you must add the path of the directory where the file is located
-to the python path in the server script. See the comments at the top of
-the server script.
-
-Check that the configuration file name is either "wikiconfig.py" or the
-module name specified in the wikis list in farmconfig.py. Note that the module
-name does not include the ".py" suffix.
-''' % {'name': name, 'err': str(err)}
+        raise error.ConfigurationError(msg)
+    return cfg
 
-        except IndentationError, err:
-            msg = '''
-Import of configuration file "%(name)s.py" failed because of
-IndentationError: %(err)s.
-
-The configuration files are python modules. Therefore, whitespace is
-important. Make sure that you use only spaces, no tabs are allowed here!
-You have to use four spaces at the beginning of the line mostly.
-''' % {'name': name, 'err': str(err)}
 
-        # But people can have many other errors. We hope that the python
-        # error message will help them.
-        except:
-            err = sys.exc_info()[1]
-            msg = '''
-Import of configuration file "%(name)s.py" failed because of %(class)s:
-%(err)s.
-
-We hope this error message make sense. If not, you are welcome to ask on
-the page http://moinmoin.wikiwikiweb.de/MoinMoinQuestions/ConfigFiles
-or the #moin channel on irc.freenode.net or on the mailing list.
-''' % {'name': name, 'class': err.__class__.__name__, 'err': str(err)}
-
-    else:
-        # URL did not match anything, probably error in farmconfig.wikis 
+def _getConfigName(url):
+    """ Return config name for url or raise """
+    match = _url_re().match(url)
+    if not (match and match.groups()):
         msg = '''
 Could not find a match for url: "%(url)s".
 
 Check your URL regular expressions in the "wikis" list in
 "farmconfig.py". 
 ''' % {'url': url}
+        raise error.ConfigurationError(msg)    
+    for name, value in match.groupdict().items():
+        if value: break
+    return name
 
-    raise error.ConfigurationError(msg)
+
+def getConfig(url):
+    """ Return cached config instance for url or create new one
+
+    If called by many threads in the same time multiple config
+    instances might be created. The first created item will be
+    returned, using dict.setdefault.
+
+    @param url: the url from request, possibly matching specific wiki
+    @rtype: DefaultConfig subclass instance
+    @return: config object for specific wiki
+    """
+    configName = _getConfigName(url)
+    try:
+        config = _config_cache[configName]
+    except KeyError:
+        config = _makeConfig(configName)
+        config = _config_cache.setdefault(configName, config)
+    return config
 
 
 # This is a way to mark some text for the gettext tools so that they don't
@@ -154,10 +161,8 @@
 
 
 class DefaultConfig:
-    """ default config values
-
-    FIXME: update according to MoinMoin:UpdateConfiguration
-    """    
+    """ default config values """
+    
     # All acl_right lines must use unicode!
     acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write"
     acl_rights_before = u""
@@ -449,7 +454,6 @@
 For more information, visit HelpOnConfiguration. Please check your
 configuration for typos before requesting support or reporting a bug.
 """ % ', '.join(unknown)
-            from MoinMoin import error
             raise error.ConfigurationError(msg)
 
     def _decode(self):
@@ -467,8 +471,6 @@
         config files.
         """
         charset = 'utf-8'
-
-        # TODO: add to translation
         message = u'''
 "%(name)s" configuration variable is a string, but should be
 unicode. Use %(name)s = u"value" syntax for unicode variables.
@@ -495,7 +497,7 @@
                     except UnicodeError:
                         raise error.ConfigurationError(message %
                                                        {'name': name})
-                # Look into lists and try to decode string inside them
+                # Look into lists and try to decode strings inside them
                 elif isinstance(attr, list):
                     for i in xrange(len(attr)):
                         item = attr[i]
@@ -523,11 +525,12 @@
             path_pages = os.path.join(path, "pages")
             if not (os.path.isdir(path_pages) and os.access(path_pages, mode)):
                 msg = '''
-"%(attr)s" does not exists at "%(path)s", or has incorrect ownership and
+%(attr)s "%(path)s" does not exists, or has incorrect ownership or
 permissions.
 
-Make sure the directory and the subdirectory pages are owned by the web server and are readable,
-writable and executable by the web server user and group.
+Make sure the directory and the subdirectory pages are owned by the web
+server and are readable, writable and executable by the web server user
+and group.
 
 It is recommended to use absolute paths and not relative paths. Check
 also the spelling of the directory name.
@@ -568,7 +571,7 @@
                 imp.release_lock()
         except ImportError, err:
             msg = '''
-Could not import plugin package from "%(path)s" because of ImportError:
+Could not import plugin package "%(path)s/plugin" because of ImportError:
 %(err)s.
 
 Make sure your data directory path is correct, check permissions, and
--- a/MoinMoin/request.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/MoinMoin/request.py	Thu Sep 29 00:07:04 2005 +0000
@@ -8,11 +8,8 @@
 """
 
 import os, time, sys, cgi, StringIO
-
-from MoinMoin import config, wikiutil, user, error
+from MoinMoin import config, wikiutil, user
 from MoinMoin.util import MoinMoinNoFooter, IsWin9x
-import MoinMoin.error
-
 
 # Timing ---------------------------------------------------------------
 
@@ -902,10 +899,9 @@
         self.setResponseCode(403)
         
     def run(self):
-        # __init__ may have failed
+        # Exit now if __init__ failed or request is forbidden
         if self.failed or self.forbidden:
             return self.finish()
-        
         if self.isForbidden():
             self.makeForbidden()
             if self.forbidden:
@@ -941,13 +937,7 @@
                 pagename = self.normalizePagename(path)
             else:
                 pagename = None
-        except: # catch and print any exception
-            self.reset_output()
-            self.http_headers()
-            self.print_exception()
-            return self.finish()
-        
-        try:
+
             # Handle request. We have these options:
             
             # 1. If user has a bad user name, delete its bad cookie and
@@ -1054,37 +1044,8 @@
             
         except MoinMoinNoFooter:
             pass
-
-        except MoinMoin.error.FatalError, err:
+        except Exception, err:
             self.fail(err)
-            return self.finish()
-            
-        except: 
-            # Catch and print any exception
-            saved_exc = sys.exc_info()
-            self.reset_output()
-            
-            # Send 500 error code
-            self.http_headers(['Status: 500 MoinMoin Internal Error'])
-            self.setResponseCode(500)
-            self.http_headers()
-            
-            self.write(u"\n<!-- ERROR REPORT FOLLOWS -->\n")
-            try:
-                from MoinMoin.support import cgitb
-            except:
-                # no cgitb, for whatever reason
-                self.print_exception(*saved_exc)
-            else:
-                try:
-                    cgitb.Hook(file=self).handle(saved_exc)
-                    # was: cgitb.handler()
-                except:
-                    self.print_exception(*saved_exc)
-                    self.write("\n\n<hr>\n")
-                    self.write("<p><strong>Additionally, cgitb raised this exception:</strong></p>\n")
-                    self.print_exception()
-            del saved_exc
 
         return self.finish()
 
@@ -1104,32 +1065,21 @@
         pass
 
     def fail(self, err):
-        """ Fail with nice error message when we can't continue
+        """ Fail when we can't continue
 
-        Log the error, then try to print nice error message. Send 500
-        status code with the error name. Reference: 
+        Send 500 status code with the error name. Reference: 
         http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
 
-        @param err: MoinMoin.error.FatalError instance or subclass.
+        Log the error, then let failure module handle it. 
+
+        @param err: Exception instance or subclass.
         """
-        self.failed = 1 # save state for self.run()
-        self.log(err.asLog())
-        self.http_headers(['Status: 500 %(name)s' % err])
+        self.failed = 1 # save state for self.run()            
+        self.http_headers(['Status: 500 MoinMoin Internal Error'])
         self.setResponseCode(500)
-        self.write(err.asHTML())
-            
-    def print_exception(self, type=None, value=None, tb=None, limit=None):
-        if type is None:
-            type, value, tb = sys.exc_info()
-        import traceback
-        self.write("<h2>request.print_exception handler</h2>\n")
-        self.write("<h3>Traceback (most recent call last):</h3>\n")
-        list = traceback.format_tb(tb, limit) + \
-               traceback.format_exception_only(type, value)
-        self.write("<pre>%s<strong>%s</strong></pre>\n" % (
-            wikiutil.escape("".join(list[:-1])),
-            wikiutil.escape(list[-1]),))
-        del tb
+        self.log('%s: %s' % (err.__class__.__name__, str(err)))        
+        from MoinMoin import failure
+        failure.handle(self)             
 
     def open_logs(self):
         pass
@@ -1373,7 +1323,7 @@
                 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
                 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
 
-        except error.FatalError, err:
+        except Exception, err:
             self.fail(err)
             
     def open_logs(self):
@@ -1474,8 +1424,13 @@
             
             RequestBase.__init__(self, properties)
 
-        except error.FatalError, err:
-            self.delayedError = err
+        except Exception, err:
+            # Wrap err inside an internal error if needed
+            from MoinMoin import error
+            if isinstance(err, error.FatalError):
+                self.delayedError = err
+            else:
+                self.delayedError = error.InternalError(str(err))
 
     def run(self):
         """ Handle delayed errors then invoke base class run """
@@ -1715,7 +1670,7 @@
             
             RequestBase.__init__(self, properties)
 
-        except error.FatalError, err:
+        except Exception, err:
             self.fail(err)
 
     def _setup_args_from_cgi_form(self, form=None):
@@ -1799,7 +1754,6 @@
         #sys.stderr.write(pformat(more_headers))
         #sys.stderr.write(pformat(self.user_headers))
 
-
 # mod_python/Apache ----------------------------------------------------
 
 class RequestModPy(RequestBase):
@@ -1828,7 +1782,7 @@
             self._setup_vars_from_std_env(env)
             RequestBase.__init__(self)
 
-        except error.FatalError, err:
+        except Exception, err:
             self.fail(err)
             
     def rewriteURI(self, env):
@@ -1987,7 +1941,7 @@
             self._setup_vars_from_std_env(env)
             RequestBase.__init__(self, properties)
 
-        except error.FatalError, err:
+        except Exception, err:
             self.fail(err)
 
     def _setup_args_from_cgi_form(self, form=None):
--- a/MoinMoin/support/cgitb.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/MoinMoin/support/cgitb.py	Thu Sep 29 00:07:04 2005 +0000
@@ -1,188 +1,570 @@
-"""Handle exceptions in CGI scripts by formatting tracebacks into nice HTML.
+"""More comprehensive traceback formatting for Python scripts.
 
 To enable this module, do:
 
     import cgitb; cgitb.enable()
 
-at the top of your CGI script.  The optional arguments to enable() are:
+at the top of your script.  The optional arguments to enable() are:
 
     display     - if true, tracebacks are displayed in the web browser
     logdir      - if set, tracebacks are written to files in this directory
     context     - number of lines of source code to show for each stack frame
+    format      - 'text' or 'html' controls the output format
+    viewClass   - sub class of View. Create this if you want to customize the
+                  layout of the traceback.
+    debug       - may be used by viewClass to decide on level of detail
 
-By default, tracebacks are displayed but not saved, and context is 5.
+By default, tracebacks are displayed but not saved, the context is 5 lines
+and the output format is 'html' (for backwards compatibility with the
+original use of this module)
 
 Alternatively, if you have caught an exception and want cgitb to display it
-for you, call cgitb.handler().  The optional argument to handler() is a 3-item
-tuple (etype, evalue, etb) just like the value of sys.exc_info()."""
+for you, call cgitb.handler().  The optional argument to handler() is a
+3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
+The default handler displays output as HTML.
+
+
+2005-04-22 Nir Soffer <nirs@freeshell.org>
+
+Rewrite:
+ - Refactor html and text functions to View class, HTMLFormatter and
+   TextFormatter. No more duplicate formating code.
+ - Layout is done with minimal html and css, in a way it can't be
+   effected by souranding code.
+ - Built to be easy to subclass and modify without duplicating code
+ - Change layout, important details come first.
+ - Factor frame analaizing and formatting into separate class
+ - Add debug argument, can be used to change error display e.g. user
+   error view, developer error view
+ - Add viewClass argument, make it easy to customize the traceback view
+ - Easy to customize system details and application details
+
+The main goal of this rewrite was to have a traceback that can render
+few tracebacks combined. Its needed when you wrap an expection and want
+to print both the traceback up to the wrapper exception, and the
+original traceback. There is no code to support this here, but its easy
+to add by using your own View sub class.
+"""
 
 __author__ = 'Ka-Ping Yee'
 __version__ = '$Revision: 1.10 $'
 
-import sys
+import sys, os, pydoc, inspect, linecache, tokenize, keyword
+
 
 def reset():
-    """Return a string that resets the CGI and browser to a known state."""
+    """ Reset the CGI and the browser
+    
+    Return a string that resets the CGI and browser to a known state.
+    TODO: probably some of this is not needed any more.
+    """
     return '''<!--: spam
 Content-Type: text/html
 
-<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> -->
-<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> -->
+<body><font style="color: white; font-size: 1px"> -->
+<body><font style="color: white; font-size: 1px"> --> -->
 </font> </font> </font> </script> </object> </blockquote> </pre>
-</table> </table> </table> </table> </table> </font> </font> </font>'''
-
-__UNDEF__ = []                          # a special sentinel object
-def small(text): return '<small>' + text + '</small>'
-def strong(text): return '<strong>' + text + '</strong>'
-def grey(text): return '<font color="#909090">' + text + '</font>'
-
-def lookup(name, frame, locals):
-    """Find the value for a given name in the given environment."""
-    if name in locals:
-        return 'local', locals[name]
-    if name in frame.f_globals:
-        return 'global', frame.f_globals[name]
-    return None, __UNDEF__
-
-def scanvars(reader, frame, locals):
-    """Scan one logical line of Python and look up values of variables used."""
-    import tokenize, keyword
-    vars, lasttoken, parent, prefix = [], None, None, ''
-    for ttype, token, start, end, line in tokenize.generate_tokens(reader):
-        if ttype == tokenize.NEWLINE: break
-        if ttype == tokenize.NAME and token not in keyword.kwlist:
-            if lasttoken == '.':
-                if parent is not __UNDEF__:
-                    value = getattr(parent, token, __UNDEF__)
-                    vars.append((prefix + token, prefix, value))
-            else:
-                where, value = lookup(token, frame, locals)
-                vars.append((token, where, value))
-        elif token == '.':
-            prefix += lasttoken + '.'
-            parent = value
-        else:
-            parent, prefix = None, ''
-        lasttoken = token
-    return vars
-
-def html((etype, evalue, etb), context=5):
-    """Return a nice HTML document describing a given traceback."""
-    import os, types, time, traceback, linecache, inspect, pydoc
+</table> </table> </table> </table> </table> </font> </font> </font>
+'''
 
-    if type(etype) is types.ClassType:
-        etype = etype.__name__
-    # XXX stuff added locally:
-    from MoinMoin import version
-    try:
-        osinfo = " ".join(os.uname()) # only on UNIX
-    except: # TODO: which error does this raise on Win/Mac?
-        osinfo = "Platform: %s (%s)" % (sys.platform, os.name)
-    pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
-    date = time.ctime(time.time())
-    versinfo = "<strong>Please include this information in your bug reports!:</strong><br>" + \
-               "Python %s<br>" % pyver + \
-               osinfo + '<br>' + \
-               'MoinMoin Release %s [Revision %s]' % (version.release, version.revision)
-    head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading(
-        '<big><big><strong>%s</strong>%s</big></big>' % (str(etype), str(evalue)),
-        '#ffffff', '#6622aa', versinfo + '<br>' + date) + '''
-<p>A problem occurred in a Python script.  Here is the sequence of
-function calls leading up to the error, in the order they occurred.'''
-    # XXX end of local changes
-
-    indent = '<tt>' + small('&nbsp;' * 5) + '&nbsp;</tt>'
-    frames = []
-    records = inspect.getinnerframes(etb, context)
-    for frame, file, lnum, func, lines, index in records:
-        file = file and os.path.abspath(file) or '?'
-        link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file))
-        args, varargs, varkw, locals = inspect.getargvalues(frame)
-        call = ''
-        if func != '?':
-            call = 'in ' + strong(func) + \
-                inspect.formatargvalues(args, varargs, varkw, locals,
-                    formatvalue=lambda value: '=' + pydoc.html.repr(value))
-
-        highlight = {}
-        def reader(lnum=[lnum]):
-            highlight[lnum[0]] = 1
-            try: return linecache.getline(file, lnum[0])
-            finally: lnum[0] += 1
-        vars = scanvars(reader, frame, locals)
-
-        rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' %
-                ('<big>&nbsp;</big>', link, call)]
-        if index is not None:
-            i = lnum - index
-            for line in lines:
-                num = small('&nbsp;' * (5-len(str(i))) + str(i)) + '&nbsp;'
-                line = '<tt>%s%s</tt>' % (num, pydoc.html.preformat(line))
-                if i in highlight:
-                    rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
-                else:
-                    rows.append('<tr><td>%s</td></tr>' % grey(line))
-                i += 1
-
-        done, dump = {}, []
-        for name, where, value in vars:
-            if name in done: continue
-            done[name] = 1
-            if value is not __UNDEF__:
-                if where == 'global': name = '<em>global</em> ' + strong(name)
-                elif where == 'local': name = strong(name)
-                else: name = where + strong(name.split('.')[-1])
-                dump.append('%s&nbsp;= %s' % (name, pydoc.html.repr(value)))
-            else:
-                dump.append(name + ' <em>undefined</em>')
-
-        rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump))))
-        frames.append('''<p>
-<table width="100%%" cellspacing=0 cellpadding=0 border=0>
-%s</table>''' % '\n'.join(rows))
-
-    exception = ['<p>%s: %s' % (strong(str(etype)), str(evalue))]
-    if type(evalue) is types.InstanceType:
-        for name in dir(evalue):
-            value = pydoc.html.repr(getattr(evalue, name))
-            exception.append('\n<br>%s%s&nbsp;=\n%s' % (indent, name, value))
-
-    import traceback
-    return head + ''.join(frames) + ''.join(exception) + '''
+__UNDEF__ = [] # a special sentinel object
 
 
-<!-- The above is a description of an error in a Python program, formatted
-     for a Web browser because the 'cgitb' module was enabled.  In case you
-     are not reading this in a Web browser, here is the original traceback:
+class HTMLFormatter:
+    """ Minimal html formatter """
+    
+    def attributes(self, attributes=None):
+        if attributes:
+            result = [' %s="%s"' % (k, v) for k, v in attributes.items()]           
+            return ''.join(result)
+        return ''
+    
+    def tag(self, name, text, attributes=None):
+        return '<%s%s>%s</%s>\n' % (name, self.attributes(attributes), 
+                                    text, name)
+    
+    def section(self, text, attributes=None):
+        return self.tag('div', text, attributes)
+
+    def title(self, text, attributes=None):
+        return self.tag('h1', text, attributes)
+
+    def subTitle(self, text, attributes=None):
+        return self.tag('h2', text, attributes)
+
+    def subSubTitle(self, text, attributes=None):
+        return self.tag('h3', text, attributes)
+
+    def paragraph(self, text, attributes=None):
+        return self.tag('p', text, attributes)
+
+    def list(self, items, attributes=None):
+        return self.formatList('ul', items, attributes)
+
+    def orderedList(self, items, attributes=None):
+        return self.formatList('ol', items, attributes)
+
+    def formatList(self, name, items, attributes=None):
+        """ Send list of raw texts or formatted items. """
+        if isinstance(items, (list, tuple)):
+            items = '\n' + ''.join([self.listItem(i) for i in items])
+        return self.tag(name, items, attributes)
+    
+    def listItem(self, text, attributes=None):
+        return self.tag('li', text, attributes)        
+
+    def link(self, href, text, attributes=None):
+        if attributes is None:
+            attributes = {}
+        attributes['href'] = href
+        return self.tag('a', text, attributes)
+
+    def strong(self, text, attributes=None):
+        return self.tag('strong', text, attributes)        
+
+    def em(self, text, attributes=None):
+        return self.tag('em', text, attributes)        
+
+    def repr(self, object):
+        return pydoc.html.repr(object)
+        
+
+class TextFormatter:
+    """ Plain text formatter """
+
+    def section(self, text, attributes=None):
+        return text
+
+    def title(self, text, attributes=None):
+        lineBellow = '=' * len(text)
+        return '%s\n%s\n\n' % (text, lineBellow)
+
+    def subTitle(self, text, attributes=None):
+        lineBellow = '-' * len(text)
+        return '%s\n%s\n\n' % (text, lineBellow)
+
+    def subSubTitle(self, text, attributes=None):
+        lineBellow = '~' * len(text)
+        return '%s\n%s\n\n' % (text, lineBellow)
+
+    def paragraph(self, text, attributes=None):
+        return text + '\n\n'
+
+    def list(self, items, attributes=None):
+        if isinstance(items, (list, tuple)):
+            items = [' * %s\n' % i for i in items]
+            return ''.join(items) + '\n'
+        return items
+
+    def orderedList(self, items, attributes=None):
+        if isinstance(items, (list, tuple)):
+            result = []
+            for i in rage(items):
+                result.append(' %d. %s\n' % (i, items[i]))
+            return ''.join(result) + '\n'
+        return items
+
+    def listItem(self, text, attributes=None):
+        return ' * %s\n' % text       
+
+    def link(self, href, text, attributes=None):
+        return '[[%s]]' % text
+
+    def strong(self, text, attributes=None):
+        return text
+   
+    def em(self, text, attributes=None):
+        return text
+   
+    def repr(self, object):
+        return repr(object)
+        
+
+class Frame:
+    """ Analyze and format single frame in a traceback """
+
+    def __init__(self, frame, file, lnum, func, lines, index):
+        self.frame = frame
+        self.file = file
+        self.lnum = lnum
+        self.func = func
+        self.lines = lines
+        self.index = index
+
+    def format(self, formatter):
+        """ Return formatted content """
+        self.formatter = formatter
+        vars, highlight = self.scan()
+        items = [self.formatCall(),
+                 self.formatContext(highlight),
+                 self.formatVariables(vars)]
+        return ''.join(items)
+
+    # -----------------------------------------------------------------
+    # Private - formatting
+        
+    def formatCall(self):
+        call = '%s in %s%s' % (self.formatFile(),
+                               self.formatter.strong(self.func),
+                               self.formatArguments(),)
+        return self.formatter.paragraph(call, {'class': 'call'})
+    
+    def formatFile(self):
+        """ Return formatted file link """
+        if not self.file:
+            return '?'
+        file = pydoc.html.escape(os.path.abspath(self.file))
+        return self.formatter.link('file://' + file, file)        
+
+    def formatArguments(self):
+        """ Return formated arguments list """
+        if self.func == '?':
+            return ''
+
+        def formatValue(value):
+            return '=' + self.formatter.repr(value)
+
+        args, varargs, varkw, locals = inspect.getargvalues(self.frame)
+        return inspect.formatargvalues(args, varargs, varkw, locals,
+                                       formatvalue=formatValue)
+
+    def formatContext(self, highlight):
+        """ Return formatted context, next call highlighted """
+        if self.index is None:
+            return ''
+        context = []
+        i = self.lnum - self.index
+        for line in self.lines:
+            line = '%5d  %s' % (i, pydoc.html.escape(line))
+            attributes = {}
+            if i in highlight:
+                attributes = {'class': 'highlight'}
+            context.append(self.formatter.listItem(line, attributes))
+            i += 1
+        context = '\n' + ''.join(context) + '\n'
+        return self.formatter.orderedList(context, {'class': 'context'})
+
+    def formatVariables(self, vars):
+        """ Return formatted variables """ 
+        done = {}
+        dump = []
+        for name, where, value in vars:
+            if name in done: 
+                continue
+            done[name] = 1
+            if value is __UNDEF__:
+                dump.append('%s %s' % (name, self.formatter.em('undefined')))
+            else:
+                dump.append(self.formatNameValue(name, where, value))
+        return self.formatter.list(dump, {'class': 'variables'})
+
+    def formatNameValue(self, name, where, value):
+        """ Format variable name and value according to scope """
+        if where in ['global', 'builtin']:
+            name = '%s %s' % (self.formatter.em(where),
+                              self.formatter.strong(name))
+        elif where == 'local':
+            name = self.formatter.strong(name)
+        else:
+            name = where + self.formatter.strong(name.split('.')[-1])
+        return '%s = %s' % (name, self.formatter.repr(value))
+
+    # ---------------------------------------------------------------
+    # Private - analyzing code
+
+    def scan(self):
+        """ Scan frame for vars while setting highlight line """
+        highlight = {}
+        
+        def reader(lnum=[self.lnum]):
+            highlight[lnum[0]] = 1
+            try: 
+                return linecache.getline(self.file, lnum[0])
+            finally: 
+                lnum[0] += 1
+
+        vars = self.scanVariables(reader)
+        return vars, highlight
+
+    def scanVariables(self, reader):
+        """ Lookup variables in one logical Python line """
+        vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
+        for ttype, token, start, end, line in tokenize.generate_tokens(reader):
+            if ttype == tokenize.NEWLINE: 
+                break
+            if ttype == tokenize.NAME and token not in keyword.kwlist:
+                if lasttoken == '.':
+                    if parent is not __UNDEF__:
+                        value = getattr(parent, token, __UNDEF__)
+                        vars.append((prefix + token, prefix, value))
+                else:
+                    where, value = self.lookup(token)
+                    vars.append((token, where, value))
+            elif token == '.':
+                prefix += lasttoken + '.'
+                parent = value
+            else:
+                parent, prefix = None, ''
+            lasttoken = token
+        return vars
+
+    def lookup(self, name):
+        """ Return the scope and the value of name """
+        scope = None
+        value = __UNDEF__
+        locals = inspect.getargvalues(self.frame)[3]
+        if name in locals:
+            scope, value = 'local', locals[name]
+        elif name in self.frame.f_globals:
+            scope, value = 'global', self.frame.f_globals[name]
+        elif '__builtins__' in self.frame.f_globals:
+            scope = 'builtin'
+            builtins = self.frame.f_globals['__builtins__']
+            if isinstance(builtins, dict):
+                value = builtins.get(name, __UNDEF__)
+            else:
+                value = getattr(builtins, name, __UNDEF__)
+        return scope, value
+
+
+class View:
+    """ Traceback view """
+    
+    frameClass = Frame # analyze and format a frame
+    
+    def __init__(self, info=None, debug=0):
+        """ Save starting info or current exception info """
+        self.info = info or sys.exc_info()
+        self.debug = debug
+        
+    def format(self, formatter, context=5):
+        self.formatter = formatter
+        self.context = context
+        return formatter.section(self.formatContent(), {'class': 'cgitb'})
+
+    def formatContent(self):
+        """ General layout - override to change layout """
+        content = (
+            self.formatStylesheet(),
+            self.formatTitle(),
+            self.formatMessage(),
+            self.formatTraceback(),
+            self.formatSystemDetails(),
+            self.formatTextTraceback(),
+            )
+        return ''.join(content)
+
+    # -----------------------------------------------------------------
+    # Stylesheet
+
+    def formatStylesheet(self):
+        """ Format inline html stylesheet """
+        return '<style type=text/css>%s</style>' % self.stylesheet()
+
+    def stylesheet(self):
+        """ Return stylesheet rules. Override to change rules.
+
+        The rules are sparated to make it easy to extend.
+
+        The stylesheet must work even if sorounding code define the same
+        css names, and it must not change the sorounding code look and
+        behavior. This is done by having all content in a .traceback
+        section.
+        """
+        return """
+.cgitb {background: #E6EAF0; border: 1px solid #4D6180;}
+.cgitb p {margin: 0.5em 0; padding: 5px 10px; text-align: left;}
+.cgitb ol {margin: 0}
+.cgitb li {margin: 0.25em 0;}
+.cgitb h1, .cgitb h2, .cgitb h3 {padding: 5px 10px; margin: 0; background: #4D6180; color: white;}
+.cgitb h1 {font-size: 1.3em;}
+.cgitb h2 {font-size: 1em; margin-top: 1em;}
+.cgitb h3 {font-size: 1em;}
+.cgitb .frames {margin: 0; padding: 0; color: #606060}
+.cgitb .frames li {display: block;}
+.cgitb .call {padding: 5px 10px; background: #A3B4CC; color: black}
+.cgitb .context {padding: 0; font-family: monospace; }
+.cgitb .context li {display: block; white-space: pre;}
+.cgitb .context li.highlight {background: #C0D3F0; color: black}
+.cgitb .variables {padding: 5px 10px; font-family: monospace;}
+.cgitb .variables li {display: inline;}
+.cgitb .variables li:after {content: ", ";}
+.cgitb .variables li:last-child:after {content: "";}
+.cgitb .exception {border: 1px solid #4D6180; margin: 10px}
+.cgitb .exception h3 {background: #4D6180; color: white;}
+.cgitb .exception p {color: black;}
+.cgitb .exception ul {padding: 0 10px; font-family: monospace;}
+.cgitb .exception li {display: block;}
+"""
+
+    # -----------------------------------------------------------------
+    # Head
+    
+    def formatTitle(self):
+        return self.formatter.title(self.exceptionTitle(self.info))
+        
+    def formatMessage(self):
+        return self.formatter.paragraph(self.exceptionMessage(self.info))
+        
+    # -----------------------------------------------------------------
+    # Traceback
+
+    def formatTraceback(self):
+        """ Return formatted traceback """
+        return self.formatOneTraceback(self.info)
+    
+    def formatOneTraceback(self, info):
+        """ Format one traceback
+        
+        Separate to enable formatting multiple tracebacks.
+        """
+        output = [self.formatter.subTitle('Traceback'),
+                  self.formatter.paragraph(self.tracebackText(info)),
+                  self.formatter.orderedList(self.tracebackFrames(info),
+                                            {'class': 'frames'}),
+                  self.formatter.section(self.formatException(info),
+                                         {'class': 'exception'}),]
+        return self.formatter.section(''.join(output), {'class': 'traceback'})
+
+    def tracebackFrames(self, info):
+        frames = []
+        traceback = info[2]
+        for record in inspect.getinnerframes(traceback, self.context):
+            frame = self.frameClass(*record)
+            frames.append(frame.format(self.formatter))
+        del traceback
+        return frames
+
+    def tracebackText(self, info):
+        return '''A problem occurred in a Python script.  Here is the
+        sequence of function calls leading up to the error, in the
+        order they occurred.'''
+
+    # --------------------------------------------------------------------
+    # Exception
+
+    def formatException(self, info):
+        items = [self.formatExceptionTitle(info),
+                 self.formatExceptionMessage(info),
+                 self.formatExceptionAttributes(info),]
+        return ''.join(items)
+
+    def formatExceptionTitle(self, info):
+        return self.formatter.subSubTitle(self.exceptionTitle(info))
+        
+    def formatExceptionMessage(self, info):
+        return self.formatter.paragraph(self.exceptionMessage(info))
+
+    def formatExceptionAttributes(self, info):
+        attribtues = []
+        for name, value in self.exceptionAttributes(info):
+            value = self.formatter.repr(value)
+            attribtues.append('%s = %s' % (name, value))           
+        return self.formatter.list(attribtues)
+
+    def exceptionAttributes(self, info):
+        """ Return list of tuples [(name, value), ...] """
+        instance = info[1]
+        attribtues = []
+        for name in dir(instance):
+            if name.startswith('_'):
+                continue
+            value = getattr(instance, name)
+            attribtues.append((name, value))
+        return attribtues
+
+    def exceptionTitle(self, info):
+        type = info[0]
+        return getattr(type, '__name__', str(type))
+        
+    def exceptionMessage(self, info):
+        instance = info[1]
+        return pydoc.html.escape(str(instance))
+
+
+    # -----------------------------------------------------------------
+    # System details
+
+    def formatSystemDetails(self):
+        details = self.systemDetails() + self.applicationDetails()
+        return (self.formatter.subTitle('System Details') +
+                self.formatter.list(details, {'class': 'system'}))
+
+    def systemDetails(self):
+        import time
+        try:
+            platform = pydoc.html.escape(' '.join(os.uname()))
+        except:
+            platform = pydoc.html.escape('%s (%s)' % (sys.platform, os.name))
+        return ['Date: %s' % time.ctime(time.time()),
+                'Platform: %s' % platform,
+                'Python: Python %s (%s)' % (sys.version.split()[0],
+                                              sys.executable),]
+
+    def applicationDetails(self):
+        """ Override for your application """
+        return []
+
+    # -----------------------------------------------------------------
+    # Text traceback
+
+    def formatTextTraceback(self):
+        template = self.textTracebackTemplate()
+        return template % self.formatOneTextTraceback(self.info)
+
+    def formatOneTextTraceback(self, info):
+        """ Separate to enable formatting multiple tracebacks. """
+        import traceback
+        return ''.join(traceback.format_exception(*info))
+    
+    def textTracebackTemplate(self):
+        return '''
+    
+<!-- The above is a description of an error in a Python program,
+     formatted for a Web browser. In case you are not reading this 
+     in a Web browser, here is the original traceback:
 
 %s
 -->
-''' % ''.join(traceback.format_exception(etype, evalue, etb))
+'''
+
 
 class Hook:
     """A hook to replace sys.excepthook that shows tracebacks in HTML."""
 
-    def __init__(self, display=1, logdir=None, context=5, file=None):
+    def __init__(self, display=1, logdir=None, context=5, file=None,
+                 format="html", viewClass=View, debug=0):
         self.display = display          # send tracebacks to browser if true
         self.logdir = logdir            # log tracebacks to files if not None
         self.context = context          # number of source code lines per frame
         self.file = file or sys.stdout  # place to send the output
+        self.format = format
+        self.viewClass = viewClass
+        self.debug = debug
 
     def __call__(self, etype, evalue, etb):
         self.handle((etype, evalue, etb))
 
     def handle(self, info=None):
         info = info or sys.exc_info()
-        self.file.write(reset())
-
+        if self.format.lower() == "html":
+            formatter = HTMLFormatter()
+            self.file.write(reset())
+            plain = False
+        else:
+            formatter = TextFormatter()
+            plain = True
         try:
-            text, doc = 0, html(info, self.context)
-        except:                         # just in case something goes wrong
+            view = self.viewClass(info, self.debug)
+            doc = view.format(formatter, self.context)
+        except:
+            raise
             import traceback
-            text, doc = 1, ''.join(traceback.format_exception(*info))
+            doc = ''.join(traceback.format_exception(*info))
+            plain = True
 
         if self.display:
-            if text:
+            if plain:
                 doc = doc.replace('&', '&amp;').replace('<', '&lt;')
                 self.file.write('<pre>' + doc + '</pre>\n')
             else:
@@ -192,10 +574,10 @@
 
         if self.logdir is not None:
             import os, tempfile
-            name = tempfile.mktemp(['.html', '.txt'][text])
-            path = os.path.join(self.logdir, os.path.basename(name))
+            suffix = ['.txt', '.html'][self.format=="html"]
+            (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
             try:
-                file = open(path, 'w')
+                file = os.fdopen(fd, 'w')
                 file.write(doc)
                 file.close()
                 msg = '<p> %s contains the description of this error.' % path
@@ -206,11 +588,15 @@
             self.file.flush()
         except: pass
 
+
 handler = Hook().handle
-def enable(display=1, logdir=None, context=5):
+
+def enable(display=1, logdir=None, context=5, format="html",
+           viewClass=View, debug=0):
     """Install an exception handler that formats tracebacks as HTML.
 
     The optional argument 'display' can be set to 0 to suppress sending the
     traceback to the browser, and 'logdir' can be set to a directory to cause
     tracebacks to be written to files there."""
-    sys.excepthook = Hook(display, logdir, context)
+    sys.excepthook = Hook(display=display, logdir=logdir, context=context,
+                          format=format, viewClass=viewClass, debug=debug)
--- a/docs/CHANGES	Wed Sep 28 23:09:56 2005 +0000
+++ b/docs/CHANGES	Thu Sep 29 00:07:04 2005 +0000
@@ -163,7 +163,7 @@
       that should fix quite some IE bugs and annoyances (on Win32).
       * for enabling IE7, use cfg.hacks = { 'ie7': True }
 
-  Fixes:
+  Fixes:  
     * Fixed a typo in xslt.py which led to a traceback instead of an
       error message in case of disabled XSLT support.
     * Fixed crash in twisted server if twisted.internet.ssl is not
@@ -198,8 +198,11 @@
       a useless mail with no account data in it. Now the system directly tells
       the user that he entered an unknown email address.
     * Fixed SystemInfo, it now also lists parsers in data/plugin/parser dir.
+    * Fix error handling on failure, improved error display
+    
+    
+Version 1.4:
 
-Version 1.4:
     We used that version number for an internal and early development version
     for what will be called moin 2.0 at some time in the future.
     There will never be a 1.4.x release.
--- a/wiki/server/moin.cgi	Wed Sep 28 23:09:56 2005 +0000
+++ b/wiki/server/moin.cgi	Thu Sep 29 00:07:04 2005 +0000
@@ -22,6 +22,9 @@
 # Path of the directory where farmconfig.py is located (if different).
 ## sys.path.insert(0, '/path/to/farmconfig')
 
+# Debug mode - show detailed error reports
+## import os
+## os.environ['MOIN_DEBUG'] = '1'
 
 # This is used to profile MoinMoin (default disabled)
 hotshotProfiler = 0
--- a/wiki/server/moin.fcg	Wed Sep 28 23:09:56 2005 +0000
+++ b/wiki/server/moin.fcg	Thu Sep 29 00:07:04 2005 +0000
@@ -22,6 +22,9 @@
 # Path of the directory where farmconfig is located (if different).
 ## sys.path.insert(0, '/path/to/farmconfig')
 
+# Debug mode - show detailed error reports
+## import os
+## os.environ['MOIN_DEBUG'] = '1'
 
 # Use threaded version or non-threaded version (default 1)?
 use_threads = 1
--- a/wiki/server/moin.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/wiki/server/moin.py	Thu Sep 29 00:07:04 2005 +0000
@@ -21,6 +21,9 @@
 # Path of the directory where farmconfig is located (if different).
 ## sys.path.insert(0, '/path/to/farmconfig')
 
+# Debug mode - show detailed error reports
+## import os
+## os.environ['MOIN_DEBUG'] = '1'
 
 from MoinMoin.server.standalone import StandaloneConfig, run
 
--- a/wiki/server/moinmodpy.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/wiki/server/moinmodpy.py	Thu Sep 29 00:07:04 2005 +0000
@@ -42,6 +42,9 @@
 # Path of the directory where farmconfig is located (if different).
 ## sys.path.insert(0, '/path/to/farmconfig')
 
+# Debug mode - show detailed error reports
+## import os
+## os.environ['MOIN_DEBUG'] = '1'
 
 # Set threads flag, so other code can use proper locking.
 # TODO: It seems that modpy does not use threads, so we don't need to
--- a/wiki/server/mointwisted.py	Wed Sep 28 23:09:56 2005 +0000
+++ b/wiki/server/mointwisted.py	Thu Sep 29 00:07:04 2005 +0000
@@ -23,6 +23,9 @@
 # Path to the directory where farmconfig is located (if different).
 ## sys.path.insert(0, '/path/to/farmconfig')
 
+# Debug mode - show detailed error reports
+## import os
+## os.environ['MOIN_DEBUG'] = '1'
 
 from MoinMoin.server.twistedmoin import TwistedConfig, makeApp