~ipython-contrib/+junk/ipython-zmq

« back to all changes in this revision

Viewing changes to IPython/Debugger.py

  • Committer: ville
  • Date: 2008-02-16 09:50:47 UTC
  • mto: (0.12.1 ipython_main)
  • mto: This revision was merged to the branch mainline in revision 990.
  • Revision ID: ville@ville-pc-20080216095047-500x6dluki1iz40o
initialization (no svn history)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
"""
 
3
Pdb debugger class.
 
4
 
 
5
Modified from the standard pdb.Pdb class to avoid including readline, so that
 
6
the command line completion of other programs which include this isn't
 
7
damaged.
 
8
 
 
9
In the future, this class will be expanded with improvements over the standard
 
10
pdb.
 
11
 
 
12
The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
 
13
changes. Licensing should therefore be under the standard Python terms.  For
 
14
details on the PSF (Python Software Foundation) standard license, see:
 
15
 
 
16
http://www.python.org/2.2.3/license.html
 
17
 
 
18
$Id: Debugger.py 2913 2007-12-31 12:42:14Z vivainio $"""
 
19
 
 
20
#*****************************************************************************
 
21
#
 
22
#       This file is licensed under the PSF license.
 
23
#
 
24
#       Copyright (C) 2001 Python Software Foundation, www.python.org
 
25
#       Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
 
26
#
 
27
#
 
28
#*****************************************************************************
 
29
 
 
30
from IPython import Release
 
31
__author__  = '%s <%s>' % Release.authors['Fernando']
 
32
__license__ = 'Python'
 
33
 
 
34
import bdb
 
35
import cmd
 
36
import linecache
 
37
import os
 
38
import sys
 
39
 
 
40
from IPython import PyColorize, ColorANSI, ipapi
 
41
from IPython.genutils import Term
 
42
from IPython.excolors import ExceptionColors
 
43
 
 
44
# See if we can use pydb.
 
45
has_pydb = False
 
46
prompt = 'ipdb> '
 
47
#We have to check this directly from sys.argv, config struct not yet available
 
48
if '-pydb' in sys.argv:
 
49
    try:        
 
50
        import pydb
 
51
        if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
 
52
            # Version 1.17 is broken, and that's what ships with Ubuntu Edgy, so we
 
53
            # better protect against it.
 
54
            has_pydb = True
 
55
    except ImportError:
 
56
        print "Pydb (http://bashdb.sourceforge.net/pydb/) does not seem to be available"
 
57
 
 
58
if has_pydb:
 
59
    from pydb import Pdb as OldPdb
 
60
    #print "Using pydb for %run -d and post-mortem" #dbg
 
61
    prompt = 'ipydb> '
 
62
else:
 
63
    from pdb import Pdb as OldPdb
 
64
 
 
65
# Allow the set_trace code to operate outside of an ipython instance, even if
 
66
# it does so with some limitations.  The rest of this support is implemented in
 
67
# the Tracer constructor.
 
68
def BdbQuit_excepthook(et,ev,tb):
 
69
    if et==bdb.BdbQuit:
 
70
        print 'Exiting Debugger.'
 
71
    else:
 
72
        BdbQuit_excepthook.excepthook_ori(et,ev,tb)
 
73
 
 
74
def BdbQuit_IPython_excepthook(self,et,ev,tb):
 
75
    print 'Exiting Debugger.'
 
76
 
 
77
class Tracer(object):
 
78
    """Class for local debugging, similar to pdb.set_trace.
 
79
 
 
80
    Instances of this class, when called, behave like pdb.set_trace, but
 
81
    providing IPython's enhanced capabilities.
 
82
 
 
83
    This is implemented as a class which must be initialized in your own code
 
84
    and not as a standalone function because we need to detect at runtime
 
85
    whether IPython is already active or not.  That detection is done in the
 
86
    constructor, ensuring that this code plays nicely with a running IPython,
 
87
    while functioning acceptably (though with limitations) if outside of it.
 
88
    """
 
89
 
 
90
    def __init__(self,colors=None):
 
