diff unixSoft/lib/python/pdb.py @ 0:c30d68fbd368

Initial import from svn.
author Augie Fackler <durin42@gmail.com>
date Wed, 26 Nov 2008 10:56:09 -0600
parents
children
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/unixSoft/lib/python/pdb.py
@@ -0,0 +1,413 @@
+"""
+Pdb++, a fancier version of pdb
+===============================
+
+This module extends the stdlib pdb in numerous ways, e.g. by providing
+real completion of Python values instead of pdb's own commands, or by
+adding few convenience commands like ``longlist``, ``interact`` or
+``watch``.
+
+For a full explanation of each command, refer to the docstring or type
+help <command> at the prompt.
+
+Installation
+------------
+
+This module is meant to replace stdlib's pdb.py from the outsite;
+simply put it in a directory in your PYTHONPATH, and you can start
+using it immediately.  Since it's named pdb.py, every place which
+imports pdb will now find the new module.
+
+Dependencies
+------------
+
+To work properly, this module needs `rlcompleter_ng`_ to be installed.
+
+To enable syntax highlighting, you must install `pygments`.
+
+.. _pygments: http://pygments.org/
+.. _`rlcompleter_ng`: http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ng.py
+
+Configuration
+-------------
+
+To customize the configuration of Pdb++, you need to put a file named
+.pdbrc.py in your home directory.  The file must contain a class named
+``Config`` inheriting from ``DefaultConfig`` and overridding the
+desired values.
+
+To know which options are available, look at the comment in the
+source.
+
+You can find a sample configuration file, here:
+http://codespeak.net/svn/user/antocuni/hack/pdbrc.py
+"""
+
+__version__='0.1'
+__author__ ='Antonio Cuni <anto.cuni@gmail.com>'
+__url__='http://codespeak.net/svn/user/antocuni/hack/pdb.py'
+
+import sys
+import os.path
+import inspect
+import code
+import types
+from rlcompleter_ng import Completer, ConfigurableClass, setcolor, colors
+
+
+def import_from_stdlib(name):
+    import code # arbitrary module which stays in the same dir as pdb
+    stdlibdir, _ = os.path.split(code.__file__)
+    pyfile = os.path.join(stdlibdir, name + '.py')
+    result = types.ModuleType(name)
+    mydict = execfile(pyfile, result.__dict__)
+    return result
+
+pdb = import_from_stdlib('pdb')
+
+
+class DefaultConfig:
+    prompt = '(Pdb++) '
+    completekey = 'tab'
+    highlight = True
+    bg = 'dark'
+    colorscheme = None
+
+    line_number_color = colors.turquoise
+    current_line_color = 44 # blue
+
+def getsourcelines(obj):
+    try:
+        return inspect.getsourcelines(obj)
+    except IOError:
+        pass
+
+    if isinstance(obj, types.FrameType):
+        filename = obj.f_code.co_filename
+        if hasattr(filename, '__source__'):
+            first = max(1, obj.f_lineno - 5)
+            lines = [line + '\n' for line in filename.__source__.lines]
+            return lines, first
+    raise IOError('could not get source code')
+
+def setbgcolor(line, color):
+    # hack hack hack
+    # add a bgcolor attribute to all escape sequences found
+    import re
+    setbg = '\x1b[%dm' % color
+    regexbg = '\\1;%dm' % color
+    return setbg + re.sub('(\x1b\\[.*?)m', regexbg, line) + '\x1b[00m'
+
+CLEARSCREEN = '\033[2J\033[1;1H'
+
+def lasti2lineno(code, lasti):
+    import dis
+    linestarts = list(dis.findlinestarts(code))
+    linestarts.reverse()
+    for i, lineno in linestarts:
+        if lasti >= i:
+            return lineno
+    assert False, 'Invalid instruction number: %s' % lasti
+
+class Undefined:
+    def __repr__(self):
+        return '<undefined>'
+undefined = Undefined()
+
+class Pdb(pdb.Pdb, ConfigurableClass):
+
+    DefaultConfig = DefaultConfig
+    config_filename = '.pdbrc.py'
+
+    def __init__(self, *args, **kwds):
+        Config = kwds.pop('Config', None)
+        pdb.Pdb.__init__(self, *args, **kwds)
+        self.config = self.get_config(Config)
+        self.prompt = self.config.prompt
+        self.completekey = self.config.completekey
+
+        self.mycompleter = None
+        self.watching = {} # frame --> (name --> last seen value)
+        self.sticky = False
+        self.sticky_ranges = {} # frame --> (start, end)
+        self.tb_lineno = {} # frame --> lineno where the exception raised
+
+    def interaction(self, frame, traceback, orig_traceback=None):
+        self.setup(frame, traceback, orig_traceback)
+        self.print_stack_entry(self.stack[self.curindex])
+        self.cmdloop()
+        self.forget()
+
+    def setup(self, frame, tb, orig_tb=None):
+        pdb.Pdb.setup(self, frame, tb)
+        tb = orig_tb
+        while tb:
+            lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
+            self.tb_lineno[tb.tb_frame] = lineno
+            tb = tb.tb_next
+
+    def forget(self):
+        pdb.Pdb.forget(self)
+        self.raise_lineno = {}
+
+    def complete(self, text, state):
+        if state == 0:
+            mydict = self.curframe.f_globals.copy()
+            mydict.update(self.curframe.f_locals)
+            self.mycompleter = Completer(mydict)
+        return self.mycompleter.complete(text, state)
+
+    def _init_pygments(self): 
+        try:
+            from pygments.lexers import PythonLexer
+            from pygments.formatters import TerminalFormatter
+        except ImportError:
+            return False
+
+        if hasattr(self, '_fmt'):
+            return True
+        
+        self._fmt = TerminalFormatter(bg=self.config.bg,
+                                      colorscheme=self.config.colorscheme)
+        self._lexer = PythonLexer()
+        return True
+
+    def format_source(self, src):
+        if not self._init_pygments():
+            return src
+        from pygments import highlight, lex
+        return highlight(src, self._lexer, self._fmt)
+
+    def format_line(self, lineno, marker, line):
+        lineno = '%4d' % lineno
+        if self.config.highlight:
+            lineno = setcolor(lineno, self.config.line_number_color)
+        line = '%s  %2s %s' % (lineno, marker, line)
+        if self.config.highlight and marker == '->':
+            line = setbgcolor(line, self.config.current_line_color)
+        return line
+
+    def parseline(self, line):
+        cmd, arg, newline = pdb.Pdb.parseline(self, line)
+        # don't execute short disruptive commands if a variable with
+        # the name exits in the current contex; this prevents pdb to
+        # quit if you type e.g. 'r[0]' by mystake.
+        if cmd in ['c', 'r', 'q'] and (cmd in self.curframe.f_globals or
+                                       cmd in self.curframe.f_locals):
+            line = '!' + line
+            return pdb.Pdb.parseline(self, line)
+        return cmd, arg, newline
+
+    def do_longlist(self, arg):
+        """
+        {longlist|ll}
+        List source code for the current function.
+        
+        Differently that list, the whole function is displayed; the
+        current line is marked with '->'.  In case of post-mortem
+        debugging, the line which effectively raised the exception is
+        marked with '>>'.
+
+        If the 'highlight' config option is set and pygments is
+        installed, the source code is colorized.
+        """
+        self.lastcmd = 'longlist'
+        self._printlonglist()
+
+    def _printlonglist(self, linerange=None):
+        try:
+            lines, lineno = getsourcelines(self.curframe)
+        except IOError, e:
+            print '** Error: %s **' % e
+            return
+        if linerange:
+            start, end = linerange
+            start = max(start, lineno)
+            end = min(end, lineno+len(lines))
+            lines = lines[start-lineno:end-lineno]
+            lineno = start
+        self._print_lines(lines, lineno)
+
+    def _print_lines(self, lines, lineno, print_markers=True):
+        exc_lineno = self.tb_lineno.get(self.curframe, None)
+        lines = [line[:-1] for line in lines] # remove the trailing '\n'
+        if self.config.highlight:
+            maxlength = max(map(len, lines))
+            lines = [line.ljust(maxlength) for line in lines]
+            src = self.format_source('\n'.join(lines))
+            lines = src.splitlines()
+        for i, line in enumerate(lines):
+            marker = ''
+            if lineno == self.curframe.f_lineno and print_markers:
+                marker = '->'
+            elif lineno == exc_lineno and print_markers:
+                marker = '>>'
+            lines[i] = self.format_line(lineno, marker, line)
+            lineno += 1
+        print '\n'.join(lines)
+
+    do_ll = do_longlist
+
+    def do_list(self, arg):
+        from StringIO import StringIO
+        oldstdout = sys.stdout
+        sys.stdout = StringIO()
+        pdb.Pdb.do_list(self, arg)
+        src = self.format_source(sys.stdout.getvalue())
+        sys.stdout = oldstdout
+        print src,
+
+    do_l = do_list
+
+    def do_interact(self, arg):
+        """
+        interact
+
+        Start an interative interpreter whose global namespace
+        contains all the names found in the current scope.
+        """
+        ns = self.curframe.f_globals.copy()
+        ns.update(self.curframe.f_locals)
+        code.interact("*interactive*", local=ns)
+
+    def do_track(self, arg):
+        """
+        track expression
+
+        Display a graph showing which objects are referred by the
+        value of the expression.  This command requires pypy to be in
+        the current PYTHONPATH.
+        """
+        try:
+            from pypy.translator.tool.reftracker import track
+        except ImportError:
+            print '** cannot import pypy.translator.tool.reftracker **'
+            return
+        val = self._getval(arg)
+        track(val)
+
+    def _get_watching(self):
+        return self.watching.setdefault(self.curframe, {})
+
+    def _getval_or_undefined(self, arg):
+        try:
+            return eval(arg, self.curframe.f_globals,
+                        self.curframe.f_locals)
+        except NameError:
+            return undefined
+
+    def do_watch(self, arg):
+        """
+        watch expression
+
+        Add expression to the watching list; expressions in this list
+        are evaluated at each step, and printed every time its value
+        changes.
+
+        WARNING: since the expressions is evaluated multiple time, pay
+        attention not to put expressions with side-effects in the
+        watching list.
+        """
+        try:
+            value = self._getval_or_undefined(arg)
+        except:
+            return
+        self._get_watching()[arg] = value
+
+    def do_unwatch(self, arg):
+        """
+        unwatch expression
+
+        Remove expression from the watching list.
+        """
+        try:
+            del self._get_watching()[arg]
+        except KeyError:
+            print '** not watching %s **' % arg
+
+    def _print_if_sticky(self):
+        if self.sticky:
+            sys.stdout.write(CLEARSCREEN)
+            frame, lineno = self.stack[self.curindex]
+            filename = self.canonic(frame.f_code.co_filename)
+            s = '> %s(%r)' % (filename, lineno)
+            print s
+            print
+            sticky_range = self.sticky_ranges.get(self.curframe, None)
+            self._printlonglist(sticky_range)
+
+    def do_sticky(self, arg):
+        """
+        sticky [start end]
+
+        Toggle sticky mode. When in sticky mode, it clear the screen
+        and longlist the current functions, making the source
+        appearing always in the same position. Useful to follow the
+        flow control of a function when doing step-by-step execution.
+
+        If ``start`` and ``end`` are given, sticky mode is enabled and
+        only lines within that range (extremes included) will be
+        displayed.
+        """
+        if arg:
+            try:
+                start, end = map(int, arg.split())
+            except ValueError:
+                print '** Error when parsing argument: %s **' % arg
+                return
+            self.sticky = True
+            self.sticky_ranges[self.curframe] = start, end+1
+        else:
+            self.sticky = not self.sticky
+            self.sticky_range = None
+        self._print_if_sticky()
+
+    def preloop(self):
+        self._print_if_sticky()
+        watching = self._get_watching()
+        for expr, oldvalue in watching.iteritems():
+            newvalue = self._getval_or_undefined(expr)
+            # check for identity first; this prevents custom __eq__ to
+            # be called at every loop, and also prevents instances
+            # whose fields are changed to be displayed
+            if newvalue is not oldvalue or newvalue != oldvalue:
+                watching[expr] = newvalue
+                print '%s: %r --> %r' % (expr, oldvalue, newvalue)
+
+    def do_source(self, arg):
+        try:
+            obj = self._getval(arg)
+        except:
+            return
+        try:
+            lines, lineno = getsourcelines(obj)
+        except (IOError, TypeError), e:
+            print '** Error: %s **' % e
+            return
+        self._print_lines(lines, lineno, print_markers=False)
+
+# Simplified interface
+
+# copy some functions from pdb.py, but rebind the global dictionary
+for name in 'run runeval runctx runcall set_trace pm'.split():
+    func = getattr(pdb, name)
+    newfunc = types.FunctionType(func.func_code, globals(), func.func_name)
+    globals()[name] = newfunc
+del name, func, newfunc
+
+def post_mortem(t, Pdb=Pdb):
+    p = Pdb()
+    p.reset()
+    orig_tb = t
+    while t.tb_next is not None:
+        t = t.tb_next
+    p.interaction(t.tb_frame, t, orig_tb)
+
+# pdb++ specific interface
+
+def xpm(Pdb=Pdb):
+    """
+    To be used inside an except clause, enter a post-mortem pdb
+    related to the just catched exception.
+    """
+    post_mortem(sys.exc_info()[2], Pdb)