| """More comprehensive traceback formatting for Python scripts. | 
 |  | 
 | To enable this module, do: | 
 |  | 
 |     import cgitb; cgitb.enable() | 
 |  | 
 | 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 | 
 |  | 
 | 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(). | 
 | The default handler displays output as HTML. | 
 | """ | 
 |  | 
 | __author__ = 'Ka-Ping Yee' | 
 |  | 
 | __version__ = '$Revision$' | 
 |  | 
 | import sys | 
 |  | 
 | def reset(): | 
 |     """Return a string that resets the CGI and browser to a known state.""" | 
 |     return '''<!--: spam | 
 | Content-Type: text/html | 
 |  | 
 | <body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> | 
 | <body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> --> | 
 | </font> </font> </font> </script> </object> </blockquote> </pre> | 
 | </table> </table> </table> </table> </table> </font> </font> </font>''' | 
 |  | 
 | __UNDEF__ = []                          # a special sentinel object | 
 | def small(text): | 
 |     if text: | 
 |         return '<small>' + text + '</small>' | 
 |     else: | 
 |         return '' | 
 |  | 
 | def strong(text): | 
 |     if text: | 
 |         return '<strong>' + text + '</strong>' | 
 |     else: | 
 |         return '' | 
 |  | 
 | def grey(text): | 
 |     if text: | 
 |         return '<font color="#909090">' + text + '</font>' | 
 |     else: | 
 |         return '' | 
 |  | 
 | 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] | 
 |     if '__builtins__' in frame.f_globals: | 
 |         builtins = frame.f_globals['__builtins__'] | 
 |         if type(builtins) is type({}): | 
 |             if name in builtins: | 
 |                 return 'builtin', builtins[name] | 
 |         else: | 
 |             if hasattr(builtins, name): | 
 |                 return 'builtin', getattr(builtins, 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, 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 = 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 | 
 |  | 
 |     if type(etype) is types.ClassType: | 
 |         etype = etype.__name__ | 
 |     pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable | 
 |     date = time.ctime(time.time()) | 
 |     head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading( | 
 |         '<big><big>%s</big></big>' % | 
 |         strong(pydoc.html.escape(str(etype))), | 
 |         '#ffffff', '#6622aa', pyver + '<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.</p>''' | 
 |  | 
 |     indent = '<tt>' + small(' ' * 5) + ' </tt>' | 
 |     frames = [] | 
 |     records = inspect.getinnerframes(etb, context) | 
 |     for frame, file, lnum, func, lines, index in records: | 
 |         if file: | 
 |             file = os.path.abspath(file) | 
 |             link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file)) | 
 |         else: | 
 |             file = link = '?' | 
 |         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 in ('global', 'builtin'): | 
 |                     name = ('<em>%s</em> ' % where) + 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(''' | 
 | <table width="100%%" cellspacing=0 cellpadding=0 border=0> | 
 | %s</table>''' % '\n'.join(rows)) | 
 |  | 
 |     exception = ['<p>%s: %s' % (strong(pydoc.html.escape(str(etype))), | 
 |                                 pydoc.html.escape(str(evalue)))] | 
 |     if isinstance(evalue, BaseException): | 
 |         for name in dir(evalue): | 
 |             if name[:1] == '_': continue | 
 |             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) + ''' | 
 |  | 
 |  | 
 | <!-- 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: | 
 |  | 
 | %s | 
 | --> | 
 | ''' % pydoc.html.escape( | 
 |           ''.join(traceback.format_exception(etype, evalue, etb))) | 
 |  | 
 | def text((etype, evalue, etb), context=5): | 
 |     """Return a plain text document describing a given traceback.""" | 
 |     import os, types, time, traceback, linecache, inspect, pydoc | 
 |  | 
 |     if type(etype) is types.ClassType: | 
 |         etype = etype.__name__ | 
 |     pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable | 
 |     date = time.ctime(time.time()) | 
 |     head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + ''' | 
 | A problem occurred in a Python script.  Here is the sequence of | 
 | function calls leading up to the error, in the order they occurred. | 
 | ''' | 
 |  | 
 |     frames = [] | 
 |     records = inspect.getinnerframes(etb, context) | 
 |     for frame, file, lnum, func, lines, index in records: | 
 |         file = file and os.path.abspath(file) or '?' | 
 |         args, varargs, varkw, locals = inspect.getargvalues(frame) | 
 |         call = '' | 
 |         if func != '?': | 
 |             call = 'in ' + func + \ | 
 |                 inspect.formatargvalues(args, varargs, varkw, locals, | 
 |                     formatvalue=lambda value: '=' + pydoc.text.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 = [' %s %s' % (file, call)] | 
 |         if index is not None: | 
 |             i = lnum - index | 
 |             for line in lines: | 
 |                 num = '%5d ' % i | 
 |                 rows.append(num+line.rstrip()) | 
 |                 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 = 'global ' + name | 
 |                 elif where != 'local': name = where + name.split('.')[-1] | 
 |                 dump.append('%s = %s' % (name, pydoc.text.repr(value))) | 
 |             else: | 
 |                 dump.append(name + ' undefined') | 
 |  | 
 |         rows.append('\n'.join(dump)) | 
 |         frames.append('\n%s\n' % '\n'.join(rows)) | 
 |  | 
 |     exception = ['%s: %s' % (str(etype), str(evalue))] | 
 |     if isinstance(evalue, BaseException): | 
 |         for name in dir(evalue): | 
 |             value = pydoc.text.repr(getattr(evalue, name)) | 
 |             exception.append('\n%s%s = %s' % (" "*4, name, value)) | 
 |  | 
 |     import traceback | 
 |     return head + ''.join(frames) + ''.join(exception) + ''' | 
 |  | 
 | The above is a description of an error in a Python program.  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, | 
 |                  format="html"): | 
 |         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 | 
 |  | 
 |     def __call__(self, etype, evalue, etb): | 
 |         self.handle((etype, evalue, etb)) | 
 |  | 
 |     def handle(self, info=None): | 
 |         info = info or sys.exc_info() | 
 |         if self.format == "html": | 
 |             self.file.write(reset()) | 
 |  | 
 |         formatter = (self.format=="html") and html or text | 
 |         plain = False | 
 |         try: | 
 |             doc = formatter(info, self.context) | 
 |         except:                         # just in case something goes wrong | 
 |             import traceback | 
 |             doc = ''.join(traceback.format_exception(*info)) | 
 |             plain = True | 
 |  | 
 |         if self.display: | 
 |             if plain: | 
 |                 doc = doc.replace('&', '&').replace('<', '<') | 
 |                 self.file.write('<pre>' + doc + '</pre>\n') | 
 |             else: | 
 |                 self.file.write(doc + '\n') | 
 |         else: | 
 |             self.file.write('<p>A problem occurred in a Python script.\n') | 
 |  | 
 |         if self.logdir is not None: | 
 |             import os, tempfile | 
 |             suffix = ['.txt', '.html'][self.format=="html"] | 
 |             (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) | 
 |             try: | 
 |                 file = os.fdopen(fd, 'w') | 
 |                 file.write(doc) | 
 |                 file.close() | 
 |                 msg = '<p> %s contains the description of this error.' % path | 
 |             except: | 
 |                 msg = '<p> Tried to save traceback to %s, but failed.' % path | 
 |             self.file.write(msg + '\n') | 
 |         try: | 
 |             self.file.flush() | 
 |         except: pass | 
 |  | 
 | handler = Hook().handle | 
 | def enable(display=1, logdir=None, context=5, format="html"): | 
 |     """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=display, logdir=logdir, | 
 |                           context=context, format=format) |