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