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