comparison unixSoft/lib/python/pdb.py @ 0:c30d68fbd368

Initial import from svn.
author Augie Fackler <durin42@gmail.com>
date Wed, 26 Nov 2008 10:56:09 -0600
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c30d68fbd368
1 """
2 Pdb++, a fancier version of pdb
3 ===============================
4
5 This module extends the stdlib pdb in numerous ways, e.g. by providing
6 real completion of Python values instead of pdb's own commands, or by
7 adding few convenience commands like ``longlist``, ``interact`` or
8 ``watch``.
9
10 For a full explanation of each command, refer to the docstring or type
11 help <command> at the prompt.
12
13 Installation
14 ------------
15
16 This module is meant to replace stdlib's pdb.py from the outsite;
17 simply put it in a directory in your PYTHONPATH, and you can start
18 using it immediately. Since it's named pdb.py, every place which
19 imports pdb will now find the new module.
20
21 Dependencies
22 ------------
23
24 To work properly, this module needs `rlcompleter_ng`_ to be installed.
25
26 To enable syntax highlighting, you must install `pygments`.
27
28 .. _pygments: http://pygments.org/
29 .. _`rlcompleter_ng`: http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ng.py
30
31 Configuration
32 -------------
33
34 To customize the configuration of Pdb++, you need to put a file named
35 .pdbrc.py in your home directory. The file must contain a class named
36 ``Config`` inheriting from ``DefaultConfig`` and overridding the
37 desired values.
38
39 To know which options are available, look at the comment in the
40 source.
41
42 You can find a sample configuration file, here:
43 http://codespeak.net/svn/user/antocuni/hack/pdbrc.py
44 """
45
46 __version__='0.1'
47 __author__ ='Antonio Cuni <anto.cuni@gmail.com>'
48 __url__='http://codespeak.net/svn/user/antocuni/hack/pdb.py'
49
50 import sys
51 import os.path
52 import inspect
53 import code
54 import types
55 from rlcompleter_ng import Completer, ConfigurableClass, setcolor, colors
56
57
58 def import_from_stdlib(name):
59 import code # arbitrary module which stays in the same dir as pdb
60 stdlibdir, _ = os.path.split(code.__file__)
61 pyfile = os.path.join(stdlibdir, name + '.py')
62 result = types.ModuleType(name)
63 mydict = execfile(pyfile, result.__dict__)
64 return result
65
66 pdb = import_from_stdlib('pdb')
67
68
69 class DefaultConfig:
70 prompt = '(Pdb++) '
71 completekey = 'tab'
72 highlight = True
73 bg = 'dark'
74 colorscheme = None
75
76 line_number_color = colors.turquoise
77 current_line_color = 44 # blue
78
79 def getsourcelines(obj):
80 try:
81 return inspect.getsourcelines(obj)
82 except IOError:
83 pass
84
85 if isinstance(obj, types.FrameType):
86 filename = obj.f_code.co_filename
87 if hasattr(filename, '__source__'):
88 first = max(1, obj.f_lineno - 5)
89 lines = [line + '\n' for line in filename.__source__.lines]
90 return lines, first
91 raise IOError('could not get source code')
92
93 def setbgcolor(line, color):
94 # hack hack hack
95 # add a bgcolor attribute to all escape sequences found
96 import re
97 setbg = '\x1b[%dm' % color
98 regexbg = '\\1;%dm' % color
99 return setbg + re.sub('(\x1b\\[.*?)m', regexbg, line) + '\x1b[00m'
100
101 CLEARSCREEN = '\033[2J\033[1;1H'
102
103 def lasti2lineno(code, lasti):
104 import dis
105 linestarts = list(dis.findlinestarts(code))
106 linestarts.reverse()
107 for i, lineno in linestarts:
108 if lasti >= i:
109 return lineno
110 assert False, 'Invalid instruction number: %s' % lasti
111
112 class Undefined:
113 def __repr__(self):
114 return '<undefined>'
115 undefined = Undefined()
116
117 class Pdb(pdb.Pdb, ConfigurableClass):
118
119 DefaultConfig = DefaultConfig
120 config_filename = '.pdbrc.py'
121
122 def __init__(self, *args, **kwds):
123 Config = kwds.pop('Config', None)
124 pdb.Pdb.__init__(self, *args, **kwds)
125 self.config = self.get_config(Config)
126 self.prompt = self.config.prompt
127 self.completekey = self.config.completekey
128
129 self.mycompleter = None
130 self.watching = {} # frame --> (name --> last seen value)
131 self.sticky = False
132 self.sticky_ranges = {} # frame --> (start, end)
133 self.tb_lineno = {} # frame --> lineno where the exception raised
134
135 def interaction(self, frame, traceback, orig_traceback=None):
136 self.setup(frame, traceback, orig_traceback)
137 self.print_stack_entry(self.stack[self.curindex])
138 self.cmdloop()
139 self.forget()
140
141 def setup(self, frame, tb, orig_tb=None):
142 pdb.Pdb.setup(self, frame, tb)
143 tb = orig_tb
144 while tb:
145 lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
146 self.tb_lineno[tb.tb_frame] = lineno
147 tb = tb.tb_next
148
149 def forget(self):
150 pdb.Pdb.forget(self)
151 self.raise_lineno = {}
152
153 def complete(self, text, state):
154 if state == 0:
155 mydict = self.curframe.f_globals.copy()
156 mydict.update(self.curframe.f_locals)
157 self.mycompleter = Completer(mydict)
158 return self.mycompleter.complete(text, state)
159
160 def _init_pygments(self):
161 try:
162 from pygments.lexers import PythonLexer
163 from pygments.formatters import TerminalFormatter
164 except ImportError:
165 return False
166
167 if hasattr(self, '_fmt'):
168 return True
169
170 self._fmt = TerminalFormatter(bg=self.config.bg,
171 colorscheme=self.config.colorscheme)
172 self._lexer = PythonLexer()
173 return True
174
175 def format_source(self, src):
176 if not self._init_pygments():
177 return src
178 from pygments import highlight, lex
179 return highlight(src, self._lexer, self._fmt)
180
181 def format_line(self, lineno, marker, line):
182 lineno = '%4d' % lineno
183 if self.config.highlight:
184 lineno = setcolor(lineno, self.config.line_number_color)
185 line = '%s %2s %s' % (lineno, marker, line)
186 if self.config.highlight and marker == '->':
187 line = setbgcolor(line, self.config.current_line_color)
188 return line
189
190 def parseline(self, line):
191 cmd, arg, newline = pdb.Pdb.parseline(self, line)
192 # don't execute short disruptive commands if a variable with
193 # the name exits in the current contex; this prevents pdb to
194 # quit if you type e.g. 'r[0]' by mystake.
195 if cmd in ['c', 'r', 'q'] and (cmd in self.curframe.f_globals or
196 cmd in self.curframe.f_locals):
197 line = '!' + line
198 return pdb.Pdb.parseline(self, line)
199 return cmd, arg, newline
200
201 def do_longlist(self, arg):
202 """
203 {longlist|ll}
204 List source code for the current function.
205
206 Differently that list, the whole function is displayed; the
207 current line is marked with '->'. In case of post-mortem
208 debugging, the line which effectively raised the exception is
209 marked with '>>'.
210
211 If the 'highlight' config option is set and pygments is
212 installed, the source code is colorized.
213 """
214 self.lastcmd = 'longlist'
215 self._printlonglist()
216
217 def _printlonglist(self, linerange=None):
218 try:
219 lines, lineno = getsourcelines(self.curframe)
220 except IOError, e:
221 print '** Error: %s **' % e
222 return
223 if linerange:
224 start, end = linerange
225 start = max(start, lineno)
226 end = min(end, lineno+len(lines))
227 lines = lines[start-lineno:end-lineno]
228 lineno = start
229 self._print_lines(lines, lineno)
230
231 def _print_lines(self, lines, lineno, print_markers=True):
232 exc_lineno = self.tb_lineno.get(self.curframe, None)
233 lines = [line[:-1] for line in lines] # remove the trailing '\n'
234 if self.config.highlight:
235 maxlength = max(map(len, lines))
236 lines = [line.ljust(maxlength) for line in lines]
237 src = self.format_source('\n'.join(lines))
238 lines = src.splitlines()
239 for i, line in enumerate(lines):
240 marker = ''
241 if lineno == self.curframe.f_lineno and print_markers:
242 marker = '->'
243 elif lineno == exc_lineno and print_markers:
244 marker = '>>'
245 lines[i] = self.format_line(lineno, marker, line)
246 lineno += 1
247 print '\n'.join(lines)
248
249 do_ll = do_longlist
250
251 def do_list(self, arg):
252 from StringIO import StringIO
253 oldstdout = sys.stdout
254 sys.stdout = StringIO()
255 pdb.Pdb.do_list(self, arg)
256 src = self.format_source(sys.stdout.getvalue())
257 sys.stdout = oldstdout
258 print src,
259
260 do_l = do_list
261
262 def do_interact(self, arg):
263 """
264 interact
265
266 Start an interative interpreter whose global namespace
267 contains all the names found in the current scope.
268 """
269 ns = self.curframe.f_globals.copy()
270 ns.update(self.curframe.f_locals)
271 code.interact("*interactive*", local=ns)
272
273 def do_track(self, arg):
274 """
275 track expression
276
277 Display a graph showing which objects are referred by the
278 value of the expression. This command requires pypy to be in
279 the current PYTHONPATH.
280 """
281 try:
282 from pypy.translator.tool.reftracker import track
283 except ImportError:
284 print '** cannot import pypy.translator.tool.reftracker **'
285 return
286 val = self._getval(arg)
287 track(val)
288
289 def _get_watching(self):
290 return self.watching.setdefault(self.curframe, {})
291
292 def _getval_or_undefined(self, arg):
293 try:
294 return eval(arg, self.curframe.f_globals,
295 self.curframe.f_locals)
296 except NameError:
297 return undefined
298
299 def do_watch(self, arg):
300 """
301 watch expression
302
303 Add expression to the watching list; expressions in this list
304 are evaluated at each step, and printed every time its value
305 changes.
306
307 WARNING: since the expressions is evaluated multiple time, pay
308 attention not to put expressions with side-effects in the
309 watching list.
310 """
311 try:
312 value = self._getval_or_undefined(arg)
313 except:
314 return
315 self._get_watching()[arg] = value
316
317 def do_unwatch(self, arg):
318 """
319 unwatch expression
320
321 Remove expression from the watching list.
322 """
323 try:
324 del self._get_watching()[arg]
325 except KeyError:
326 print '** not watching %s **' % arg
327
328 def _print_if_sticky(self):
329 if self.sticky:
330 sys.stdout.write(CLEARSCREEN)
331 frame, lineno = self.stack[self.curindex]
332 filename = self.canonic(frame.f_code.co_filename)
333 s = '> %s(%r)' % (filename, lineno)
334 print s
335 print
336 sticky_range = self.sticky_ranges.get(self.curframe, None)
337 self._printlonglist(sticky_range)
338
339 def do_sticky(self, arg):
340 """
341 sticky [start end]
342
343 Toggle sticky mode. When in sticky mode, it clear the screen
344 and longlist the current functions, making the source
345 appearing always in the same position. Useful to follow the
346 flow control of a function when doing step-by-step execution.
347
348 If ``start`` and ``end`` are given, sticky mode is enabled and
349 only lines within that range (extremes included) will be
350 displayed.
351 """
352 if arg:
353 try:
354 start, end = map(int, arg.split())
355 except ValueError:
356 print '** Error when parsing argument: %s **' % arg
357 return
358 self.sticky = True
359 self.sticky_ranges[self.curframe] = start, end+1
360 else:
361 self.sticky = not self.sticky
362 self.sticky_range = None
363 self._print_if_sticky()
364
365 def preloop(self):
366 self._print_if_sticky()
367 watching = self._get_watching()
368 for expr, oldvalue in watching.iteritems():
369 newvalue = self._getval_or_undefined(expr)
370 # check for identity first; this prevents custom __eq__ to
371 # be called at every loop, and also prevents instances
372 # whose fields are changed to be displayed
373 if newvalue is not oldvalue or newvalue != oldvalue:
374 watching[expr] = newvalue
375 print '%s: %r --> %r' % (expr, oldvalue, newvalue)
376
377 def do_source(self, arg):
378 try:
379 obj = self._getval(arg)
380 except:
381 return
382 try:
383 lines, lineno = getsourcelines(obj)
384 except (IOError, TypeError), e:
385 print '** Error: %s **' % e
386 return
387 self._print_lines(lines, lineno, print_markers=False)
388
389 # Simplified interface
390
391 # copy some functions from pdb.py, but rebind the global dictionary
392 for name in 'run runeval runctx runcall set_trace pm'.split():
393 func = getattr(pdb, name)
394 newfunc = types.FunctionType(func.func_code, globals(), func.func_name)
395 globals()[name] = newfunc
396 del name, func, newfunc
397
398 def post_mortem(t, Pdb=Pdb):
399 p = Pdb()
400 p.reset()
401 orig_tb = t
402 while t.tb_next is not None:
403 t = t.tb_next
404 p.interaction(t.tb_frame, t, orig_tb)
405
406 # pdb++ specific interface
407
408 def xpm(Pdb=Pdb):
409 """
410 To be used inside an except clause, enter a post-mortem pdb
411 related to the just catched exception.
412 """
413 post_mortem(sys.exc_info()[2], Pdb)