Mercurial > dotfiles
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) |