Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Yay! We have a useful debugger!

  • Loading branch information...
commit ac18daadb2d382f713a3a469aae2b0e5345bfc70 1 parent 389d874
@inducer authored
Showing with 885 additions and 116 deletions.
  1. +276 −0 ez_setup.py
  2. +8 −0 fermat.py
  3. +582 −116 pudb.py
  4. +19 −0 setup.py
View
276 ez_setup.py
@@ -0,0 +1,276 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from ez_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c9"
+DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+ 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+ 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+ 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+ 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+ 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+ 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+ 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+ 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+ 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+ 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+ 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+ 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+ 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+ 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+ 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+ 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+ 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+ 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+ 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+ 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+ 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+ 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+ 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+ 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+ 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+ 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+ 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+ 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
+ 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
+ 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
+ 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
+ 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
+ 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
+ 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
+}
+
+import sys, os
+try: from hashlib import md5
+except ImportError: from md5 import md5
+
+def _validate_md5(egg_name, data):
+ if egg_name in md5_data:
+ digest = md5(data).hexdigest()
+ if digest != md5_data[egg_name]:
+ print >>sys.stderr, (
+ "md5 validation of %s failed! (Possible download problem?)"
+ % egg_name
+ )
+ sys.exit(2)
+ return data
+
+def use_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ download_delay=15
+):
+ """Automatically find/download setuptools and make it available on sys.path
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end with
+ a '/'). `to_dir` is the directory where setuptools will be downloaded, if
+ it is not already available. If `download_delay` is specified, it should
+ be the number of seconds that will be paused before initiating a download,
+ should one be required. If an older version of setuptools is installed,
+ this routine will print a message to ``sys.stderr`` and raise SystemExit in
+ an attempt to abort the calling script.
+ """
+ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
+ def do_download():
+ egg = download_setuptools(version, download_base, to_dir, download_delay)
+ sys.path.insert(0, egg)
+ import setuptools; setuptools.bootstrap_install_from = egg
+ try:
+ import pkg_resources
+ except ImportError:
+ return do_download()
+ try:
+ pkg_resources.require("setuptools>="+version); return
+ except pkg_resources.VersionConflict, e:
+ if was_imported:
+ print >>sys.stderr, (
+ "The required version of setuptools (>=%s) is not available, and\n"
+ "can't be installed while this script is running. Please install\n"
+ " a more recent version first, using 'easy_install -U setuptools'."
+ "\n\n(Currently using %r)"
+ ) % (version, e.args[0])
+ sys.exit(2)
+ else:
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return do_download()
+ except pkg_resources.DistributionNotFound:
+ return do_download()
+
+def download_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ delay = 15
+):
+ """Download setuptools from a specified location and return its filename
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download attempt.
+ """
+ import urllib2, shutil
+ egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+ url = download_base + egg_name
+ saveto = os.path.join(to_dir, egg_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ from distutils import log
+ if delay:
+ log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help). I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+ %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+ version, download_base, delay, url
+ ); from time import sleep; sleep(delay)
+ log.warn("Downloading %s", url)
+ src = urllib2.urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = _validate_md5(egg_name, src.read())
+ dst = open(saveto,"wb"); dst.write(data)
+ finally:
+ if src: src.close()
+ if dst: dst.close()
+ return os.path.realpath(saveto)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ try:
+ import setuptools
+ except ImportError:
+ egg = None
+ try:
+ egg = download_setuptools(version, delay=0)
+ sys.path.insert(0,egg)
+ from setuptools.command.easy_install import main
+ return main(list(argv)+[egg]) # we're done here
+ finally:
+ if egg and os.path.exists(egg):
+ os.unlink(egg)
+ else:
+ if setuptools.__version__ == '0.0.1':
+ print >>sys.stderr, (
+ "You have an obsolete version of setuptools installed. Please\n"
+ "remove it from your system entirely before rerunning this script."
+ )
+ sys.exit(2)
+
+ req = "setuptools>="+version
+ import pkg_resources
+ try:
+ pkg_resources.require(req)
+ except pkg_resources.VersionConflict:
+ try:
+ from setuptools.command.easy_install import main
+ except ImportError:
+ from easy_install import main
+ main(list(argv)+[download_setuptools(delay=0)])
+ sys.exit(0) # try to force an exit
+ else:
+ if argv:
+ from setuptools.command.easy_install import main
+ main(argv)
+ else:
+ print "Setuptools version",version,"or greater has been installed."
+ print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+def update_md5(filenames):
+ """Update our built-in md5 registry"""
+
+ import re
+
+ for name in filenames:
+ base = os.path.basename(name)
+ f = open(name,'rb')
+ md5_data[base] = md5(f.read()).hexdigest()
+ f.close()
+
+ data = [" %r: %r,\n" % it for it in md5_data.items()]
+ data.sort()
+ repl = "".join(data)
+
+ import inspect
+ srcfile = inspect.getsourcefile(sys.modules[__name__])
+ f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+ match = re.search("\nmd5_data = {\n([^}]+)}", src)
+ if not match:
+ print >>sys.stderr, "Internal error!"
+ sys.exit(2)
+
+ src = src[:match.start(1)] + repl + src[match.end(1):]
+ f = open(srcfile,'w')
+ f.write(src)
+ f.close()
+
+
+if __name__=='__main__':
+ if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+ update_md5(sys.argv[2:])
+ else:
+ main(sys.argv[1:])
+
+
+
+
+
+
View
8 fermat.py
@@ -1,3 +1,7 @@
+def simple_func(x):
+ x += 1
+ return 2*x
+
def fermat(n):
"""Returns triplets of the form x^n + y^n = z^n.
Warning! Untested with n > 2.
@@ -6,9 +10,13 @@ def fermat(n):
for x in count(1):
for y in range(1, x+1):
for z in range(1, x**n+y**n + 1):
+ #from pudb import set_trace; set_trace()
if x**n + y**n == z**n:
+ fuckin
yield x, y, z
+print "SF", simple_func(10)
+
for i in fermat(2):
print i
View
698 pudb.py 100644 → 100755
@@ -4,15 +4,10 @@
import urwid.raw_display
import urwid
import bdb
+from code import InteractiveConsole
-# TODO: Breakpoint setting and deleting
-# TODO: Breakpoint listing
-# TODO: File open
-# TODO: Stack display
-# TODO: Stack browsing
-# TODO: Show stuff when exception happens
-# TODO: Postmortem
-# TODO: set_trace
+# TODO: Syntax highlighting
+# TODO: Pop open local variables
@@ -22,19 +17,28 @@
-------------------------------------------
Keys:
- n - next
- s - step
+ n - step over ("next")
+ s - step into
c - continue
- f - finish
+ r/f - finish current function
+ t - run to cursor
+ e - re-show traceback [post-mortem mode]
- o - view output
+ ! - invoke python shell in current environment
- b - breakpoint
+ b - toggle breakpoint
+ m - open module
j/k - up/down
h/l - scroll left/right
g/G - start/end
+ V - focus variables
+ S - focus stack
+ B - focus breakpoint list
+
+ f1/?/H - show this help screen
+
License:
--------
@@ -68,13 +72,20 @@
# debugger interface ----------------------------------------------------------
class Debugger(bdb.Bdb):
- def __init__(self):
+ def __init__(self, steal_output=True):
bdb.Bdb.__init__(self)
self.ui = DebuggerUI(self)
self.mainpyfile = ''
self._wait_for_mainpyfile = False
+ self.ignore_stack_start = 0
+ self.post_mortem = False
+
+ def enter_post_mortem(self, exc_tuple):
+ self.post_mortem = True
+ self.ui.enter_post_mortem(exc_tuple)
+
def reset(self):
bdb.Bdb.reset(self)
self.forget()
@@ -82,13 +93,28 @@ def reset(self):
def forget(self):
self.curframe = None
- def interaction(self, frame, traceback):
- self.stack, self.curindex = self.get_stack(frame, traceback)
- self.curframe = self.stack[self.curindex][0]
- self.ui.set_current_line(
- self.curframe.f_code.co_filename,
- self.curframe.f_lineno)
+ def do_clear(self, arg):
+ self.clear_bpbynumber(int(arg))
+
+ def set_frame_index(self, index):
+ self.curindex = index
+ self.curframe, lineno = self.stack[index]
+ self.ui.set_current_line(self.curframe.f_code.co_filename, lineno)
self.ui.set_locals(self.curframe.f_locals)
+ self.ui.update_stack()
+
+ def interaction(self, frame, traceback, exc_type=None, exc_value=None):
+ self.stack, self.curindex = self.get_stack(frame, traceback)
+ if traceback is not None:
+ self.curindex = len(self.stack)-1
+
+ if traceback:
+ self.ui.call_with_ui(
+ self.ui.show_exception,
+ exc_type, exc_value, traceback)
+
+ self.set_frame_index(self.curindex)
+
self.ui.call_with_ui(self.ui.event_loop)
def user_call(self, frame, argument_list):
@@ -97,7 +123,6 @@ def user_call(self, frame, argument_list):
if self._wait_for_mainpyfile:
return
if self.stop_here(frame):
- #print '--Call--'
self.interaction(frame, None)
def user_line(self, frame):
@@ -112,23 +137,19 @@ def user_line(self, frame):
def user_return(self, frame, return_value):
"""This function is called when a return trap is set here."""
frame.f_locals['__return__'] = return_value
- #print '--Return--'
self.interaction(frame, None)
def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
"""This function is called if an exception occurs,
but only if we are to stop at or just below this level."""
frame.f_locals['__exception__'] = exc_type, exc_value
+ self.enter_post_mortem((exc_type, exc_value, exc_traceback))
- if type(exc_type) == type(''):
- exc_type_name = exc_type
- else:
- exc_type_name = exc_type.__name__
-
- #print exc_type_name + ':', _saferepr(exc_value)
- self.interaction(frame, exc_traceback)
+ self.interaction(frame, exc_traceback, exc_type, exc_value)
def _runscript(self, filename):
+ self.ignore_stack_start = 2
+
# Start with fresh empty copy of globals and locals and tell the script
# that it's being run as __main__ to avoid scripts being able to access
# the debugger's namespace.
@@ -150,6 +171,12 @@ def _runscript(self, filename):
# UI stuff --------------------------------------------------------------------
+class MyConsole(InteractiveConsole):
+ pass
+
+
+
+
class SelectableText(urwid.Text):
def selectable(self):
return True
@@ -159,6 +186,20 @@ def keypress(self, size, key):
+def make_hotkey_markup(s):
+ import re
+ match = re.match(r"^([^_]*)_(.)(.*)$", s)
+ assert match is not None
+
+ return [
+ (None, match.group(1)),
+ ("hotkey", match.group(2)),
+ (None, match.group(3)),
+ ]
+
+
+
+
class SignalWrap(urwid.WidgetWrap):
def __init__(self, w):
@@ -175,11 +216,25 @@ def keypress(self, size, key):
return self._w.keypress(size, key)
+class PostSignalWrap(SignalWrap):
+ def keypress(self, size, key):
+ result = self._w.keypress(size, key)
+
+ if result is not None:
+ for mask, handler in self.event_listeners:
+ if mask is None or mask == key:
+ return handler(self, size, key)
+
+ return result
+
+
+
class SourceLine(urwid.FlowWidget):
- def __init__(self, dbg_ui, text):
+ def __init__(self, dbg_ui, text, attr):
self.dbg_ui = dbg_ui
self.text = text
+ self.attr = attr
self.has_breakpoint = False
self.is_current = False
@@ -198,6 +253,7 @@ def rows(self, (maxcol,), focus=False):
return 1
def render(self, (maxcol,), focus=False):
+ hscroll = self.dbg_ui.source_hscroll_start
attrs = []
if self.is_current:
crnt = ">"
@@ -211,22 +267,37 @@ def render(self, (maxcol,), focus=False):
else:
bp = " "
+ if focus:
+ attrs.append("focused")
+
+ if not attrs and self.attr is not None:
+ attr = self.attr
+ else:
+ attr = [(" ".join(attrs+["source"]), hscroll+maxcol-2)]
+
+ from urwid.util import rle_subseg, rle_len
+
text = self.text
if self.dbg_ui.source_hscroll_start:
- text = text[self.dbg_ui.source_hscroll_start:]
+ text = text[hscroll:]
+ attr = rle_subseg(attr,
+ self.dbg_ui.source_hscroll_start,
+ rle_len(attr))
- if len(text) + 2 > maxcol:
- text = text[:maxcol-2]
+ text = crnt+bp+text
+ attr = [("source", 2)] + attr
- line = crnt+bp+text
+ # clipping ------------------------------------------------------------
- if focus:
- attrs.append("focused")
+ if len(text) > maxcol:
+ text = text[:maxcol]
+
+ # shipout -------------------------------------------------------------
- return urwid.TextCanvas(
- [line],
- attr=[[(" ".join(attrs+["source"]), maxcol)]],
- maxcol=maxcol)
+ from urwid.util import apply_target_encoding
+ txt, cs = apply_target_encoding(text)
+
+ return urwid.TextCanvas([txt], [attr], [cs], maxcol=maxcol)
def keypress(self, size, key):
return key
@@ -235,6 +306,9 @@ def keypress(self, size, key):
class DebuggerUI(object):
+ CAPTION_TEXT = (u"PuDB - The Python Urwid debugger - F1 for help"
+ u" - © Andreas Klöckner 2008")
+
def __init__(self, dbg):
self.debugger = dbg
Attr = urwid.AttrWrap
@@ -247,72 +321,171 @@ def __init__(self, dbg):
self.var_list = urwid.ListBox(self.locals)
self.stack_walker = urwid.SimpleListWalker(
- [Attr(SelectableText(fname),
+ [Attr(SelectableText(fname, wrap="clip"),
None, "focused frame")
for fname in []])
- self.stack_list = urwid.ListBox(self.stack_walker)
+ self.stack_list = SignalWrap(
+ urwid.ListBox(self.stack_walker))
self.bp_walker = urwid.SimpleListWalker(
- [Attr(SelectableText(fname),
+ [Attr(SelectableText(fname, wrap="clip"),
None, "focused breakpoint")
for fname in []])
- self.bp_list = urwid.ListBox(self.bp_walker)
+ self.bp_list = SignalWrap(
+ urwid.ListBox(self.bp_walker))
- rhs_col = urwid.Pile([
+ self.rhs_col = urwid.Pile([
Attr(urwid.Pile([
- ("flow", urwid.Text("Locals:")),
+ ("flow", urwid.Text(make_hotkey_markup("_Variables:"))),
Attr(self.var_list, "variables"),
]), None, "focused sidebar"),
Attr(urwid.Pile([
- ("flow", urwid.Text("Stack:")),
+ ("flow", urwid.Text(make_hotkey_markup("_Stack:"))),
Attr(self.stack_list, "stack"),
]), None, "focused sidebar"),
Attr(urwid.Pile([
- ("flow", urwid.Text("Breakpoints:")),
- Attr(self.bp_list, "breakpoints"),
+ ("flow", urwid.Text(make_hotkey_markup("_Breakpoints:"))),
+ Attr(self.bp_list, "breakpoint"),
]), None, "focused sidebar"),
])
- columns = urwid.AttrWrap(
+ self.columns = urwid.AttrWrap(
urwid.Columns(
[
("weight", 3,
urwid.AttrWrap(self.source_list, "source")),
- ("weight", 1, rhs_col),
+ ("weight", 1, self.rhs_col),
],
dividechars=1),
"background")
- instruct = urwid.Text(u"PuDB - The Python Urwid debugger - F1 for help"
- u" - © Andreas Klöckner 2008")
- header = urwid.AttrWrap(instruct, "header")
- self.top = SignalWrap(urwid.Frame(columns, header))
+ self.caption = urwid.Text(self.CAPTION_TEXT)
+ header = urwid.AttrWrap(self.caption, "header")
+ self.top = SignalWrap(urwid.Frame(self.columns, header))
+
+ # stack listeners -----------------------------------------------------
+ def examine_frame(w, size, key):
+ _, pos = self.stack_list._w.get_focus()
+ self.debugger.set_frame_index(
+ self.debugger.ignore_stack_start + pos)
+
+ self.stack_list.listen("enter", examine_frame)
+
+ # stack listeners -----------------------------------------------------
+ def examine_breakpoint(w, size, key):
+ _, pos = self.bp_list._w.get_focus()
+ bp = self._get_bp_list()[pos]
+
+ def make_lv(label, value):
+ return urwid.AttrWrap(urwid.Text([
+ ("label", label), str(value)]),
+ "fixed value", "fixed value")
+
+ if bp.cond is None:
+ cond = ""
+ else:
+ cond = str(bp.cond)
+
+ enabled_checkbox = urwid.CheckBox(
+ "Enabled", bp.enabled)
+ cond_edit = urwid.Edit([
+ ("label", "Condition: ")
+ ], cond)
+
+ lb = urwid.ListBox([
+ make_lv("File: ", bp.file),
+ make_lv("Line: ", bp.line),
+ make_lv("Hits: ", bp.hits),
+ enabled_checkbox,
+ urwid.AttrWrap(cond_edit, "value", "value")
+ ])
+
+ if self.dialog(lb, [
+ ("OK", True),
+ ("Cancel", False),
+ ], title="Edit Breakpoint"):
+ bp.enabled = enabled_checkbox.get_state()
+ cond = cond_edit.get_edit_text()
+ if cond:
+ bp.cond = cond
+ else:
+ bp.cond = None
+
+ self.bp_list.listen("enter", examine_breakpoint)
- # listeners -----------------------------------------------------------
+ # top-level listeners -------------------------------------------------
def end():
self.quit_event_loop = True
def next(w, size, key):
- self.debugger.set_next(self.debugger.curframe)
- end()
+ if self.debugger.post_mortem:
+ self.message("Post-mortem mode: Can't modify state.")
+ else:
+ self.debugger.set_next(self.debugger.curframe)
+ end()
def step(w, size, key):
- self.debugger.set_step()
- end()
+ if self.debugger.post_mortem:
+ self.message("Post-mortem mode: Can't modify state.")
+ else:
+ self.debugger.set_step()
+ end()
+
+ def finish(w, size, key):
+ if self.debugger.post_mortem:
+ self.message("Post-mortem mode: Can't modify state.")
+ else:
+ self.debugger.set_return(self.debugger.curframe)
+ end()
+
+
+ def cont(w, size, key):
+ if self.debugger.post_mortem:
+ self.message("Post-mortem mode: Can't modify state.")
+ else:
+ self.debugger.set_continue()
+ end()
+
+ def run_to_cursor(w, size, key):
+ if self.debugger.post_mortem:
+ self.message("Post-mortem mode: Can't modify state.")
+ else:
+ sline, pos = self.source.get_focus()
+ canon_file = self.debugger.canonic(self.shown_file)
+ lineno = pos+1
+
+ err = self.debugger.set_break(self.shown_file, pos+1, temporary=True)
+ if err:
+ self.message("Error dealing with breakpoint:\n"+ err)
+
+ self.debugger.set_continue()
+ end()
+
+ def show_traceback(w, size, key):
+ if self.debugger.post_mortem:
+ self.show_exception(*self.post_mortem_exc_tuple)
+ else:
+ self.message("Not in post-mortem mode: No traceback available.")
def show_output(w, size, key):
self.screen.stop()
raw_input("Hit Enter to return:")
self.screen.start()
- def finish(w, size, key):
- self.debugger.set_return(self.debugger.curframe)
- end()
+ def run_shell(w, size, key):
+ self.screen.stop()
+ loc = self.debugger.curframe.f_locals
+ cons = MyConsole(loc)
+ cons.interact("")
+ self.screen.start()
+ class RHColumnFocuser:
+ def __init__(self, idx):
+ self.idx = idx
- def cont(w, size, key):
- self.debugger.set_continue()
- end()
+ def __call__(subself, w, size, key):
+ self.columns.set_focus(self.rhs_col)
+ self.rhs_col.set_focus(self.rhs_col.widget_list[subself.idx])
def move_home(w, size, key):
self.source.set_focus(0)
@@ -338,63 +511,113 @@ def scroll_right(w, size, key):
for sl in self.source:
sl._invalidate()
- def breakpoint(w, size, key):
+ def toggle_breakpoint(w, size, key):
if self.shown_file:
sline, pos = self.source.get_focus()
- err = self.debugger.set_break(self.shown_file, pos+1,
- temporary=False, cond=None, funcname=None)
- sline.set_breakpoint(True)
+ canon_file = self.debugger.canonic(self.shown_file)
+ lineno = pos+1
+
+ existing_breaks = self.debugger.get_breaks(canon_file, lineno)
+ if existing_breaks:
+ err = self.debugger.clear_break(canon_file, lineno)
+ sline.set_breakpoint(False)
+ else:
+ err = self.debugger.set_break(self.shown_file, pos+1)
+ sline.set_breakpoint(True)
+
+ if err:
+ self.message("Error dealing with breakpoint:\n"+ err)
+
self.update_breakpoints()
else:
raise RuntimeError, "no valid current file"
+ def pick_module(w, size, key):
+ import sys
+ modules = sorted(name
+ for name, mod in sys.modules.iteritems()
+ if hasattr(mod, "__file__"))
+
+ def build_filtered_mod_list(filt_string=""):
+ return [urwid.AttrWrap(SelectableText(mod),
+ None, "focused selectable")
+ for mod in modules if filt_string in mod]
+
+ mod_list = urwid.SimpleListWalker(build_filtered_mod_list())
+ lb = urwid.ListBox(mod_list)
+
+ class FilterEdit(urwid.Edit):
+ def keypress(self, size, key):
+ result = urwid.Edit.keypress(self, size, key)
+
+ if result is None:
+ mod_list[:] = build_filtered_mod_list(
+ self.get_edit_text())
+
+ return result
+
+ edit = FilterEdit("Filter: ")
+ w = urwid.Pile([
+ ("flow", edit),
+ urwid.AttrWrap(lb, "selectable")])
+
+ result = self.dialog(w, [
+ ("OK", True),
+ ("Cancel", False),
+ ], title="Pick Module")
+
+ if result:
+ widget, pos = lb.get_focus()
+ mod = sys.modules[widget.get_text()[0]]
+ filename = self.debugger.canonic(mod.__file__)
+
+ from os.path import splitext
+ base, ext = splitext(filename)
+ if ext == ".pyc":
+ ext = ".py"
+ filename = base+".py"
+
+ self.set_current_file(filename)
+
def quit(w, size, key):
self.debugger.set_quit()
end()
def help(w, size, key):
- l = urwid.ListBox([urwid.Text(HELP_TEXT)])
- ok = Attr(urwid.Button("OK", lambda btn: end()), "button")
- w = urwid.Columns([
- l,
- ("fixed", 10, urwid.ListBox([ok])),
- ])
- #w = urwid.Padding(w, ('fixed left', 1), ('fixed right', 0))
- w = urwid.LineBox(w)
-
- w = urwid.Overlay(w, self.top,
- align="center",
- valign="middle",
- width=('relative', 75),
- height=('relative', 75),
- )
- w = Attr(w, "background")
-
- self.event_loop(w)
+ self.message(HELP_TEXT, title="PuDB Help")
self.top.listen("n", next)
self.top.listen("s", step)
- self.top.listen("o", show_output)
self.top.listen("f", finish)
+ self.top.listen("r", finish)
self.top.listen("c", cont)
+ self.top.listen("t", run_to_cursor)
+ self.top.listen("e", show_traceback)
self.top.listen("o", show_output)
+ self.top.listen("!", run_shell)
self.top.listen("j", move_down)
self.top.listen("k", move_up)
self.top.listen("h", scroll_left)
self.top.listen("l", scroll_right)
+ self.top.listen("V", RHColumnFocuser(0))
+ self.top.listen("S", RHColumnFocuser(1))
+ self.top.listen("B", RHColumnFocuser(2))
+
self.top.listen("home", move_home)
self.top.listen("end", move_end)
self.top.listen("g", move_home)
self.top.listen("G", move_end)
- self.top.listen("b", breakpoint)
+ self.top.listen("b", toggle_breakpoint)
+ self.top.listen("m", pick_module)
self.top.listen("q", quit)
self.top.listen("H", help)
self.top.listen("f1", help)
+ self.top.listen("?", help)
# setup ---------------------------------------------------------------
self.screen = urwid.raw_display.Screen()
@@ -407,34 +630,110 @@ def help(w, size, key):
self.quit_event_loop = False
+ def message(self, msg, title="Message", **kwargs):
+ self.dialog(
+ urwid.ListBox([urwid.Text(msg)]),
+ [("OK", True)], title=title, **kwargs)
+
+ def dialog(self, content, buttons_and_results,
+ title=None, bind_enter_esc=True):
+ class ResultSetter:
+ def __init__(subself, res):
+ subself.res = res
+
+ def __call__(subself, btn):
+ self.quit_event_loop = [subself.res]
+
+ Attr = urwid.AttrWrap
+
+ if bind_enter_esc:
+ content = PostSignalWrap(content)
+ def enter(w, size, key): self.quit_event_loop = [True]
+ def esc(w, size, key): self.quit_event_loop = [False]
+ content.listen("enter", enter)
+ content.listen("esc", esc)
+
+ w = urwid.Columns([
+ content,
+ ("fixed", 1, urwid.SolidFill()),
+ ("fixed", 10, urwid.ListBox([
+ Attr(urwid.Button(btn_text, ResultSetter(btn_result)),
+ "button", "focused button")
+ for btn_text, btn_result in buttons_and_results
+ ])),
+ ])
+
+ if title is not None:
+ w = urwid.Pile([
+ ("flow", urwid.AttrWrap(
+ urwid.Text(title, align="center"),
+ "dialog title")),
+ ("fixed", 1, urwid.SolidFill()),
+ w])
+
+ w = urwid.LineBox(w)
+
+ w = urwid.Overlay(w, self.top,
+ align="center",
+ valign="middle",
+ width=('relative', 75),
+ height=('relative', 75),
+ )
+ w = Attr(w, "background")
+
+ return self.event_loop(w)[0]
+
@staticmethod
def setup_palette(screen):
screen.register_palette([
- ("header", "black", "dark cyan", "standout"),
-
- ("source", "yellow", "dark blue", "standout"),
- ("focused source", "black", "dark green"),
- ("current source", "black", "dark cyan"),
- ("current focused source", "white", "dark cyan"),
+ ("header", "black", "light gray", "standout"),
- ("breakpoint source", "yellow", "dark red", "standout"),
+ ("breakpoint source", "yellow", "dark red"),
("breakpoint focused source", "black", "dark red"),
("current breakpoint source", "black", "dark red"),
("current breakpoint focused source", "white", "dark red"),
- ("variables", "black", "dark cyan", "standout"),
- ("focused variable", "black", "dark green", "standout"),
+ ("variables", "black", "dark cyan"),
+ ("focused variable", "black", "dark green"),
+ ("return value", "yellow", "dark blue"),
+ ("focused return value", "white", "light blue"),
("stack", "black", "dark cyan", "standout"),
- ("focused frame", "black", "dark green", "standout"),
+ ("focused frame", "black", "dark green"),
+ ("current frame", "white,bold", "dark cyan"),
+ ("focused current frame", "white,bold", "dark green", "bold"),
- ("breakpoint", "black", "dark cyan", "standout"),
- ("focused breakpoint", "black", "dark green", "standout"),
+ ("breakpoint", "black", "dark cyan"),
+ ("focused breakpoint", "black", "dark green"),
- ("button", "white", "dark blue", "standout"),
+ ("selectable", "black", "dark cyan"),
+ ("focused selectable", "black", "dark green"),
- ("background", "black", "light gray", "standout"),
+ ("button", "white", "dark blue"),
+ ("focused button", "light cyan", "black"),
+
+ ("background", "black", "light gray"),
+ ("hotkey", "black,underline", "light gray", "underline"),
("focused sidebar", "yellow", "dark gray", "standout"),
+
+ ("warning", "white,bold", "dark red", "standout"),
+
+ ("label", "black", "light gray"),
+ ("value", "black", "dark cyan"),
+ ("fixed value", "dark gray", "dark cyan"),
+
+ ("dialog title", "white, bold", "dark cyan"),
+
+ # highlighting
+ ("source", "yellow", "dark blue"),
+ ("focused source", "black", "dark green"),
+ ("current source", "black", "dark cyan"),
+ ("current focused source", "white", "dark cyan"),
+
+ ("keyword", "white,bold", "dark blue"),
+ ("literal", "light magenta", "dark blue"),
+ ("punctuation", "light gray", "dark blue"),
+ ("comment", "light gray", "dark blue"),
])
# UI enter/exit -----------------------------------------------------------
@@ -477,10 +776,93 @@ def event_loop(self, toplevel=None):
self.size = self.screen.get_cols_rows()
else:
toplevel.keypress(self.size, k)
+
+ return self.quit_event_loop
finally:
self.quit_event_loop = prev_quit_loop
# debugger-facing interface -----------------------------------------------
+ def enter_post_mortem(self, exc_tuple):
+ self.post_mortem_exc_tuple = exc_tuple
+
+ self.caption.set_text([
+ (None, self.CAPTION_TEXT+ " "),
+
+ ("warning", "[POST-MORTEM MODE]")
+ ])
+
+ def format_source(self, lines):
+ try:
+ import pygments
+ except ImportError:
+ return [SourceLine(self,
+ line.rstrip("\n\r").replace("\t", 8*" "), None)
+ for line in lines]
+ else:
+ from pygments import highlight
+ from pygments.lexers import PythonLexer
+ from pygments.formatter import Formatter
+ import pygments.token as t
+
+ result = []
+
+ ATTR_MAP = {
+ t.Token: "source",
+ t.Keyword: "keyword",
+ t.Literal: "literal",
+ t.Punctuation: "punctuation",
+ t.Comment: "comment",
+ }
+
+ class UrwidFormatter(Formatter):
+ def __init__(subself, **options):
+ Formatter.__init__(subself, **options)
+ subself.current_line = ""
+ subself.current_attr = []
+
+ def format(subself, tokensource, outfile):
+ def add_snippet(ttype, s):
+ if not s:
+ return
+
+ while not ttype in ATTR_MAP:
+ if ttype.parent is not None:
+ ttype = ttype.parent
+ else:
+ raise RuntimeError(
+ "untreated token type: %s" % str(ttype))
+
+ attr = ATTR_MAP[ttype]
+
+ subself.current_line += s
+ subself.current_attr.append((attr, len(s)))
+
+ def shipout_line():
+ result.append(
+ SourceLine(self,
+ subself.current_line,
+ subself.current_attr))
+ subself.current_line = ""
+ subself.current_attr = []
+
+ for ttype, value in tokensource:
+ while True:
+ newline_pos = value.find("\n")
+ if newline_pos == -1:
+ add_snippet(ttype, value)
+ break
+ else:
+ add_snippet(ttype, value[:newline_pos])
+ shipout_line()
+ value = value[newline_pos+1:]
+
+ if subself.current_line:
+ shipout_line()
+
+ highlight("".join(lines), PythonLexer(), UrwidFormatter())
+
+ return result
+
def set_current_file(self, fname):
if self.shown_file != fname:
try:
@@ -488,14 +870,14 @@ def set_current_file(self, fname):
except IOError:
self.source[:] = [SourceLine(self, fname)]
else:
- self.source[:] = [
- SourceLine(self, line.rstrip("\n"))
- for line in inf.readlines()]
+ self.source[:] = self.format_source(inf.readlines())
self.shown_file = fname
self.current_line = None
def set_current_line(self, fname, line):
+ changed_file = self.shown_file != fname
+
self.set_current_file(fname)
line = line-1
@@ -505,29 +887,107 @@ def set_current_line(self, fname, line):
if line >= 0 and line < len(self.source):
self.current_line = self.source[line]
self.current_line.set_current(True)
- self.source.set_focus(line)
- #self.source_list.shi('middle')
+ self.source_list.set_focus(line)
+ if changed_file:
+ self.source_list.set_focus_valign("middle")
def set_locals(self, locals):
vars = locals.keys()
vars.sort()
- self.locals[:] = [
+ loc_list = []
+
+ if "__return__" in vars:
+ loc_list.append(
+ urwid.AttrWrap(
+ SelectableText("Return: %s" % locals["__return__"],
+ wrap="clip"),
+ "return value", "focused return value"))
+
+ loc_list.extend(
urwid.AttrWrap(
- SelectableText("%s: %s" % (var, locals[var])),
+ SelectableText("%s: %s" % (var, locals[var]),
+ wrap="clip"),
None, "focused variable")
for var in vars
- if not var.startswith("_")]
+ if not var.startswith("_"))
+
+
+ self.locals[:] = loc_list
+
+ def _get_bp_list(self):
+ return [bp
+ for fn, bp_lst in self.debugger.get_all_breaks().iteritems()
+ for lineno in bp_lst
+ for bp in self.debugger.get_breaks(fn, lineno)
+ if not bp.temporary]
+
+ def _format_fname(self, fname):
+ from os.path import dirname, basename
+ name = basename(fname)
+
+ if name == "__init__.py":
+ name = "..."+dirname(filename)[:-10]+name
+ return name
def update_breakpoints(self):
+ def format_bp(bp):
+ return "%s:%d" % (self._format_fname(bp.file), bp.line)
+
self.bp_walker[:] = [
urwid.AttrWrap(
- SelectableText(str(bp)),
- "breakpoint", "focused breakpoint")
- for bp in self.debugger.get_all_breaks()]
+ SelectableText(format_bp(bp), wrap="clip"),
+ None, "focused breakpoint")
+ for bp in self._get_bp_list()]
+
+ def update_stack(self):
+ def format_frame(frame_lineno):
+ frame, lineno = frame_lineno
+ code = frame.f_code
+ result = "%s (%s:%d)" % (
+ code.co_name,
+ self._format_fname(code.co_filename),
+ lineno)
+
+ if frame is self.debugger.curframe:
+ result = ">> "+result
+ attr = "current frame"
+ focused_attr = "focused current frame"
+ else:
+ result = " "+result
+ attr = None
+ focused_attr = "focused frame"
+ return urwid.AttrWrap(SelectableText(result, wrap="clip"),
+ attr, focused_attr)
+
+
+ self.stack_walker[:] = [format_frame(frame)
+ for frame in self.debugger.stack[self.debugger.ignore_stack_start:]]
+
+ def show_exception(self, exc_type, exc_value, traceback):
+ from traceback import format_exception
+
+ self.message(
+ "".join(format_exception(
+ exc_type, exc_value, traceback)),
+ title="Exception Occurred")
+
+
+
+def set_trace():
+ import sys
+ Debugger().set_trace(sys._getframe().f_back)
+def post_mortem(t):
+ p = Pdb()
+ p.reset()
+ while t.tb_next is not None:
+ t = t.tb_next
+ p.interaction(t.tb_frame, t)
+def pm():
+ post_mortem(sys.last_traceback)
def main():
import sys
@@ -554,7 +1014,13 @@ def main():
# command line arguments.
dbg = Debugger()
- dbg._runscript(mainpyfile)
+ try:
+ dbg._runscript(mainpyfile)
+ except:
+ exc_tuple = sys.exc_info()
+ type, value, tb = exc_tuple
+ dbg.enter_post_mortem(exc_tuple)
+ dbg.interaction(tb.tb_frame, tb, type, value)
View
19 setup.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+from ez_setup import use_setuptools
+
+use_setuptools()
+
+from setuptools import setup
+
+setup(name='pudb',
+ version='0.90',
+ description='Python Urwid debugger',
+ author='Andreas Kloeckner',
+ author_email='inform@tiker.net',
+ requires=[
+ "urwid>=0.9.8.4",
+ ],
+ url='http://pypi.python.org/pypi/pudb',
+ py_modules="pudb")
+
Please sign in to comment.
Something went wrong with that request. Please try again.