91
        """Create a local debugger instance.
 
92
 
 
93
        :Parameters:
 
94
 
 
95
          - `colors` (None): a string containing the name of the color scheme to
 
96
        use, it must be one of IPython's valid color schemes.  If not given, the
 
97
        function will default to the current IPython scheme when running inside
 
98
        IPython, and to 'NoColor' otherwise.
 
99
 
 
100
        Usage example:
 
101
 
 
102
        from IPython.Debugger import Tracer; debug_here = Tracer()
 
103
 
 
104
        ... later in your code
 
105
        debug_here()  # -> will open up the debugger at that point.
 
106
 
 
107
        Once the debugger activates, you can use all of its regular commands to
 
108
        step through code, set breakpoints, etc.  See the pdb documentation
 
109
        from the Python standard library for usage details.
 
110
        """
 
111
 
 
112
        global __IPYTHON__
 
113
        try:
 
114
            __IPYTHON__
 
115
        except NameError:
 
116
            # Outside of ipython, we set our own exception hook manually
 
117
            __IPYTHON__ = ipapi.get(True,False)
 
118
            BdbQuit_excepthook.excepthook_ori = sys.excepthook
 
119
            sys.excepthook = BdbQuit_excepthook
 
120
            def_colors = 'NoColor'
 
121
            try:
 
122
                # Limited tab completion support
 
123
                import rlcompleter,readline
 
124
                readline.parse_and_bind('tab: complete')
 
125
            except ImportError:
 
126
                pass
 
127
        else:
 
128
            # In ipython, we use its custom exception handler mechanism
 
129
            ip = ipapi.get()
 
130
            def_colors = ip.options.colors
 
131
            ip.set_custom_exc((bdb.BdbQuit,),BdbQuit_IPython_excepthook)
 
132
 
 
133
        if colors is None:
 
134
            colors = def_colors
 
135
        self.debugger = Pdb(colors)
 
136
 
 
137
    def __call__(self):
 
138
        """Starts an interactive debugger at the point where called.
 
139
 
 
140
        This is similar to the pdb.set_trace() function from the std lib, but
 
141
        using IPython's enhanced debugger."""
 
142
        
 
143
        self.debugger.set_trace(sys._getframe().f_back)
 
144
 
 
145
def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
 
146
    """Make new_fn have old_fn's doc string. This is particularly useful
 
147
    for the do_... commands that hook into the help system.
 
148
    Adapted from from a comp.lang.python posting
 
149
    by Duncan Booth."""
 
150
    def wrapper(*args, **kw):
 
151
        return new_fn(*args, **kw)
 
152
    if old_fn.__doc__:
 
153
        wrapper.__doc__ = old_fn.__doc__ + additional_text
 
154
    return wrapper
 
155
 
 
156
def _file_lines(fname):
 
157
    """Return the contents of a named file as a list of lines.
 
158
 
 
159
    This function never raises an IOError exception: if the file can't be
 
160
    read, it simply returns an empty list."""
 
161
 
 
162
    try:
 
163
        outfile = open(fname)
 
164
    except IOError:
 
165
        return []
 
166
    else:
 
167
        out = outfile.readlines()
 
168
        outfile.close()
 
169
        return out
 
170
 
 
171
class Pdb(OldPdb):
 
172
    """Modified Pdb class, does not load readline."""
 
173
 
 
174
    if sys.version[:3] >= '2.5' or has_pydb:
 
175
        def __init__(self,color_scheme='NoColor',completekey=None,
 
176
                     stdin=None, stdout=None):
 
177
 
 
178
            # Parent constructor:
 
179
            if has_pydb and completekey is None:
 
180
                OldPdb.__init__(self,stdin=stdin,stdout=Term.cout)
 
181
            else:
 
182
                OldPdb.__init__(self,completekey,stdin,stdout)
 
183
                
 
184
            self.prompt = prompt # The default prompt is '(Pdb)'
 
185
            
 
186
            # IPython changes...
 
187
            self.is_pydb = has_pydb
 
188
 
 
189
            if self.is_pydb:
 
190
 
 
191
                # iplib.py's ipalias seems to want pdb's checkline
 
192
                # which located in pydb.fn
 
193
                import pydb.fns
 
194
                self.checkline = lambda filename, lineno: \
 
195
                                 pydb.fns.checkline(self, filename, lineno)
 
196
 
 
197
                self.curframe = None
 
198
                self.do_restart = self.new_do_restart
 
199
 
 
200
                self.old_all_completions = __IPYTHON__.Completer.all_completions
 
201
                __IPYTHON__.Completer.all_completions=self.all_completions
 
202
 
 
203
                self.do_list = decorate_fn_with_doc(self.list_command_pydb,
 
204
                                                    OldPdb.do_list)
 
205
                self.do_l     = self.do_list
 
