Mercurial > moin > 1.9
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, 1007 insertions(+), 449 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 + +Supply Error class and sub classes used to raise various errors - @copyright: 2004 by Nir Soffer - @license: GNU GPL, see COPYING for details. +@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 +def _makeConfig(name): + """ Create and return a config instance - 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 + Timestamp config with either module mtime or farmconfig mtime. This + mtime can be used later to invalidate older caches. - 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 = ''' + @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 + raise error.ConfigurationError(msg) + return cfg - # 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)} - 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(' ' * 5) + ' </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> </big>', link, call)] - if index is not None: - i = lnum - index - for line in lines: - num = small(' ' * (5-len(str(i))) + str(i)) + ' ' - 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 = %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 =\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('&', '&').replace('<', '<') 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