| # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
| # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| # |
| # This file is part of logilab-common. |
| # |
| # logilab-common is free software: you can redistribute it and/or modify it under |
| # the terms of the GNU Lesser General Public License as published by the Free |
| # Software Foundation, either version 2.1 of the License, or (at your option) any |
| # later version. |
| # |
| # logilab-common is distributed in the hope that it will be useful, but WITHOUT |
| # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
| # details. |
| # |
| # You should have received a copy of the GNU Lesser General Public License along |
| # with logilab-common. If not, see <http://www.gnu.org/licenses/>. |
| """Customized version of pdb's default debugger. |
| |
| - sets up a history file |
| - uses ipython if available to colorize lines of code |
| - overrides list command to search for current block instead |
| of using 5 lines of context |
| |
| |
| |
| |
| """ |
| __docformat__ = "restructuredtext en" |
| |
| try: |
| import readline |
| except ImportError: |
| readline = None |
| import os |
| import os.path as osp |
| import sys |
| from pdb import Pdb |
| from cStringIO import StringIO |
| import inspect |
| |
| try: |
| from IPython import PyColorize |
| except ImportError: |
| def colorize(source, *args): |
| """fallback colorize function""" |
| return source |
| def colorize_source(source, *args): |
| return source |
| else: |
| def colorize(source, start_lineno, curlineno): |
| """colorize and annotate source with linenos |
| (as in pdb's list command) |
| """ |
| parser = PyColorize.Parser() |
| output = StringIO() |
| parser.format(source, output) |
| annotated = [] |
| for index, line in enumerate(output.getvalue().splitlines()): |
| lineno = index + start_lineno |
| if lineno == curlineno: |
| annotated.append('%4s\t->\t%s' % (lineno, line)) |
| else: |
| annotated.append('%4s\t\t%s' % (lineno, line)) |
| return '\n'.join(annotated) |
| |
| def colorize_source(source): |
| """colorize given source""" |
| parser = PyColorize.Parser() |
| output = StringIO() |
| parser.format(source, output) |
| return output.getvalue() |
| |
| |
| def getsource(obj): |
| """Return the text of the source code for an object. |
| |
| The argument may be a module, class, method, function, traceback, frame, |
| or code object. The source code is returned as a single string. An |
| IOError is raised if the source code cannot be retrieved.""" |
| lines, lnum = inspect.getsourcelines(obj) |
| return ''.join(lines), lnum |
| |
| |
| ################################################################ |
| class Debugger(Pdb): |
| """custom debugger |
| |
| - sets up a history file |
| - uses ipython if available to colorize lines of code |
| - overrides list command to search for current block instead |
| of using 5 lines of context |
| """ |
| def __init__(self, tcbk=None): |
| Pdb.__init__(self) |
| self.reset() |
| if tcbk: |
| while tcbk.tb_next is not None: |
| tcbk = tcbk.tb_next |
| self._tcbk = tcbk |
| self._histfile = os.path.expanduser("~/.pdbhist") |
| |
| def setup_history_file(self): |
| """if readline is available, read pdb history file |
| """ |
| if readline is not None: |
| try: |
| # XXX try..except shouldn't be necessary |
| # read_history_file() can accept None |
| readline.read_history_file(self._histfile) |
| except IOError: |
| pass |
| |
| def start(self): |
| """starts the interactive mode""" |
| self.interaction(self._tcbk.tb_frame, self._tcbk) |
| |
| def setup(self, frame, tcbk): |
| """setup hook: set up history file""" |
| self.setup_history_file() |
| Pdb.setup(self, frame, tcbk) |
| |
| def set_quit(self): |
| """quit hook: save commands in the history file""" |
| if readline is not None: |
| readline.write_history_file(self._histfile) |
| Pdb.set_quit(self) |
| |
| def complete_p(self, text, line, begin_idx, end_idx): |
| """provide variable names completion for the ``p`` command""" |
| namespace = dict(self.curframe.f_globals) |
| namespace.update(self.curframe.f_locals) |
| if '.' in text: |
| return self.attr_matches(text, namespace) |
| return [varname for varname in namespace if varname.startswith(text)] |
| |
| |
| def attr_matches(self, text, namespace): |
| """implementation coming from rlcompleter.Completer.attr_matches |
| Compute matches when text contains a dot. |
| |
| Assuming the text is of the form NAME.NAME....[NAME], and is |
| evaluatable in self.namespace, it will be evaluated and its attributes |
| (as revealed by dir()) are used as possible completions. (For class |
| instances, class members are also considered.) |
| |
| WARNING: this can still invoke arbitrary C code, if an object |
| with a __getattr__ hook is evaluated. |
| |
| """ |
| import re |
| m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) |
| if not m: |
| return |
| expr, attr = m.group(1, 3) |
| object = eval(expr, namespace) |
| words = dir(object) |
| if hasattr(object, '__class__'): |
| words.append('__class__') |
| words = words + self.get_class_members(object.__class__) |
| matches = [] |
| n = len(attr) |
| for word in words: |
| if word[:n] == attr and word != "__builtins__": |
| matches.append("%s.%s" % (expr, word)) |
| return matches |
| |
| def get_class_members(self, klass): |
| """implementation coming from rlcompleter.get_class_members""" |
| ret = dir(klass) |
| if hasattr(klass, '__bases__'): |
| for base in klass.__bases__: |
| ret = ret + self.get_class_members(base) |
| return ret |
| |
| ## specific / overridden commands |
| def do_list(self, arg): |
| """overrides default list command to display the surrounding block |
| instead of 5 lines of context |
| """ |
| self.lastcmd = 'list' |
| if not arg: |
| try: |
| source, start_lineno = getsource(self.curframe) |
| print colorize(''.join(source), start_lineno, |
| self.curframe.f_lineno) |
| except KeyboardInterrupt: |
| pass |
| except IOError: |
| Pdb.do_list(self, arg) |
| else: |
| Pdb.do_list(self, arg) |
| do_l = do_list |
| |
| def do_open(self, arg): |
| """opens source file corresponding to the current stack level""" |
| filename = self.curframe.f_code.co_filename |
| lineno = self.curframe.f_lineno |
| cmd = 'emacsclient --no-wait +%s %s' % (lineno, filename) |
| os.system(cmd) |
| |
| do_o = do_open |
| |
| def pm(): |
| """use our custom debugger""" |
| dbg = Debugger(sys.last_traceback) |
| dbg.start() |
| |
| def set_trace(): |
| Debugger().set_trace(sys._getframe().f_back) |