You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
6.9 KiB
Python
211 lines
6.9 KiB
Python
# 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)
|