206
                self.do_frame = decorate_fn_with_doc(self.new_do_frame,
 
207
                                                     OldPdb.do_frame)
 
208
 
 
209
            self.aliases = {}
 
210
 
 
211
            # Create color table: we copy the default one from the traceback
 
212
            # module and add a few attributes needed for debugging
 
213
            self.color_scheme_table = ExceptionColors.copy()
 
214
 
 
215
            # shorthands 
 
216
            C = ColorANSI.TermColors
 
217
            cst = self.color_scheme_table
 
218
 
 
219
            cst['NoColor'].colors.breakpoint_enabled = C.NoColor
 
220
            cst['NoColor'].colors.breakpoint_disabled = C.NoColor
 
221
 
 
222
            cst['Linux'].colors.breakpoint_enabled = C.LightRed
 
223
            cst['Linux'].colors.breakpoint_disabled = C.Red
 
224
 
 
225
            cst['LightBG'].colors.breakpoint_enabled = C.LightRed
 
226
            cst['LightBG'].colors.breakpoint_disabled = C.Red
 
227
 
 
228
            self.set_colors(color_scheme)
 
229
 
 
230
            # Add a python parser so we can syntax highlight source while
 
231
            # debugging.
 
232
            self.parser = PyColorize.Parser()
 
233
 
 
234
 
 
235
    else:
 
236
        # Ugly hack: for Python 2.3-2.4, we can't call the parent constructor,
 
237
        # because it binds readline and breaks tab-completion.  This means we
 
238
        # have to COPY the constructor here.
 
239
        def __init__(self,color_scheme='NoColor'):
 
240
            bdb.Bdb.__init__(self)
 
241
            cmd.Cmd.__init__(self,completekey=None) # don't load readline
 
242
            self.prompt = 'ipdb> ' # The default prompt is '(Pdb)'
 
243
            self.aliases = {}
 
244
 
 
245
            # These two lines are part of the py2.4 constructor, let's put them
 
246
            # unconditionally here as they won't cause any problems in 2.3.
 
247
            self.mainpyfile = ''
 
248
            self._wait_for_mainpyfile = 0
 
249
 
 
250
            # Read $HOME/.pdbrc and ./.pdbrc
 
251
            try:
 
252
                self.rcLines = _file_lines(os.path.join(os.environ['HOME'],
 
253
                                                        ".pdbrc"))
 
254
            except KeyError:
 
255
                self.rcLines = []
 
256
            self.rcLines.extend(_file_lines(".pdbrc"))
 
257
 
 
258
            # Create color table: we copy the default one from the traceback
 
259
            # module and add a few attributes needed for debugging
 
260
            ExceptionColors.set_active_scheme(color_scheme)
 
261
            self.color_scheme_table = ExceptionColors.copy()
 
262
 
 
263
            # shorthands 
 
264
            C = ColorANSI.TermColors
 
265
            cst = self.color_scheme_table
 
266
 
 
267
            cst['NoColor'].colors.breakpoint_enabled = C.NoColor
 
268
            cst['NoColor'].colors.breakpoint_disabled = C.NoColor
 
269
 
 
270
            cst['Linux'].colors.breakpoint_enabled = C.LightRed
 
271
            cst['Linux'].colors.breakpoint_disabled = C.Red
 
272
 
 
273
            cst['LightBG'].colors.breakpoint_enabled = C.LightRed
 
274
            cst['LightBG'].colors.breakpoint_disabled = C.Red
 
275
 
 
276
            self.set_colors(color_scheme)
 
277
 
 
278
            # Add a python parser so we can syntax highlight source while
 
279
            # debugging.
 
280
            self.parser = PyColorize.Parser()
 
281
        
 
282
    def set_colors(self, scheme):
 
283
        """Shorthand access to the color table scheme selector method."""
 
284
        self.color_scheme_table.set_active_scheme(scheme)
 
285
 
 
286
    def interaction(self, frame, traceback):
 
287
        __IPYTHON__.set_completer_frame(frame)
 
288
        OldPdb.interaction(self, frame, traceback)
 
289
 
 
290
    def new_do_up(self, arg):
 
291
        OldPdb.do_up(self, arg)
 
292
        __IPYTHON__.set_completer_frame(self.curframe)
 
293
    do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
 
294
 
 
295
    def new_do_down(self, arg):
 
296
        OldPdb.do_down(self, arg)
 
297
        __IPYTHON__.set_completer_frame(self.curframe)
 
