From 364ca40c2a053718f67c2032769de1f1fd76bb22 Mon Sep 17 00:00:00 2001
From: Skip Montanaro <skip@pobox.com>
Date: Tue, 17 Jun 2003 12:58:31 +0000
Subject: SF Patch 569574 - enhancements to cgitb for plain text display

---
 Doc/lib/libcgitb.tex |  14 +++++--
 Lib/cgitb.py         | 109 ++++++++++++++++++++++++++++++++++++++++++++-------
 Misc/NEWS            |   3 ++
 3 files changed, 108 insertions(+), 18 deletions(-)

diff --git a/Doc/lib/libcgitb.tex b/Doc/lib/libcgitb.tex
index e0e4a01..dab34c5 100644
--- a/Doc/lib/libcgitb.tex
+++ b/Doc/lib/libcgitb.tex
@@ -12,9 +12,12 @@
 \index{exceptions!in CGI scripts}
 \index{tracebacks!in CGI scripts}
 
-The \module{cgitb} module provides a special exception handler for CGI
-scripts.  After this module is activated, if an uncaught exception occurs,
-a detailed, formatted report will be sent to the Web browser.  The report
+The \module{cgitb} module provides a special exception handler for Python
+scripts.  (It's name is a bit misleading.  It was originally designed to
+display extensive traceback information in HTML for CGI scripts.  It was
+later generalized to also display this information in plain text.)  After
+this module is activated, if an uncaught exception occurs, a detailed,
+formatted report will be displayed.  The report
 includes a traceback showing excerpts of the source code for each level,
 as well as the values of the arguments and local variables to currently
 running functions, to help you debug the problem.  Optionally, you can
@@ -32,7 +35,7 @@ to a file for later analysis.
 
 
 \begin{funcdesc}{enable}{\optional{display\optional{, logdir\optional{,
-                         context}}}}
+                         context\optional{, format}}}}}
   This function causes the \module{cgitb} module to take over the
   interpreter's default handling for exceptions by setting the
   value of \code{\refmodule{sys}.excepthook}.
@@ -46,6 +49,9 @@ to a file for later analysis.
   The optional argument \var{context} is the number of lines of
   context to display around the current line of source code in the
   traceback; this defaults to \code{5}.
+  If the optional argument \var{format} is \code{"html"}, the output is
+  formatted as HTML.  Any other value forces plain text output.  The default
+  value is \code{"html"}.
 \end{funcdesc}
 
 \begin{funcdesc}{handler}{\optional{info}}
diff --git a/Lib/cgitb.py b/Lib/cgitb.py
index 200e8ee..4e0c9fb 100644
--- a/Lib/cgitb.py
+++ b/Lib/cgitb.py
@@ -1,20 +1,25 @@
-"""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
 
-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.
+"""
 
 __author__ = 'Ka-Ping Yee'
 __version__ = '$Revision$'
@@ -160,30 +165,105 @@ function calls leading up to the error, in the order they occurred.'''
 -->
 ''' % ''.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 = name
+                else: 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 type(evalue) is types.InstanceType:
+        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):
+    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()
-        self.file.write(reset())
+        if self.format == "html":
+            self.file.write(reset())
 
+        formatter = (self.format=="html") and html or text
+        plain = False
         try:
-            text, doc = 0, html(info, self.context)
+            doc = formatter(info, self.context)
         except:                         # just in case something goes wrong
             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:
@@ -193,8 +273,8 @@ class Hook:
 
         if self.logdir is not None:
             import os, tempfile
-            (fd, path) = tempfile.mkstemp(suffix=['.html', '.txt'][text],
-                                          dir=self.logdir)
+            suffix = ['.html', '.txt'][self.format=="html"]
+            (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
             try:
                 file = os.fdopen(fd, 'w')
                 file.write(doc)
@@ -208,10 +288,11 @@ class Hook:
         except: pass
 
 handler = Hook().handle
-def enable(display=1, logdir=None, context=5):
+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, logdir, context)
+    sys.excepthook = Hook(display=display, logdir=logdir,
+                          context=context, format=format)
diff --git a/Misc/NEWS b/Misc/NEWS
index b8b9a9b..2d2c8a0 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -83,6 +83,9 @@ Extension modules
 Library
 -------
 
+- The cgitb module has been extended to support plain text display (SF patch
+  569574).
+
 - A brand new version of IDLE (from the IDLEfork project at
   SourceForge) is now included as Lib/idlelib.  The old Tools/idle is
   no more.
-- 
cgit v0.12

