]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/cgitb.py
aux/cpuid: decode family and model bitfields
[plan9front.git] / sys / lib / python / cgitb.py
1 """More comprehensive traceback formatting for Python scripts.
2
3 To enable this module, do:
4
5     import cgitb; cgitb.enable()
6
7 at the top of your script.  The optional arguments to enable() are:
8
9     display     - if true, tracebacks are displayed in the web browser
10     logdir      - if set, tracebacks are written to files in this directory
11     context     - number of lines of source code to show for each stack frame
12     format      - 'text' or 'html' controls the output format
13
14 By default, tracebacks are displayed but not saved, the context is 5 lines
15 and the output format is 'html' (for backwards compatibility with the
16 original use of this module)
17
18 Alternatively, if you have caught an exception and want cgitb to display it
19 for you, call cgitb.handler().  The optional argument to handler() is a
20 3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
21 The default handler displays output as HTML.
22 """
23
24 __author__ = 'Ka-Ping Yee'
25
26 __version__ = '$Revision: 39758 $'
27
28 import sys
29
30 def reset():
31     """Return a string that resets the CGI and browser to a known state."""
32     return '''<!--: spam
33 Content-Type: text/html
34
35 <body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> -->
36 <body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> -->
37 </font> </font> </font> </script> </object> </blockquote> </pre>
38 </table> </table> </table> </table> </table> </font> </font> </font>'''
39
40 __UNDEF__ = []                          # a special sentinel object
41 def small(text):
42     if text:
43         return '<small>' + text + '</small>'
44     else:
45         return ''
46
47 def strong(text):
48     if text:
49         return '<strong>' + text + '</strong>'
50     else:
51         return ''
52
53 def grey(text):
54     if text:
55         return '<font color="#909090">' + text + '</font>'
56     else:
57         return ''
58
59 def lookup(name, frame, locals):
60     """Find the value for a given name in the given environment."""
61     if name in locals:
62         return 'local', locals[name]
63     if name in frame.f_globals:
64         return 'global', frame.f_globals[name]
65     if '__builtins__' in frame.f_globals:
66         builtins = frame.f_globals['__builtins__']
67         if type(builtins) is type({}):
68             if name in builtins:
69                 return 'builtin', builtins[name]
70         else:
71             if hasattr(builtins, name):
72                 return 'builtin', getattr(builtins, name)
73     return None, __UNDEF__
74
75 def scanvars(reader, frame, locals):
76     """Scan one logical line of Python and look up values of variables used."""
77     import tokenize, keyword
78     vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
79     for ttype, token, start, end, line in tokenize.generate_tokens(reader):
80         if ttype == tokenize.NEWLINE: break
81         if ttype == tokenize.NAME and token not in keyword.kwlist:
82             if lasttoken == '.':
83                 if parent is not __UNDEF__:
84                     value = getattr(parent, token, __UNDEF__)
85                     vars.append((prefix + token, prefix, value))
86             else:
87                 where, value = lookup(token, frame, locals)
88                 vars.append((token, where, value))
89         elif token == '.':
90             prefix += lasttoken + '.'
91             parent = value
92         else:
93             parent, prefix = None, ''
94         lasttoken = token
95     return vars
96
97 def html((etype, evalue, etb), context=5):
98     """Return a nice HTML document describing a given traceback."""
99     import os, types, time, traceback, linecache, inspect, pydoc
100
101     if type(etype) is types.ClassType:
102         etype = etype.__name__
103     pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
104     date = time.ctime(time.time())
105     head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading(
106         '<big><big>%s</big></big>' %
107         strong(pydoc.html.escape(str(etype))),
108         '#ffffff', '#6622aa', pyver + '<br>' + date) + '''
109 <p>A problem occurred in a Python script.  Here is the sequence of
110 function calls leading up to the error, in the order they occurred.</p>'''
111
112     indent = '<tt>' + small('&nbsp;' * 5) + '&nbsp;</tt>'
113     frames = []
114     records = inspect.getinnerframes(etb, context)
115     for frame, file, lnum, func, lines, index in records:
116         if file:
117             file = os.path.abspath(file)
118             link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file))
119         else:
120             file = link = '?'
121         args, varargs, varkw, locals = inspect.getargvalues(frame)
122         call = ''
123         if func != '?':
124             call = 'in ' + strong(func) + \
125                 inspect.formatargvalues(args, varargs, varkw, locals,
126                     formatvalue=lambda value: '=' + pydoc.html.repr(value))
127
128         highlight = {}
129         def reader(lnum=[lnum]):
130             highlight[lnum[0]] = 1
131             try: return linecache.getline(file, lnum[0])
132             finally: lnum[0] += 1
133         vars = scanvars(reader, frame, locals)
134
135         rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' %
136                 ('<big>&nbsp;</big>', link, call)]
137         if index is not None:
138             i = lnum - index
139             for line in lines:
140                 num = small('&nbsp;' * (5-len(str(i))) + str(i)) + '&nbsp;'
141                 line = '<tt>%s%s</tt>' % (num, pydoc.html.preformat(line))
142                 if i in highlight:
143                     rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
144                 else:
145                     rows.append('<tr><td>%s</td></tr>' % grey(line))
146                 i += 1
147
148         done, dump = {}, []
149         for name, where, value in vars:
150             if name in done: continue
151             done[name] = 1
152             if value is not __UNDEF__:
153                 if where in ('global', 'builtin'):
154                     name = ('<em>%s</em> ' % where) + strong(name)
155                 elif where == 'local':
156                     name = strong(name)
157                 else:
158                     name = where + strong(name.split('.')[-1])
159                 dump.append('%s&nbsp;= %s' % (name, pydoc.html.repr(value)))
160             else:
161                 dump.append(name + ' <em>undefined</em>')
162
163         rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump))))
164         frames.append('''
165 <table width="100%%" cellspacing=0 cellpadding=0 border=0>
166 %s</table>''' % '\n'.join(rows))
167
168     exception = ['<p>%s: %s' % (strong(pydoc.html.escape(str(etype))),
169                                 pydoc.html.escape(str(evalue)))]
170     if type(evalue) is types.InstanceType:
171         for name in dir(evalue):
172             if name[:1] == '_': continue
173             value = pydoc.html.repr(getattr(evalue, name))
174             exception.append('\n<br>%s%s&nbsp;=\n%s' % (indent, name, value))
175
176     import traceback
177     return head + ''.join(frames) + ''.join(exception) + '''
178
179
180 <!-- The above is a description of an error in a Python program, formatted
181      for a Web browser because the 'cgitb' module was enabled.  In case you
182      are not reading this in a Web browser, here is the original traceback:
183
184 %s
185 -->
186 ''' % ''.join(traceback.format_exception(etype, evalue, etb))
187
188 def text((etype, evalue, etb), context=5):
189     """Return a plain text document describing a given traceback."""
190     import os, types, time, traceback, linecache, inspect, pydoc
191
192     if type(etype) is types.ClassType:
193         etype = etype.__name__
194     pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
195     date = time.ctime(time.time())
196     head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
197 A problem occurred in a Python script.  Here is the sequence of
198 function calls leading up to the error, in the order they occurred.
199 '''
200
201     frames = []
202     records = inspect.getinnerframes(etb, context)
203     for frame, file, lnum, func, lines, index in records:
204         file = file and os.path.abspath(file) or '?'
205         args, varargs, varkw, locals = inspect.getargvalues(frame)
206         call = ''
207         if func != '?':
208             call = 'in ' + func + \
209                 inspect.formatargvalues(args, varargs, varkw, locals,
210                     formatvalue=lambda value: '=' + pydoc.text.repr(value))
211
212         highlight = {}
213         def reader(lnum=[lnum]):
214             highlight[lnum[0]] = 1
215             try: return linecache.getline(file, lnum[0])
216             finally: lnum[0] += 1
217         vars = scanvars(reader, frame, locals)
218
219         rows = [' %s %s' % (file, call)]
220         if index is not None:
221             i = lnum - index
222             for line in lines:
223                 num = '%5d ' % i
224                 rows.append(num+line.rstrip())
225                 i += 1
226
227         done, dump = {}, []
228         for name, where, value in vars:
229             if name in done: continue
230             done[name] = 1
231             if value is not __UNDEF__:
232                 if where == 'global': name = 'global ' + name
233                 elif where != 'local': name = where + name.split('.')[-1]
234                 dump.append('%s = %s' % (name, pydoc.text.repr(value)))
235             else:
236                 dump.append(name + ' undefined')
237
238         rows.append('\n'.join(dump))
239         frames.append('\n%s\n' % '\n'.join(rows))
240
241     exception = ['%s: %s' % (str(etype), str(evalue))]
242     if type(evalue) is types.InstanceType:
243         for name in dir(evalue):
244             value = pydoc.text.repr(getattr(evalue, name))
245             exception.append('\n%s%s = %s' % (" "*4, name, value))
246
247     import traceback
248     return head + ''.join(frames) + ''.join(exception) + '''
249
250 The above is a description of an error in a Python program.  Here is
251 the original traceback:
252
253 %s
254 ''' % ''.join(traceback.format_exception(etype, evalue, etb))
255
256 class Hook:
257     """A hook to replace sys.excepthook that shows tracebacks in HTML."""
258
259     def __init__(self, display=1, logdir=None, context=5, file=None,
260                  format="html"):
261         self.display = display          # send tracebacks to browser if true
262         self.logdir = logdir            # log tracebacks to files if not None
263         self.context = context          # number of source code lines per frame
264         self.file = file or sys.stdout  # place to send the output
265         self.format = format
266
267     def __call__(self, etype, evalue, etb):
268         self.handle((etype, evalue, etb))
269
270     def handle(self, info=None):
271         info = info or sys.exc_info()
272         if self.format == "html":
273             self.file.write(reset())
274
275         formatter = (self.format=="html") and html or text
276         plain = False
277         try:
278             doc = formatter(info, self.context)
279         except:                         # just in case something goes wrong
280             import traceback
281             doc = ''.join(traceback.format_exception(*info))
282             plain = True
283
284         if self.display:
285             if plain:
286                 doc = doc.replace('&', '&amp;').replace('<', '&lt;')
287                 self.file.write('<pre>' + doc + '</pre>\n')
288             else:
289                 self.file.write(doc + '\n')
290         else:
291             self.file.write('<p>A problem occurred in a Python script.\n')
292
293         if self.logdir is not None:
294             import os, tempfile
295             suffix = ['.txt', '.html'][self.format=="html"]
296             (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
297             try:
298                 file = os.fdopen(fd, 'w')
299                 file.write(doc)
300                 file.close()
301                 msg = '<p> %s contains the description of this error.' % path
302             except:
303                 msg = '<p> Tried to save traceback to %s, but failed.' % path
304             self.file.write(msg + '\n')
305         try:
306             self.file.flush()
307         except: pass
308
309 handler = Hook().handle
310 def enable(display=1, logdir=None, context=5, format="html"):
311     """Install an exception handler that formats tracebacks as HTML.
312
313     The optional argument 'display' can be set to 0 to suppress sending the
314     traceback to the browser, and 'logdir' can be set to a directory to cause
315     tracebacks to be written to files there."""
316     sys.excepthook = Hook(display=display, logdir=logdir,
317                           context=context, format=format)