| Guido van Rossum | 217a5fa | 1990-12-26 15:40:07 +0000 | [diff] [blame] | 1 | # Print tracebacks, with a dump of local variables. | 
|  | 2 | # Also an interactive stack trace browser. | 
|  | 3 |  | 
|  | 4 | import sys | 
|  | 5 | try: | 
|  | 6 | import mac | 
|  | 7 | os = mac | 
| Guido van Rossum | decc4b9 | 1991-12-26 13:06:22 +0000 | [diff] [blame] | 8 | except ImportError: | 
| Guido van Rossum | 217a5fa | 1990-12-26 15:40:07 +0000 | [diff] [blame] | 9 | import posix | 
|  | 10 | os = posix | 
|  | 11 | from stat import * | 
|  | 12 | import string | 
|  | 13 |  | 
|  | 14 | def br(): browser(sys.last_traceback) | 
|  | 15 |  | 
|  | 16 | def tb(): printtb(sys.last_traceback) | 
|  | 17 |  | 
|  | 18 | def browser(tb): | 
|  | 19 | if not tb: | 
|  | 20 | print 'No traceback.' | 
|  | 21 | return | 
|  | 22 | tblist = [] | 
|  | 23 | while tb: | 
|  | 24 | tblist.append(tb) | 
|  | 25 | tb = tb.tb_next | 
|  | 26 | ptr = len(tblist)-1 | 
|  | 27 | tb = tblist[ptr] | 
|  | 28 | while 1: | 
|  | 29 | if tb <> tblist[ptr]: | 
|  | 30 | tb = tblist[ptr] | 
|  | 31 | print `ptr` + ':', | 
|  | 32 | printtbheader(tb) | 
|  | 33 | try: | 
|  | 34 | line = raw_input('TB: ') | 
|  | 35 | except KeyboardInterrupt: | 
|  | 36 | print '\n[Interrupted]' | 
|  | 37 | break | 
|  | 38 | except EOFError: | 
|  | 39 | print '\n[EOF]' | 
|  | 40 | break | 
|  | 41 | cmd = string.strip(line) | 
|  | 42 | if cmd: | 
|  | 43 | if cmd = 'quit': | 
|  | 44 | break | 
|  | 45 | elif cmd = 'list': | 
|  | 46 | browserlist(tb) | 
|  | 47 | elif cmd = 'up': | 
|  | 48 | if ptr-1 >= 0: ptr = ptr-1 | 
|  | 49 | else: print 'Bottom of stack.' | 
|  | 50 | elif cmd = 'down': | 
|  | 51 | if ptr+1 < len(tblist): ptr = ptr+1 | 
|  | 52 | else: print 'Top of stack.' | 
|  | 53 | elif cmd = 'locals': | 
|  | 54 | printsymbols(tb.tb_frame.f_locals) | 
|  | 55 | elif cmd = 'globals': | 
|  | 56 | printsymbols(tb.tb_frame.f_globals) | 
|  | 57 | elif cmd in ('?', 'help'): | 
|  | 58 | browserhelp() | 
|  | 59 | else: | 
|  | 60 | browserexec(tb, cmd) | 
|  | 61 |  | 
|  | 62 | def browserlist(tb): | 
|  | 63 | filename = tb.tb_frame.f_code.co_filename | 
|  | 64 | lineno = tb.tb_lineno | 
|  | 65 | last = lineno | 
|  | 66 | first = max(1, last-10) | 
|  | 67 | for i in range(first, last+1): | 
|  | 68 | if i = lineno: prefix = '***' + string.rjust(`i`, 4) + ':' | 
|  | 69 | else: prefix = string.rjust(`i`, 7) + ':' | 
|  | 70 | line = readfileline(filename, i) | 
|  | 71 | if line[-1:] = '\n': line = line[:-1] | 
|  | 72 | print prefix + line | 
|  | 73 |  | 
|  | 74 | def browserexec(tb, cmd): | 
|  | 75 | locals = tb.tb_frame.f_locals | 
|  | 76 | globals = tb.tb_frame.f_globals | 
|  | 77 | try: | 
|  | 78 | exec(cmd+'\n', globals, locals) | 
|  | 79 | except: | 
|  | 80 | print '*** Exception:', | 
|  | 81 | print sys.exc_type, | 
|  | 82 | if sys.exc_value <> None: | 
|  | 83 | print ':', sys.exc_value, | 
|  | 84 | print | 
|  | 85 | print 'Type help to get help.' | 
|  | 86 |  | 
|  | 87 | def browserhelp(): | 
|  | 88 | print | 
|  | 89 | print '    This is the traceback browser.  Commands are:' | 
|  | 90 | print '        up      : move one level up in the call stack' | 
|  | 91 | print '        down    : move one level down in the call stack' | 
|  | 92 | print '        locals  : print all local variables at this level' | 
|  | 93 | print '        globals : print all global variables at this level' | 
|  | 94 | print '        list    : list source code around the failure' | 
|  | 95 | print '        help    : print help (what you are reading now)' | 
|  | 96 | print '        quit    : back to command interpreter' | 
|  | 97 | print '    Typing any other 1-line statement will execute it' | 
|  | 98 | print '    using the current level\'s symbol tables' | 
|  | 99 | print | 
|  | 100 |  | 
|  | 101 | def printtb(tb): | 
|  | 102 | while tb: | 
|  | 103 | print1tb(tb) | 
|  | 104 | tb = tb.tb_next | 
|  | 105 |  | 
|  | 106 | def print1tb(tb): | 
|  | 107 | printtbheader(tb) | 
|  | 108 | if tb.tb_frame.f_locals is not tb.tb_frame.f_globals: | 
|  | 109 | printsymbols(tb.tb_frame.f_locals) | 
|  | 110 |  | 
|  | 111 | def printtbheader(tb): | 
|  | 112 | filename = tb.tb_frame.f_code.co_filename | 
|  | 113 | lineno = tb.tb_lineno | 
|  | 114 | info = '"' + filename + '"(' + `lineno` + ')' | 
|  | 115 | line = readfileline(filename, lineno) | 
|  | 116 | if line: | 
|  | 117 | info = info + ': ' + string.strip(line) | 
|  | 118 | print info | 
|  | 119 |  | 
|  | 120 | def printsymbols(d): | 
|  | 121 | keys = d.keys() | 
|  | 122 | keys.sort() | 
|  | 123 | for name in keys: | 
|  | 124 | print '  ' + string.ljust(name, 12) + ':', | 
|  | 125 | printobject(d[name], 4) | 
|  | 126 | print | 
|  | 127 |  | 
|  | 128 | def printobject(v, maxlevel): | 
|  | 129 | if v = None: | 
|  | 130 | print 'None', | 
|  | 131 | elif type(v) in (type(0), type(0.0)): | 
|  | 132 | print v, | 
|  | 133 | elif type(v) = type(''): | 
|  | 134 | if len(v) > 20: | 
|  | 135 | print `v[:17] + '...'`, | 
|  | 136 | else: | 
|  | 137 | print `v`, | 
|  | 138 | elif type(v) = type(()): | 
|  | 139 | print '(', | 
|  | 140 | printlist(v, maxlevel) | 
|  | 141 | print ')', | 
|  | 142 | elif type(v) = type([]): | 
|  | 143 | print '[', | 
|  | 144 | printlist(v, maxlevel) | 
|  | 145 | print ']', | 
|  | 146 | elif type(v) = type({}): | 
|  | 147 | print '{', | 
|  | 148 | printdict(v, maxlevel) | 
|  | 149 | print '}', | 
|  | 150 | else: | 
|  | 151 | print v, | 
|  | 152 |  | 
|  | 153 | def printlist(v, maxlevel): | 
|  | 154 | n = len(v) | 
|  | 155 | if n = 0: return | 
|  | 156 | if maxlevel <= 0: | 
|  | 157 | print '...', | 
|  | 158 | return | 
|  | 159 | for i in range(min(6, n)): | 
|  | 160 | printobject(v[i], maxlevel-1) | 
|  | 161 | if i+1 < n: print ',', | 
|  | 162 | if n > 6: print '...', | 
|  | 163 |  | 
|  | 164 | def printdict(v, maxlevel): | 
|  | 165 | keys = v.keys() | 
|  | 166 | n = len(keys) | 
|  | 167 | if n = 0: return | 
|  | 168 | if maxlevel <= 0: | 
|  | 169 | print '...', | 
|  | 170 | return | 
|  | 171 | keys.sort() | 
|  | 172 | for i in range(min(6, n)): | 
|  | 173 | key = keys[i] | 
|  | 174 | print `key` + ':', | 
|  | 175 | printobject(v[key], maxlevel-1) | 
|  | 176 | if i+1 < n: print ',', | 
|  | 177 | if n > 6: print '...', | 
|  | 178 |  | 
|  | 179 | _filecache = {} | 
|  | 180 |  | 
|  | 181 | def readfileline(filename, lineno): | 
|  | 182 | try: | 
|  | 183 | stat = os.stat(filename) | 
|  | 184 | except os.error, msg: | 
|  | 185 | print 'Cannot stat', filename, '--', msg | 
|  | 186 | return '' | 
|  | 187 | cache_ok = 0 | 
|  | 188 | if _filecache.has_key(filename): | 
|  | 189 | cached_stat, lines = _filecache[filename] | 
|  | 190 | if stat[ST_SIZE] = cached_stat[ST_SIZE] and \ | 
|  | 191 | stat[ST_MTIME] = cached_stat[ST_MTIME]: | 
|  | 192 | cache_ok = 1 | 
|  | 193 | else: | 
|  | 194 | print 'Stale cache entry for', filename | 
|  | 195 | del _filecache[filename] | 
|  | 196 | if not cache_ok: | 
|  | 197 | lines = readfilelines(filename) | 
|  | 198 | if not lines: | 
|  | 199 | return '' | 
|  | 200 | _filecache[filename] = stat, lines | 
|  | 201 | if 0 <= lineno-1 < len(lines): | 
|  | 202 | return lines[lineno-1] | 
|  | 203 | else: | 
|  | 204 | print 'Line number out of range, last line is', len(lines) | 
|  | 205 | return '' | 
|  | 206 |  | 
|  | 207 | def readfilelines(filename): | 
|  | 208 | try: | 
|  | 209 | fp = open(filename, 'r') | 
|  | 210 | except: | 
|  | 211 | print 'Cannot open', filename | 
|  | 212 | return [] | 
|  | 213 | lines = [] | 
|  | 214 | while 1: | 
|  | 215 | line = fp.readline() | 
|  | 216 | if not line: break | 
|  | 217 | lines.append(line) | 
|  | 218 | if not lines: | 
|  | 219 | print 'Empty file', filename | 
|  | 220 | return lines |