298
 
 
299
    do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
 
300
 
 
301
    def new_do_frame(self, arg):
 
302
        OldPdb.do_frame(self, arg)
 
303
        __IPYTHON__.set_completer_frame(self.curframe)
 
304
 
 
305
    def new_do_quit(self, arg):
 
306
        
 
307
        if hasattr(self, 'old_all_completions'):
 
308
            __IPYTHON__.Completer.all_completions=self.old_all_completions
 
309
        
 
310
        
 
311
        return OldPdb.do_quit(self, arg)
 
312
 
 
313
    do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
 
314
 
 
315
    def new_do_restart(self, arg):
 
316
        """Restart command. In the context of ipython this is exactly the same
 
317
        thing as 'quit'."""
 
318
        self.msg("Restart doesn't make sense here. Using 'quit' instead.")
 
319
        return self.do_quit(arg)
 
320
 
 
321
    def postloop(self):
 
322
        __IPYTHON__.set_completer_frame(None)
 
323
 
 
324
    def print_stack_trace(self):
 
325
        try:
 
326
            for frame_lineno in self.stack:
 
327
                self.print_stack_entry(frame_lineno, context = 5)
 
328
        except KeyboardInterrupt:
 
329
            pass
 
330
 
 
331
    def print_stack_entry(self,frame_lineno,prompt_prefix='\n-> ',
 
332
                          context = 3):
 
333
        #frame, lineno = frame_lineno
 
334
        print >>Term.cout, self.format_stack_entry(frame_lineno, '', context)
 
335
 
 
336
    def format_stack_entry(self, frame_lineno, lprefix=': ', context = 3):
 
337
        import linecache, repr
 
338
        
 
339
        ret = []
 
340
        
 
341
        Colors = self.color_scheme_table.active_colors
 
342
        ColorsNormal = Colors.Normal
 
343
        tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
 
344
        tpl_call = '%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
 
345
        tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
 
346
        tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
 
347
                                            ColorsNormal)
 
348
        
 
349
        frame, lineno = frame_lineno
 
350
        
 
351
        return_value = ''
 
352
        if '__return__' in frame.f_locals:
 
353
            rv = frame.f_locals['__return__']
 
354
            #return_value += '->'
 
355
            return_value += repr.repr(rv) + '\n'
 
356
        ret.append(return_value)
 
357
 
 
358
        #s = filename + '(' + `lineno` + ')'
 
359
        filename = self.canonic(frame.f_code.co_filename)
 
360
        link = tpl_link % filename
 
361
        
 
362
        if frame.f_code.co_name:
 
363
            func = frame.f_code.co_name
 
364
        else:
 
365
            func = "<lambda>"
 
366
            
 
367
        call = ''
 
368
        if func != '?':         
 
369
            if '__args__' in frame.f_locals:
 
370
                args = repr.repr(frame.f_locals['__args__'])
 
371
            else:
 
372
                args = '()'
 
373
            call = tpl_call % (func, args)
 
374
 
 
375
        # The level info should be generated in the same format pdb uses, to
 
376
        # avoid breaking the pdbtrack functionality of python-mode in *emacs.
 
377
        if frame is self.curframe:
 
378
            ret.append('> ')
 
379
        else:
 
380
            ret.append('  ')
 
381
        ret.append('%s(%s)%s\n' % (link,lineno,call))
 
382
            
 
383
        start = lineno - 1 - context//2
 
384
        lines = linecache.getlines(filename)
 
385
        start = max(start, 0)
 
386
        start = min(start, len(lines) - context)
 
387
        lines = lines[start : start + context]
 
388
            
 
389
        for i,line in enumerate(lines):
 
390
            show_arrow = (start + 1 + i == lineno)
 
391
            linetpl = (frame is self.curframe or show_arrow) \
 
392
                      and tpl_line_em \
 
393
                      or tpl_line
 
394
            ret.append(self.__format_line(linetpl, filename,
 
395
                                          start + 1 + i, line,
 
396
                                          arrow = show_arrow) )
 
397
 
 
398
        return ''.join(ret)
 
399
 
 
400
    def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
 
401
        bp_mark = ""
 
402
        bp_mark_color = ""
 
403
 
 
404
        scheme = self.color_scheme_table.active_scheme_name
 
405
        new_line, err = self.parser.format2(line, 'str', scheme)
 
406
        if not err: line = new_line
 
407
 
 
408
        bp = None
 
