0
+ − 1 """
+ − 2 Pdb++, a fancier version of pdb
+ − 3 ===============================
+ − 4
+ − 5 This module extends the stdlib pdb in numerous ways, e.g. by providing
+ − 6 real completion of Python values instead of pdb's own commands, or by
+ − 7 adding few convenience commands like ``longlist``, ``interact`` or
+ − 8 ``watch``.
+ − 9
+ − 10 For a full explanation of each command, refer to the docstring or type
+ − 11 help <command> at the prompt.
+ − 12
+ − 13 Installation
+ − 14 ------------
+ − 15
+ − 16 This module is meant to replace stdlib's pdb.py from the outsite;
+ − 17 simply put it in a directory in your PYTHONPATH, and you can start
+ − 18 using it immediately. Since it's named pdb.py, every place which
+ − 19 imports pdb will now find the new module.
+ − 20
+ − 21 Dependencies
+ − 22 ------------
+ − 23
+ − 24 To work properly, this module needs `rlcompleter_ng`_ to be installed.
+ − 25
+ − 26 To enable syntax highlighting, you must install `pygments`.
+ − 27
+ − 28 .. _pygments: http://pygments.org/
+ − 29 .. _`rlcompleter_ng`: http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ng.py
+ − 30
+ − 31 Configuration
+ − 32 -------------
+ − 33
+ − 34 To customize the configuration of Pdb++, you need to put a file named
+ − 35 .pdbrc.py in your home directory. The file must contain a class named
+ − 36 ``Config`` inheriting from ``DefaultConfig`` and overridding the
+ − 37 desired values.
+ − 38
+ − 39 To know which options are available, look at the comment in the
+ − 40 source.
+ − 41
+ − 42 You can find a sample configuration file, here:
+ − 43 http://codespeak.net/svn/user/antocuni/hack/pdbrc.py
+ − 44 """
+ − 45
+ − 46 __version__ = '0.1'
+ − 47 __author__ = 'Antonio Cuni <anto.cuni@gmail.com>'
+ − 48 __url__ = 'http://codespeak.net/svn/user/antocuni/hack/pdb.py'
+ − 49
+ − 50 import sys
+ − 51 import os.path
+ − 52 import inspect
+ − 53 import code
+ − 54 import types
+ − 55 from rlcompleter_ng import Completer , ConfigurableClass , setcolor , colors
+ − 56
+ − 57
+ − 58 def import_from_stdlib ( name ):
+ − 59 import code # arbitrary module which stays in the same dir as pdb
+ − 60 stdlibdir , _ = os . path . split ( code . __file__ )
+ − 61 pyfile = os . path . join ( stdlibdir , name + '.py' )
+ − 62 result = types . ModuleType ( name )
+ − 63 mydict = execfile ( pyfile , result . __dict__ )
+ − 64 return result
+ − 65
+ − 66 pdb = import_from_stdlib ( 'pdb' )
+ − 67
+ − 68
+ − 69 class DefaultConfig :
+ − 70 prompt = '(Pdb++) '
+ − 71 completekey = 'tab'
+ − 72 highlight = True
+ − 73 bg = 'dark'
+ − 74 colorscheme = None
+ − 75
+ − 76 line_number_color = colors . turquoise
+ − 77 current_line_color = 44 # blue
+ − 78
+ − 79 def getsourcelines ( obj ):
+ − 80 try :
+ − 81 return inspect . getsourcelines ( obj )
+ − 82 except IOError :
+ − 83 pass
+ − 84
+ − 85 if isinstance ( obj , types . FrameType ):
+ − 86 filename = obj . f_code . co_filename
+ − 87 if hasattr ( filename , '__source__' ):
+ − 88 first = max ( 1 , obj . f_lineno - 5 )
+ − 89 lines = [ line + ' \n ' for line in filename . __source__ . lines ]
+ − 90 return lines , first
+ − 91 raise IOError ( 'could not get source code' )
+ − 92
+ − 93 def setbgcolor ( line , color ):
+ − 94 # hack hack hack
+ − 95 # add a bgcolor attribute to all escape sequences found
+ − 96 import re
+ − 97 setbg = ' \x1b [ %d m' % color
+ − 98 regexbg = ' \\ 1; %d m' % color
+ − 99 return setbg + re . sub ( '( \x1b\\ [.*?)m' , regexbg , line ) + ' \x1b [00m'
+ − 100
+ − 101 CLEARSCREEN = ' \033 [2J \033 [1;1H'
+ − 102
+ − 103 def lasti2lineno ( code , lasti ):
+ − 104 import dis
+ − 105 linestarts = list ( dis . findlinestarts ( code ))
+ − 106 linestarts . reverse ()
+ − 107 for i , lineno in linestarts :
+ − 108 if lasti >= i :
+ − 109 return lineno
+ − 110 assert False , 'Invalid instruction number: %s ' % lasti
+ − 111
+ − 112 class Undefined :
+ − 113 def __repr__ ( self ):
+ − 114 return '<undefined>'
+ − 115 undefined = Undefined ()
+ − 116
+ − 117 class Pdb ( pdb . Pdb , ConfigurableClass ):
+ − 118
+ − 119 DefaultConfig = DefaultConfig
+ − 120 config_filename = '.pdbrc.py'
+ − 121
+ − 122 def __init__ ( self , * args , ** kwds ):
+ − 123 Config = kwds . pop ( 'Config' , None )
+ − 124 pdb . Pdb . __init__ ( self , * args , ** kwds )
+ − 125 self . config = self . get_config ( Config )
+ − 126 self . prompt = self . config . prompt
+ − 127 self . completekey = self . config . completekey
+ − 128
+ − 129 self . mycompleter = None
+ − 130 self . watching = {} # frame --> (name --> last seen value)
+ − 131 self . sticky = False
+ − 132 self . sticky_ranges = {} # frame --> (start, end)
+ − 133 self . tb_lineno = {} # frame --> lineno where the exception raised
+ − 134
+ − 135 def interaction ( self , frame , traceback , orig_traceback = None ):
+ − 136 self . setup ( frame , traceback , orig_traceback )
+ − 137 self . print_stack_entry ( self . stack [ self . curindex ])
+ − 138 self . cmdloop ()
+ − 139 self . forget ()
+ − 140
+ − 141 def setup ( self , frame , tb , orig_tb = None ):
+ − 142 pdb . Pdb . setup ( self , frame , tb )
+ − 143 tb = orig_tb
+ − 144 while tb :
+ − 145 lineno = lasti2lineno ( tb . tb_frame . f_code , tb . tb_lasti )
+ − 146 self . tb_lineno [ tb . tb_frame ] = lineno
+ − 147 tb = tb . tb_next
+ − 148
+ − 149 def forget ( self ):
+ − 150 pdb . Pdb . forget ( self )
+ − 151 self . raise_lineno = {}
+ − 152
+ − 153 def complete ( self , text , state ):
+ − 154 if state == 0 :
+ − 155 mydict = self . curframe . f_globals . copy ()
+ − 156 mydict . update ( self . curframe . f_locals )
+ − 157 self . mycompleter = Completer ( mydict )
+ − 158 return self . mycompleter . complete ( text , state )
+ − 159
+ − 160 def _init_pygments ( self ):
+ − 161 try :
+ − 162 from pygments.lexers import PythonLexer
+ − 163 from pygments.formatters import TerminalFormatter
+ − 164 except ImportError :
+ − 165 return False
+ − 166
+ − 167 if hasattr ( self , '_fmt' ):
+ − 168 return True
+ − 169
+ − 170 self . _fmt = TerminalFormatter ( bg = self . config . bg ,
+ − 171 colorscheme = self . config . colorscheme )
+ − 172 self . _lexer = PythonLexer ()
+ − 173 return True
+ − 174
+ − 175 def format_source ( self , src ):
+ − 176 if not self . _init_pygments ():
+ − 177 return src
+ − 178 from pygments import highlight , lex
+ − 179 return highlight ( src , self . _lexer , self . _fmt )
+ − 180
+ − 181 def format_line ( self , lineno , marker , line ):
+ − 182 lineno = ' %4d ' % lineno
+ − 183 if self . config . highlight :
+ − 184 lineno = setcolor ( lineno , self . config . line_number_color )
+ − 185 line = ' %s %2s %s ' % ( lineno , marker , line )
+ − 186 if self . config . highlight and marker == '->' :
+ − 187 line = setbgcolor ( line , self . config . current_line_color )
+ − 188 return line
+ − 189
+ − 190 def parseline ( self , line ):
+ − 191 cmd , arg , newline = pdb . Pdb . parseline ( self , line )
+ − 192 # don't execute short disruptive commands if a variable with
+ − 193 # the name exits in the current contex; this prevents pdb to
+ − 194 # quit if you type e.g. 'r[0]' by mystake.
+ − 195 if cmd in [ 'c' , 'r' , 'q' ] and ( cmd in self . curframe . f_globals or
+ − 196 cmd in self . curframe . f_locals ):
+ − 197 line = '!' + line
+ − 198 return pdb . Pdb . parseline ( self , line )
+ − 199 return cmd , arg , newline
+ − 200
+ − 201 def do_longlist ( self , arg ):
+ − 202 """
+ − 203 {longlist|ll}
+ − 204 List source code for the current function.
+ − 205
+ − 206 Differently that list, the whole function is displayed; the
+ − 207 current line is marked with '->'. In case of post-mortem
+ − 208 debugging, the line which effectively raised the exception is
+ − 209 marked with '>>'.
+ − 210
+ − 211 If the 'highlight' config option is set and pygments is
+ − 212 installed, the source code is colorized.
+ − 213 """
+ − 214 self . lastcmd = 'longlist'
+ − 215 self . _printlonglist ()
+ − 216
+ − 217 def _printlonglist ( self , linerange = None ):
+ − 218 try :
+ − 219 lines , lineno = getsourcelines ( self . curframe )
+ − 220 except IOError , e :
+ − 221 print '** Error: %s **' % e
+ − 222 return
+ − 223 if linerange :
+ − 224 start , end = linerange
+ − 225 start = max ( start , lineno )
+ − 226 end = min ( end , lineno + len ( lines ))
+ − 227 lines = lines [ start - lineno : end - lineno ]
+ − 228 lineno = start
+ − 229 self . _print_lines ( lines , lineno )
+ − 230
+ − 231 def _print_lines ( self , lines , lineno , print_markers = True ):
+ − 232 exc_lineno = self . tb_lineno . get ( self . curframe , None )
+ − 233 lines = [ line [: - 1 ] for line in lines ] # remove the trailing '\n'
+ − 234 if self . config . highlight :
+ − 235 maxlength = max ( map ( len , lines ))
+ − 236 lines = [ line . ljust ( maxlength ) for line in lines ]
+ − 237 src = self . format_source ( ' \n ' . join ( lines ))
+ − 238 lines = src . splitlines ()
+ − 239 for i , line in enumerate ( lines ):
+ − 240 marker = ''
+ − 241 if lineno == self . curframe . f_lineno and print_markers :
+ − 242 marker = '->'
+ − 243 elif lineno == exc_lineno and print_markers :
+ − 244 marker = '>>'
+ − 245 lines [ i ] = self . format_line ( lineno , marker , line )
+ − 246 lineno += 1
+ − 247 print ' \n ' . join ( lines )
+ − 248
+ − 249 do_ll = do_longlist
+ − 250
+ − 251 def do_list ( self , arg ):
+ − 252 from StringIO import StringIO
+ − 253 oldstdout = sys . stdout
+ − 254 sys . stdout = StringIO ()
+ − 255 pdb . Pdb . do_list ( self , arg )
+ − 256 src = self . format_source ( sys . stdout . getvalue ())
+ − 257 sys . stdout = oldstdout
+ − 258 print src ,
+ − 259
+ − 260 do_l = do_list
+ − 261
+ − 262 def do_interact ( self , arg ):
+ − 263 """
+ − 264 interact
+ − 265
+ − 266 Start an interative interpreter whose global namespace
+ − 267 contains all the names found in the current scope.
+ − 268 """
+ − 269 ns = self . curframe . f_globals . copy ()
+ − 270 ns . update ( self . curframe . f_locals )
+ − 271 code . interact ( "*interactive*" , local = ns )
+ − 272
+ − 273 def do_track ( self , arg ):
+ − 274 """
+ − 275 track expression
+ − 276
+ − 277 Display a graph showing which objects are referred by the
+ − 278 value of the expression. This command requires pypy to be in
+ − 279 the current PYTHONPATH.
+ − 280 """
+ − 281 try :
+ − 282 from pypy.translator.tool.reftracker import track
+ − 283 except ImportError :
+ − 284 print '** cannot import pypy.translator.tool.reftracker **'
+ − 285 return
+ − 286 val = self . _getval ( arg )
+ − 287 track ( val )
+ − 288
+ − 289 def _get_watching ( self ):
+ − 290 return self . watching . setdefault ( self . curframe , {})
+ − 291
+ − 292 def _getval_or_undefined ( self , arg ):
+ − 293 try :
+ − 294 return eval ( arg , self . curframe . f_globals ,
+ − 295 self . curframe . f_locals )
+ − 296 except NameError :
+ − 297 return undefined
+ − 298
+ − 299 def do_watch ( self , arg ):
+ − 300 """
+ − 301 watch expression
+ − 302
+ − 303 Add expression to the watching list; expressions in this list
+ − 304 are evaluated at each step, and printed every time its value
+ − 305 changes.
+ − 306
+ − 307 WARNING: since the expressions is evaluated multiple time, pay
+ − 308 attention not to put expressions with side-effects in the
+ − 309 watching list.
+ − 310 """
+ − 311 try :
+ − 312 value = self . _getval_or_undefined ( arg )
+ − 313 except :
+ − 314 return
+ − 315 self . _get_watching ()[ arg ] = value
+ − 316
+ − 317 def do_unwatch ( self , arg ):
+ − 318 """
+ − 319 unwatch expression
+ − 320
+ − 321 Remove expression from the watching list.
+ − 322 """
+ − 323 try :
+ − 324 del self . _get_watching ()[ arg ]
+ − 325 except KeyError :
+ − 326 print '** not watching %s **' % arg
+ − 327
+ − 328 def _print_if_sticky ( self ):
+ − 329 if self . sticky :
+ − 330 sys . stdout . write ( CLEARSCREEN )
+ − 331 frame , lineno = self . stack [ self . curindex ]
+ − 332 filename = self . canonic ( frame . f_code . co_filename )
+ − 333 s = '> %s ( %r )' % ( filename , lineno )
+ − 334 print s
+ − 335 print
+ − 336 sticky_range = self . sticky_ranges . get ( self . curframe , None )
+ − 337 self . _printlonglist ( sticky_range )
+ − 338
+ − 339 def do_sticky ( self , arg ):
+ − 340 """
+ − 341 sticky [start end]
+ − 342
+ − 343 Toggle sticky mode. When in sticky mode, it clear the screen
+ − 344 and longlist the current functions, making the source
+ − 345 appearing always in the same position. Useful to follow the
+ − 346 flow control of a function when doing step-by-step execution.
+ − 347
+ − 348 If ``start`` and ``end`` are given, sticky mode is enabled and
+ − 349 only lines within that range (extremes included) will be
+ − 350 displayed.
+ − 351 """
+ − 352 if arg :
+ − 353 try :
+ − 354 start , end = map ( int , arg . split ())
+ − 355 except ValueError :
+ − 356 print '** Error when parsing argument: %s **' % arg
+ − 357 return
+ − 358 self . sticky = True
+ − 359 self . sticky_ranges [ self . curframe ] = start , end + 1
+ − 360 else :
+ − 361 self . sticky = not self . sticky
+ − 362 self . sticky_range = None
+ − 363 self . _print_if_sticky ()
+ − 364
+ − 365 def preloop ( self ):
+ − 366 self . _print_if_sticky ()
+ − 367 watching = self . _get_watching ()
+ − 368 for expr , oldvalue in watching . iteritems ():
+ − 369 newvalue = self . _getval_or_undefined ( expr )
+ − 370 # check for identity first; this prevents custom __eq__ to
+ − 371 # be called at every loop, and also prevents instances
+ − 372 # whose fields are changed to be displayed
+ − 373 if newvalue is not oldvalue or newvalue != oldvalue :
+ − 374 watching [ expr ] = newvalue
+ − 375 print ' %s : %r --> %r ' % ( expr , oldvalue , newvalue )
+ − 376
+ − 377 def do_source ( self , arg ):
+ − 378 try :
+ − 379 obj = self . _getval ( arg )
+ − 380 except :
+ − 381 return
+ − 382 try :
+ − 383 lines , lineno = getsourcelines ( obj )
+ − 384 except ( IOError , TypeError ), e :
+ − 385 print '** Error: %s **' % e
+ − 386 return
+ − 387 self . _print_lines ( lines , lineno , print_markers = False )
+ − 388
+ − 389 # Simplified interface
+ − 390
+ − 391 # copy some functions from pdb.py, but rebind the global dictionary
+ − 392 for name in 'run runeval runctx runcall set_trace pm' . split ():
+ − 393 func = getattr ( pdb , name )
+ − 394 newfunc = types . FunctionType ( func . func_code , globals (), func . func_name )
+ − 395 globals ()[ name ] = newfunc
+ − 396 del name , func , newfunc
+ − 397
+ − 398 def post_mortem ( t , Pdb = Pdb ):
+ − 399 p = Pdb ()
+ − 400 p . reset ()
+ − 401 orig_tb = t
+ − 402 while t . tb_next is not None :
+ − 403 t = t . tb_next
+ − 404 p . interaction ( t . tb_frame , t , orig_tb )
+ − 405
+ − 406 # pdb++ specific interface
+ − 407
+ − 408 def xpm ( Pdb = Pdb ):
+ − 409 """
+ − 410 To be used inside an except clause, enter a post-mortem pdb
+ − 411 related to the just catched exception.
+ − 412 """
+ − 413 post_mortem ( sys . exc_info ()[ 2 ], Pdb )