Mercurial > dotfiles
view unixSoft/lib/python/pdb.py @ 172:593b5263291d
emacs: different font height on OS X
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Wed, 16 Dec 2009 22:27:44 -0600 |
parents | c30d68fbd368 |
children |
line wrap: on
line source
""" Pdb++, a fancier version of pdb =============================== This module extends the stdlib pdb in numerous ways, e.g. by providing real completion of Python values instead of pdb's own commands, or by adding few convenience commands like ``longlist``, ``interact`` or ``watch``. For a full explanation of each command, refer to the docstring or type help <command> at the prompt. Installation ------------ This module is meant to replace stdlib's pdb.py from the outsite; simply put it in a directory in your PYTHONPATH, and you can start using it immediately. Since it's named pdb.py, every place which imports pdb will now find the new module. Dependencies ------------ To work properly, this module needs `rlcompleter_ng`_ to be installed. To enable syntax highlighting, you must install `pygments`. .. _pygments: http://pygments.org/ .. _`rlcompleter_ng`: http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ng.py Configuration ------------- To customize the configuration of Pdb++, you need to put a file named .pdbrc.py in your home directory. The file must contain a class named ``Config`` inheriting from ``DefaultConfig`` and overridding the desired values. To know which options are available, look at the comment in the source. You can find a sample configuration file, here: http://codespeak.net/svn/user/antocuni/hack/pdbrc.py """ __version__='0.1' __author__ ='Antonio Cuni <anto.cuni@gmail.com>' __url__='http://codespeak.net/svn/user/antocuni/hack/pdb.py' import sys import os.path import inspect import code import types from rlcompleter_ng import Completer, ConfigurableClass, setcolor, colors def import_from_stdlib(name): import code # arbitrary module which stays in the same dir as pdb stdlibdir, _ = os.path.split(code.__file__) pyfile = os.path.join(stdlibdir, name + '.py') result = types.ModuleType(name) mydict = execfile(pyfile, result.__dict__) return result pdb = import_from_stdlib('pdb') class DefaultConfig: prompt = '(Pdb++) ' completekey = 'tab' highlight = True bg = 'dark' colorscheme = None line_number_color = colors.turquoise current_line_color = 44 # blue def getsourcelines(obj): try: return inspect.getsourcelines(obj) except IOError: pass if isinstance(obj, types.FrameType): filename = obj.f_code.co_filename if hasattr(filename, '__source__'): first = max(1, obj.f_lineno - 5) lines = [line + '\n' for line in filename.__source__.lines] return lines, first raise IOError('could not get source code') def setbgcolor(line, color): # hack hack hack # add a bgcolor attribute to all escape sequences found import re setbg = '\x1b[%dm' % color regexbg = '\\1;%dm' % color return setbg + re.sub('(\x1b\\[.*?)m', regexbg, line) + '\x1b[00m' CLEARSCREEN = '\033[2J\033[1;1H' def lasti2lineno(code, lasti): import dis linestarts = list(dis.findlinestarts(code)) linestarts.reverse() for i, lineno in linestarts: if lasti >= i: return lineno assert False, 'Invalid instruction number: %s' % lasti class Undefined: def __repr__(self): return '<undefined>' undefined = Undefined() class Pdb(pdb.Pdb, ConfigurableClass): DefaultConfig = DefaultConfig config_filename = '.pdbrc.py' def __init__(self, *args, **kwds): Config = kwds.pop('Config', None) pdb.Pdb.__init__(self, *args, **kwds) self.config = self.get_config(Config) self.prompt = self.config.prompt self.completekey = self.config.completekey self.mycompleter = None self.watching = {} # frame --> (name --> last seen value) self.sticky = False self.sticky_ranges = {} # frame --> (start, end) self.tb_lineno = {} # frame --> lineno where the exception raised def interaction(self, frame, traceback, orig_traceback=None): self.setup(frame, traceback, orig_traceback) self.print_stack_entry(self.stack[self.curindex]) self.cmdloop() self.forget() def setup(self, frame, tb, orig_tb=None): pdb.Pdb.setup(self, frame, tb) tb = orig_tb while tb: lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti) self.tb_lineno[tb.tb_frame] = lineno tb = tb.tb_next def forget(self): pdb.Pdb.forget(self) self.raise_lineno = {} def complete(self, text, state): if state == 0: mydict = self.curframe.f_globals.copy() mydict.update(self.curframe.f_locals) self.mycompleter = Completer(mydict) return self.mycompleter.complete(text, state) def _init_pygments(self): try: from pygments.lexers import PythonLexer from pygments.formatters import TerminalFormatter except ImportError: return False if hasattr(self, '_fmt'): return True self._fmt = TerminalFormatter(bg=self.config.bg, colorscheme=self.config.colorscheme) self._lexer = PythonLexer() return True def format_source(self, src): if not self._init_pygments(): return src from pygments import highlight, lex return highlight(src, self._lexer, self._fmt) def format_line(self, lineno, marker, line): lineno = '%4d' % lineno if self.config.highlight: lineno = setcolor(lineno, self.config.line_number_color) line = '%s %2s %s' % (lineno, marker, line) if self.config.highlight and marker == '->': line = setbgcolor(line, self.config.current_line_color) return line def parseline(self, line): cmd, arg, newline = pdb.Pdb.parseline(self, line) # don't execute short disruptive commands if a variable with # the name exits in the current contex; this prevents pdb to # quit if you type e.g. 'r[0]' by mystake. if cmd in ['c', 'r', 'q'] and (cmd in self.curframe.f_globals or cmd in self.curframe.f_locals): line = '!' + line return pdb.Pdb.parseline(self, line) return cmd, arg, newline def do_longlist(self, arg): """ {longlist|ll} List source code for the current function. Differently that list, the whole function is displayed; the current line is marked with '->'. In case of post-mortem debugging, the line which effectively raised the exception is marked with '>>'. If the 'highlight' config option is set and pygments is installed, the source code is colorized. """ self.lastcmd = 'longlist' self._printlonglist() def _printlonglist(self, linerange=None): try: lines, lineno = getsourcelines(self.curframe) except IOError, e: print '** Error: %s **' % e return if linerange: start, end = linerange start = max(start, lineno) end = min(end, lineno+len(lines)) lines = lines[start-lineno:end-lineno] lineno = start self._print_lines(lines, lineno) def _print_lines(self, lines, lineno, print_markers=True): exc_lineno = self.tb_lineno.get(self.curframe, None) lines = [line[:-1] for line in lines] # remove the trailing '\n' if self.config.highlight: maxlength = max(map(len, lines)) lines = [line.ljust(maxlength) for line in lines] src = self.format_source('\n'.join(lines)) lines = src.splitlines() for i, line in enumerate(lines): marker = '' if lineno == self.curframe.f_lineno and print_markers: marker = '->' elif lineno == exc_lineno and print_markers: marker = '>>' lines[i] = self.format_line(lineno, marker, line) lineno += 1 print '\n'.join(lines) do_ll = do_longlist def do_list(self, arg): from StringIO import StringIO oldstdout = sys.stdout sys.stdout = StringIO() pdb.Pdb.do_list(self, arg) src = self.format_source(sys.stdout.getvalue()) sys.stdout = oldstdout print src, do_l = do_list def do_interact(self, arg): """ interact Start an interative interpreter whose global namespace contains all the names found in the current scope. """ ns = self.curframe.f_globals.copy() ns.update(self.curframe.f_locals) code.interact("*interactive*", local=ns) def do_track(self, arg): """ track expression Display a graph showing which objects are referred by the value of the expression. This command requires pypy to be in the current PYTHONPATH. """ try: from pypy.translator.tool.reftracker import track except ImportError: print '** cannot import pypy.translator.tool.reftracker **' return val = self._getval(arg) track(val) def _get_watching(self): return self.watching.setdefault(self.curframe, {}) def _getval_or_undefined(self, arg): try: return eval(arg, self.curframe.f_globals, self.curframe.f_locals) except NameError: return undefined def do_watch(self, arg): """ watch expression Add expression to the watching list; expressions in this list are evaluated at each step, and printed every time its value changes. WARNING: since the expressions is evaluated multiple time, pay attention not to put expressions with side-effects in the watching list. """ try: value = self._getval_or_undefined(arg) except: return self._get_watching()[arg] = value def do_unwatch(self, arg): """ unwatch expression Remove expression from the watching list. """ try: del self._get_watching()[arg] except KeyError: print '** not watching %s **' % arg def _print_if_sticky(self): if self.sticky: sys.stdout.write(CLEARSCREEN) frame, lineno = self.stack[self.curindex] filename = self.canonic(frame.f_code.co_filename) s = '> %s(%r)' % (filename, lineno) print s print sticky_range = self.sticky_ranges.get(self.curframe, None) self._printlonglist(sticky_range) def do_sticky(self, arg): """ sticky [start end] Toggle sticky mode. When in sticky mode, it clear the screen and longlist the current functions, making the source appearing always in the same position. Useful to follow the flow control of a function when doing step-by-step execution. If ``start`` and ``end`` are given, sticky mode is enabled and only lines within that range (extremes included) will be displayed. """ if arg: try: start, end = map(int, arg.split()) except ValueError: print '** Error when parsing argument: %s **' % arg return self.sticky = True self.sticky_ranges[self.curframe] = start, end+1 else: self.sticky = not self.sticky self.sticky_range = None self._print_if_sticky() def preloop(self): self._print_if_sticky() watching = self._get_watching() for expr, oldvalue in watching.iteritems(): newvalue = self._getval_or_undefined(expr) # check for identity first; this prevents custom __eq__ to # be called at every loop, and also prevents instances # whose fields are changed to be displayed if newvalue is not oldvalue or newvalue != oldvalue: watching[expr] = newvalue print '%s: %r --> %r' % (expr, oldvalue, newvalue) def do_source(self, arg): try: obj = self._getval(arg) except: return try: lines, lineno = getsourcelines(obj) except (IOError, TypeError), e: print '** Error: %s **' % e return self._print_lines(lines, lineno, print_markers=False) # Simplified interface # copy some functions from pdb.py, but rebind the global dictionary for name in 'run runeval runctx runcall set_trace pm'.split(): func = getattr(pdb, name) newfunc = types.FunctionType(func.func_code, globals(), func.func_name) globals()[name] = newfunc del name, func, newfunc def post_mortem(t, Pdb=Pdb): p = Pdb() p.reset() orig_tb = t while t.tb_next is not None: t = t.tb_next p.interaction(t.tb_frame, t, orig_tb) # pdb++ specific interface def xpm(Pdb=Pdb): """ To be used inside an except clause, enter a post-mortem pdb related to the just catched exception. """ post_mortem(sys.exc_info()[2], Pdb)