| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 
|  | 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | 
|  | 3 # | 
|  | 4 # This file is part of logilab-common. | 
|  | 5 # | 
|  | 6 # logilab-common is free software: you can redistribute it and/or modify it unde
     r | 
|  | 7 # the terms of the GNU Lesser General Public License as published by the Free | 
|  | 8 # Software Foundation, either version 2.1 of the License, or (at your option) an
     y | 
|  | 9 # later version. | 
|  | 10 # | 
|  | 11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT | 
|  | 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|  | 13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more | 
|  | 14 # details. | 
|  | 15 # | 
|  | 16 # You should have received a copy of the GNU Lesser General Public License along | 
|  | 17 # with logilab-common.  If not, see <http://www.gnu.org/licenses/>. | 
|  | 18 """Customized version of pdb's default debugger. | 
|  | 19 | 
|  | 20 - sets up a history file | 
|  | 21 - uses ipython if available to colorize lines of code | 
|  | 22 - overrides list command to search for current block instead | 
|  | 23   of using 5 lines of context | 
|  | 24 | 
|  | 25 | 
|  | 26 | 
|  | 27 | 
|  | 28 """ | 
|  | 29 __docformat__ = "restructuredtext en" | 
|  | 30 | 
|  | 31 try: | 
|  | 32     import readline | 
|  | 33 except ImportError: | 
|  | 34     readline = None | 
|  | 35 import os | 
|  | 36 import os.path as osp | 
|  | 37 import sys | 
|  | 38 from pdb import Pdb | 
|  | 39 from cStringIO import StringIO | 
|  | 40 import inspect | 
|  | 41 | 
|  | 42 try: | 
|  | 43     from IPython import PyColorize | 
|  | 44 except ImportError: | 
|  | 45     def colorize(source, *args): | 
|  | 46         """fallback colorize function""" | 
|  | 47         return source | 
|  | 48     def colorize_source(source, *args): | 
|  | 49         return source | 
|  | 50 else: | 
|  | 51     def colorize(source, start_lineno, curlineno): | 
|  | 52         """colorize and annotate source with linenos | 
|  | 53         (as in pdb's list command) | 
|  | 54         """ | 
|  | 55         parser = PyColorize.Parser() | 
|  | 56         output = StringIO() | 
|  | 57         parser.format(source, output) | 
|  | 58         annotated = [] | 
|  | 59         for index, line in enumerate(output.getvalue().splitlines()): | 
|  | 60             lineno = index + start_lineno | 
|  | 61             if lineno == curlineno: | 
|  | 62                 annotated.append('%4s\t->\t%s' % (lineno, line)) | 
|  | 63             else: | 
|  | 64                 annotated.append('%4s\t\t%s' % (lineno, line)) | 
|  | 65         return '\n'.join(annotated) | 
|  | 66 | 
|  | 67     def colorize_source(source): | 
|  | 68         """colorize given source""" | 
|  | 69         parser = PyColorize.Parser() | 
|  | 70         output = StringIO() | 
|  | 71         parser.format(source, output) | 
|  | 72         return output.getvalue() | 
|  | 73 | 
|  | 74 | 
|  | 75 def getsource(obj): | 
|  | 76     """Return the text of the source code for an object. | 
|  | 77 | 
|  | 78     The argument may be a module, class, method, function, traceback, frame, | 
|  | 79     or code object.  The source code is returned as a single string.  An | 
|  | 80     IOError is raised if the source code cannot be retrieved.""" | 
|  | 81     lines, lnum = inspect.getsourcelines(obj) | 
|  | 82     return ''.join(lines), lnum | 
|  | 83 | 
|  | 84 | 
|  | 85 ################################################################ | 
|  | 86 class Debugger(Pdb): | 
|  | 87     """custom debugger | 
|  | 88 | 
|  | 89     - sets up a history file | 
|  | 90     - uses ipython if available to colorize lines of code | 
|  | 91     - overrides list command to search for current block instead | 
|  | 92       of using 5 lines of context | 
|  | 93     """ | 
|  | 94     def __init__(self, tcbk=None): | 
|  | 95         Pdb.__init__(self) | 
|  | 96         self.reset() | 
|  | 97         if tcbk: | 
|  | 98             while tcbk.tb_next is not None: | 
|  | 99                 tcbk = tcbk.tb_next | 
|  | 100         self._tcbk = tcbk | 
|  | 101         self._histfile = os.path.expanduser("~/.pdbhist") | 
|  | 102 | 
|  | 103     def setup_history_file(self): | 
|  | 104         """if readline is available, read pdb history file | 
|  | 105         """ | 
|  | 106         if readline is not None: | 
|  | 107             try: | 
|  | 108                 # XXX try..except shouldn't be necessary | 
|  | 109                 # read_history_file() can accept None | 
|  | 110                 readline.read_history_file(self._histfile) | 
|  | 111             except IOError: | 
|  | 112                 pass | 
|  | 113 | 
|  | 114     def start(self): | 
|  | 115         """starts the interactive mode""" | 
|  | 116         self.interaction(self._tcbk.tb_frame, self._tcbk) | 
|  | 117 | 
|  | 118     def setup(self, frame, tcbk): | 
|  | 119         """setup hook: set up history file""" | 
|  | 120         self.setup_history_file() | 
|  | 121         Pdb.setup(self, frame, tcbk) | 
|  | 122 | 
|  | 123     def set_quit(self): | 
|  | 124         """quit hook: save commands in the history file""" | 
|  | 125         if readline is not None: | 
|  | 126             readline.write_history_file(self._histfile) | 
|  | 127         Pdb.set_quit(self) | 
|  | 128 | 
|  | 129     def complete_p(self, text, line, begin_idx, end_idx): | 
|  | 130         """provide variable names completion for the ``p`` command""" | 
|  | 131         namespace = dict(self.curframe.f_globals) | 
|  | 132         namespace.update(self.curframe.f_locals) | 
|  | 133         if '.' in text: | 
|  | 134             return self.attr_matches(text, namespace) | 
|  | 135         return [varname for varname in namespace if varname.startswith(text)] | 
|  | 136 | 
|  | 137 | 
|  | 138     def attr_matches(self, text, namespace): | 
|  | 139         """implementation coming from rlcompleter.Completer.attr_matches | 
|  | 140         Compute matches when text contains a dot. | 
|  | 141 | 
|  | 142         Assuming the text is of the form NAME.NAME....[NAME], and is | 
|  | 143         evaluatable in self.namespace, it will be evaluated and its attributes | 
|  | 144         (as revealed by dir()) are used as possible completions.  (For class | 
|  | 145         instances, class members are also considered.) | 
|  | 146 | 
|  | 147         WARNING: this can still invoke arbitrary C code, if an object | 
|  | 148         with a __getattr__ hook is evaluated. | 
|  | 149 | 
|  | 150         """ | 
|  | 151         import re | 
|  | 152         m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) | 
|  | 153         if not m: | 
|  | 154             return | 
|  | 155         expr, attr = m.group(1, 3) | 
|  | 156         object = eval(expr, namespace) | 
|  | 157         words = dir(object) | 
|  | 158         if hasattr(object, '__class__'): | 
|  | 159             words.append('__class__') | 
|  | 160             words = words + self.get_class_members(object.__class__) | 
|  | 161         matches = [] | 
|  | 162         n = len(attr) | 
|  | 163         for word in words: | 
|  | 164             if word[:n] == attr and word != "__builtins__": | 
|  | 165                 matches.append("%s.%s" % (expr, word)) | 
|  | 166         return matches | 
|  | 167 | 
|  | 168     def get_class_members(self, klass): | 
|  | 169         """implementation coming from rlcompleter.get_class_members""" | 
|  | 170         ret = dir(klass) | 
|  | 171         if hasattr(klass, '__bases__'): | 
|  | 172             for base in klass.__bases__: | 
|  | 173                 ret = ret + self.get_class_members(base) | 
|  | 174         return ret | 
|  | 175 | 
|  | 176     ## specific / overridden commands | 
|  | 177     def do_list(self, arg): | 
|  | 178         """overrides default list command to display the surrounding block | 
|  | 179         instead of 5 lines of context | 
|  | 180         """ | 
|  | 181         self.lastcmd = 'list' | 
|  | 182         if not arg: | 
|  | 183             try: | 
|  | 184                 source, start_lineno = getsource(self.curframe) | 
|  | 185                 print colorize(''.join(source), start_lineno, | 
|  | 186                                self.curframe.f_lineno) | 
|  | 187             except KeyboardInterrupt: | 
|  | 188                 pass | 
|  | 189             except IOError: | 
|  | 190                 Pdb.do_list(self, arg) | 
|  | 191         else: | 
|  | 192             Pdb.do_list(self, arg) | 
|  | 193     do_l = do_list | 
|  | 194 | 
|  | 195     def do_open(self, arg): | 
|  | 196         """opens source file corresponding to the current stack level""" | 
|  | 197         filename = self.curframe.f_code.co_filename | 
|  | 198         lineno = self.curframe.f_lineno | 
|  | 199         cmd = 'emacsclient --no-wait +%s %s' % (lineno, filename) | 
|  | 200         os.system(cmd) | 
|  | 201 | 
|  | 202     do_o = do_open | 
|  | 203 | 
|  | 204 def pm(): | 
|  | 205     """use our custom debugger""" | 
|  | 206     dbg = Debugger(sys.last_traceback) | 
|  | 207     dbg.start() | 
|  | 208 | 
|  | 209 def set_trace(): | 
|  | 210     Debugger().set_trace(sys._getframe().f_back) | 
| OLD | NEW | 
|---|