Source code for logilab.common.debugger

# 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:
    # mypy: Incompatible types in assignment (expression has type "None",
    # mypy: variable has type Module))
    # conditional import
    readline = None  # type: ignore
import os
import sys
from pdb import Pdb
import inspect

from io import StringIO

try:
    from IPython import PyColorize
except ImportError:

    def colorize(source, start_lineno, curlineno):
        """fallback colorize function"""
        return source

    def colorize_source(source):
        return source

else:

[docs] 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(f"{lineno:>4}\t->\t{line}") else: annotated.append(f"{lineno:>4}\t\t{line}") return "\n".join(annotated)
[docs] def colorize_source(source): """colorize given source""" parser = PyColorize.Parser() output = StringIO() parser.format(source, output) return output.getvalue()
[docs]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
################################################################
[docs]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")
[docs] 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 OSError: pass
[docs] def start(self): """starts the interactive mode""" self.interaction(self._tcbk.tb_frame, self._tcbk)
[docs] def setup(self, frame, tcbk): """setup hook: set up history file""" self.setup_history_file() Pdb.setup(self, frame, tcbk)
[docs] 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)
[docs] 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)]
[docs] 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(f"{expr}.{word}") return matches
[docs] 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
[docs] 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 OSError: Pdb.do_list(self, arg) else: Pdb.do_list(self, arg)
do_l = do_list
[docs] 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 = f"emacsclient --no-wait +{lineno} {filename}" os.system(cmd)
do_o = do_open
[docs]def pm(): """use our custom debugger""" dbg = Debugger(sys.last_traceback) dbg.start()
[docs]def set_trace(): Debugger().set_trace(sys._getframe().f_back)