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