Package logilab :: Package common :: Module debugger
[frames] | no frames]

Source Code for Module logilab.common.debugger

  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 under 
  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) any 
  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   
 30  from __future__ import print_function 
 31   
 32  __docformat__ = "restructuredtext en" 
 33   
 34  try: 
 35      import readline 
 36  except ImportError: 
 37      readline = None 
 38  import os 
 39  import os.path as osp 
 40  import sys 
 41  from pdb import Pdb 
 42  import inspect 
 43   
 44  from logilab.common.compat import StringIO 
 45   
 46  try: 
 47      from IPython import PyColorize 
 48  except ImportError: 
49 - def colorize(source, *args):
50 """fallback colorize function""" 51 return source
52 - def colorize_source(source, *args):
53 return source
54 else:
55 - def colorize(source, start_lineno, curlineno):
56 """colorize and annotate source with linenos 57 (as in pdb's list command) 58 """ 59 parser = PyColorize.Parser() 60 output = StringIO() 61 parser.format(source, output) 62 annotated = [] 63 for index, line in enumerate(output.getvalue().splitlines()): 64 lineno = index + start_lineno 65 if lineno == curlineno: 66 annotated.append('%4s\t->\t%s' % (lineno, line)) 67 else: 68 annotated.append('%4s\t\t%s' % (lineno, line)) 69 return '\n'.join(annotated)
70
71 - def colorize_source(source):
72 """colorize given source""" 73 parser = PyColorize.Parser() 74 output = StringIO() 75 parser.format(source, output) 76 return output.getvalue()
77 78
79 -def getsource(obj):
80 """Return the text of the source code for an object. 81 82 The argument may be a module, class, method, function, traceback, frame, 83 or code object. The source code is returned as a single string. An 84 IOError is raised if the source code cannot be retrieved.""" 85 lines, lnum = inspect.getsourcelines(obj) 86 return ''.join(lines), lnum
87 88 89 ################################################################
90 -class Debugger(Pdb):
91 """custom debugger 92 93 - sets up a history file 94 - uses ipython if available to colorize lines of code 95 - overrides list command to search for current block instead 96 of using 5 lines of context 97 """
98 - def __init__(self, tcbk=None):
99 Pdb.__init__(self) 100 self.reset() 101 if tcbk: 102 while tcbk.tb_next is not None: 103 tcbk = tcbk.tb_next 104 self._tcbk = tcbk 105 self._histfile = os.path.expanduser("~/.pdbhist")
106
107 - def setup_history_file(self):
108 """if readline is available, read pdb history file 109 """ 110 if readline is not None: 111 try: 112 # XXX try..except shouldn't be necessary 113 # read_history_file() can accept None 114 readline.read_history_file(self._histfile) 115 except IOError: 116 pass
117
118 - def start(self):
119 """starts the interactive mode""" 120 self.interaction(self._tcbk.tb_frame, self._tcbk)
121
122 - def setup(self, frame, tcbk):
123 """setup hook: set up history file""" 124 self.setup_history_file() 125 Pdb.setup(self, frame, tcbk)
126
127 - def set_quit(self):
128 """quit hook: save commands in the history file""" 129 if readline is not None: 130 readline.write_history_file(self._histfile) 131 Pdb.set_quit(self)
132
133 - def complete_p(self, text, line, begin_idx, end_idx):
134 """provide variable names completion for the ``p`` command""" 135 namespace = dict(self.curframe.f_globals) 136 namespace.update(self.curframe.f_locals) 137 if '.' in text: 138 return self.attr_matches(text, namespace) 139 return [varname for varname in namespace if varname.startswith(text)]
140 141
142 - def attr_matches(self, text, namespace):
143 """implementation coming from rlcompleter.Completer.attr_matches 144 Compute matches when text contains a dot. 145 146 Assuming the text is of the form NAME.NAME....[NAME], and is 147 evaluatable in self.namespace, it will be evaluated and its attributes 148 (as revealed by dir()) are used as possible completions. (For class 149 instances, class members are also considered.) 150 151 WARNING: this can still invoke arbitrary C code, if an object 152 with a __getattr__ hook is evaluated. 153 154 """ 155 import re 156 m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) 157 if not m: 158 return 159 expr, attr = m.group(1, 3) 160 object = eval(expr, namespace) 161 words = dir(object) 162 if hasattr(object, '__class__'): 163 words.append('__class__') 164 words = words + self.get_class_members(object.__class__) 165 matches = [] 166 n = len(attr) 167 for word in words: 168 if word[:n] == attr and word != "__builtins__": 169 matches.append("%s.%s" % (expr, word)) 170 return matches
171
172 - def get_class_members(self, klass):
173 """implementation coming from rlcompleter.get_class_members""" 174 ret = dir(klass) 175 if hasattr(klass, '__bases__'): 176 for base in klass.__bases__: 177 ret = ret + self.get_class_members(base) 178 return ret
179 180 ## specific / overridden commands
181 - def do_list(self, arg):
182 """overrides default list command to display the surrounding block 183 instead of 5 lines of context 184 """ 185 self.lastcmd = 'list' 186 if not arg: 187 try: 188 source, start_lineno = getsource(self.curframe) 189 print(colorize(''.join(source), start_lineno, 190 self.curframe.f_lineno)) 191 except KeyboardInterrupt: 192 pass 193 except IOError: 194 Pdb.do_list(self, arg) 195 else: 196 Pdb.do_list(self, arg)
197 do_l = do_list 198
199 - def do_open(self, arg):
200 """opens source file corresponding to the current stack level""" 201 filename = self.curframe.f_code.co_filename 202 lineno = self.curframe.f_lineno 203 cmd = 'emacsclient --no-wait +%s %s' % (lineno, filename) 204 os.system(cmd)
205 206 do_o = do_open
207
208 -def pm():
209 """use our custom debugger""" 210 dbg = Debugger(sys.last_traceback) 211 dbg.start()
212
213 -def set_trace():
214 Debugger().set_trace(sys._getframe().f_back)
215