409
        if lineno in self.get_file_breaks(filename):
 
410
            bps = self.get_breaks(filename, lineno)
 
411
            bp = bps[-1]
 
412
        
 
413
        if bp:
 
414
            Colors = self.color_scheme_table.active_colors
 
415
            bp_mark = str(bp.number)
 
416
            bp_mark_color = Colors.breakpoint_enabled
 
417
            if not bp.enabled:
 
418
                bp_mark_color = Colors.breakpoint_disabled
 
419
 
 
420
        numbers_width = 7
 
421
        if arrow:
 
422
            # This is the line with the error
 
423
            pad = numbers_width - len(str(lineno)) - len(bp_mark)
 
424
            if pad >= 3:
 
425
                marker = '-'*(pad-3) + '-> '
 
426
            elif pad == 2:
 
427
                 marker = '> '
 
428
            elif pad == 1:
 
429
                 marker = '>'
 
430
            else:
 
431
                 marker = ''
 
432
            num = '%s%s' % (marker, str(lineno))
 
433
            line = tpl_line % (bp_mark_color + bp_mark, num, line)
 
434
        else:
 
435
            num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
 
436
            line = tpl_line % (bp_mark_color + bp_mark, num, line)
 
437
            
 
438
        return line
 
439
 
 
440
    def list_command_pydb(self, arg):
 
441
        """List command to use if we have a newer pydb installed"""
 
442
        filename, first, last = OldPdb.parse_list_cmd(self, arg)
 
443
        if filename is not None:
 
444
            self.print_list_lines(filename, first, last)
 
445
        
 
446
    def print_list_lines(self, filename, first, last):
 
447
        """The printing (as opposed to the parsing part of a 'list'
 
448
        command."""
 
449
        try:
 
450
            Colors = self.color_scheme_table.active_colors
 
451
            ColorsNormal = Colors.Normal
 
452
            tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
 
453
            tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
 
454
            src = []
 
455
            for lineno in range(first, last+1):
 
456
                line = linecache.getline(filename, lineno)
 
457
                if not line:
 
458
                    break
 
459
 
 
460
                if lineno == self.curframe.f_lineno:
 
461
                    line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
 
462
                else:
 
463
                    line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
 
464
 
 
465
                src.append(line)
 
466
                self.lineno = lineno
 
467
 
 
468
            print >>Term.cout, ''.join(src)
 
469
 
 
470
        except KeyboardInterrupt:
 
471
            pass
 
472
 
 
473
    def do_list(self, arg):
 
474
        self.lastcmd = 'list'
 
475
        last = None
 
476
        if arg:
 
477
            try:
 
478
                x = eval(arg, {}, {})
 
479
                if type(x) == type(()):
 
480
                    first, last = x
 
481
                    first = int(first)
 
482
                    last = int(last)
 
483
                    if last < first:
 
484
                        # Assume it's a count
 
485
                        last = first + last
 
486
                else:
 
487
                    first = max(1, int(x) - 5)
 
488
            except:
 
489
                print '*** Error in argument:', `arg`
 
490
                return
 
491
        elif self.lineno is None:
 
492
            first = max(1, self.curframe.f_lineno - 5)
 
493
        else:
 
494
            first = self.lineno + 1
 
495
        if last is None:
 
496
            last = first + 10
 
497
        self.print_list_lines(self.curframe.f_code.co_filename, first, last)
 
498
 
 
499
    do_l = do_list
 
500
 
 
501
    def do_pdef(self, arg):
 
502
        """The debugger interface to magic_pdef"""
 
503
        namespaces = [('Locals', self.curframe.f_locals),
 
504
                      ('Globals', self.curframe.f_globals)]
 
505
        __IPYTHON__.magic_pdef(arg, namespaces=namespaces)
 
506
 
 
507
    def do_pdoc(self, arg):
 
508
        """The debugger interface to magic_pdoc"""
 
509
        namespaces = [('Locals', self.curframe.f_locals),
 
510
                      ('Globals', self.curframe.f_globals)]
 
511
        __IPYTHON__.magic_pdoc(arg, namespaces=namespaces)
 
512
 
 
513
    def do_pinfo(self, arg):
 
514
        """The debugger equivalant of ?obj"""
 
515
        namespaces = [('Locals', self.curframe.f_locals),
 
516
                      ('Globals', self.curframe.f_globals)]
 
517
        __IPYTHON__.magic_pinfo("pinfo %s" % arg, namespaces=namespaces)