Index: third_party/logilab/common/debugger.py |
diff --git a/third_party/logilab/common/debugger.py b/third_party/logilab/common/debugger.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7556322777fee918f51b47f64f5648677ca52ace |
--- /dev/null |
+++ b/third_party/logilab/common/debugger.py |
@@ -0,0 +1,210 @@ |
+# 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) |