debugger.py 6.9 KB

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