pax_global_header00006660000000000000000000000064131525766340014526gustar00rootroot0000000000000052 comment=6be804e07a5dbce17a3e8c25128c037e5f7ef536 pudb-2017.1.4/000077500000000000000000000000001315257663400127145ustar00rootroot00000000000000pudb-2017.1.4/.coveragerc000066400000000000000000000010211315257663400150270ustar00rootroot00000000000000[run] branch = True [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ if self\.debug # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: ignore_errors = True [html] directory = coverage_html_reportpudb-2017.1.4/.gitignore000066400000000000000000000002361315257663400147050ustar00rootroot00000000000000*.pyc *~ .*.sw[op] *.egg-info dist build distribute-*gz distribute-*egg setuptools-*gz setuptools-*egg traceback*txt .DS_Store .cache/ .coverage coverage.xml pudb-2017.1.4/.gitlab-ci.yml000066400000000000000000000030111315257663400153430ustar00rootroot00000000000000Python 2.7: script: - py_version=2.7 - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - export REQUIREMENTS_TXT=requirements.dev.txt - ". ./build-and-test-py-project.sh" tags: - python2.7 - linux except: - tags Python 3.5: script: - py_version=3.5 - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - export REQUIREMENTS_TXT=requirements.dev.txt - ". ./build-and-test-py-project.sh" tags: - python3.5 - linux except: - tags Python 3.6: script: - py_version=3.6 - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - export REQUIREMENTS_TXT=requirements.dev.txt - ". ./build-and-test-py-project.sh" tags: - python3.6 - linux except: - tags Python 2.6: script: - py_version=2.6 - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - export REQUIREMENTS_TXT=requirements.dev.txt - ". ./build-and-test-py-project.sh" tags: - python2.6 - linux except: - tags Flake8: script: - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-flake8.sh - ". ./prepare-and-run-flake8.sh pudb test" tags: - python3.5 except: - tags Documentation: script: - EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-docs.sh - ". ./build-docs.sh" tags: - python3.5 only: - master pudb-2017.1.4/.mailmap000066400000000000000000000007521315257663400143410ustar00rootroot00000000000000# Prevent git from showing duplicate names with commands like "git shortlog" # See the manpage of git-shortlog for details. # The syntax is: # Name that should be used Bad name # # You can skip Bad name if it is the same as the one that should be used, and # is unique. # # This file is up-to-date if the command git log --format="%aN <%aE>" | sort # -u # gives no duplicates. Andreas Klöckner Andreas Kloeckner `_, too. Features -------- * Syntax-highlighted source, the stack, breakpoints and variables are all visible at once and continuously updated. This helps you be more aware of what's going on in your program. Variable displays can be expanded, collapsed and have various customization options. * Simple, keyboard-based navigation using single keystrokes makes debugging quick and easy. PuDB understands cursor-keys and Vi shortcuts for navigation. Other keys are inspired by the corresponding pdb commands. * Use search to find relevant source code, or use "m" to invoke the module browser that shows loaded modules, lets you load new ones and reload existing ones. * Breakpoints can be set just by pointing at a source line and hitting "b" and then edited visually in the breakpoints window. Or hit "t" to run to the line under the cursor. * Drop to a Python shell in the current environment by pressing "!". * PuDB places special emphasis on exception handling. A post-mortem mode makes it easy to retrace a crashing program's last steps. * IPython integration (see `wiki `_) * Should work with Python 2.6 and newer, including Python 3. Links ----- `PuDB documentation `_ PuDB also has a `mailing list `_ that you may use to submit patches and requests for help. You can also send a pull request to the `GitHub repository `_ Development Version ------------------- You may obtain the development version using the `Git `_ version control tool.:: git clone http://git.tiker.net/trees/pudb.git You may also `browse the code `_ online. The repository is also mirrored at `GitHub `_. pudb-2017.1.4/debug_me.py000066400000000000000000000015671315257663400150460ustar00rootroot00000000000000class MyClass(object): def __init__(self, a, b): self.a = a self.b = b self._b = [b] mc = MyClass(15, MyClass(12, None)) def simple_func(x): x += 1 s = range(20) z = None w = () y = dict((i, i**2) for i in s) k = set(range(5, 99)) try: x.invalid except AttributeError: pass #import sys #sys.exit(1) return 2*x def fermat(n): """Returns triplets of the form x^n + y^n = z^n. Warning! Untested with n > 2. """ # source: "Fermat's last Python script" # https://earthboundkid.jottit.com/fermat.py # :) for x in range(100): for y in range(1, x+1): for z in range(1, x**n+y**n + 1): if x**n + y**n == z**n: yield x, y, z print("SF %s" % simple_func(10)) for i in fermat(2): print(i) print("FINISHED") pudb-2017.1.4/doc/000077500000000000000000000000001315257663400134615ustar00rootroot00000000000000pudb-2017.1.4/doc/.gitignore000066400000000000000000000000071315257663400154460ustar00rootroot00000000000000_build pudb-2017.1.4/doc/Makefile000066400000000000000000000011311315257663400151150ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = pudb SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)pudb-2017.1.4/doc/conf.py000066400000000000000000000123661315257663400147700ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # pudb documentation build configuration file, created by # sphinx-quickstart on Wed Apr 5 16:44:29 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'pudb' copyright = '2017, Andreas Kloeckner and contributors' author = 'Andreas Kloeckner and contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. ver_dic = {} with open("../pudb/__init__.py") as ver_file: ver_src = ver_file.read() exec(compile(ver_src, "../pudb/__init__.py", 'exec'), ver_dic) version = ".".join(str(x) for x in ver_dic["VERSION"]) # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} html_theme = "alabaster" html_theme_options = { "extra_nav_links": { "🚀 Github": "https://github.com/inducer/pudb", "💾 Download Releases": "https://pypi.python.org/pypi/pudb", } } html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', 'searchbox.html', ] } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'pudbdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'pudb.tex', 'pudb Documentation', 'Andreas Kloeckner and contributors', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'pudb', 'pudb Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'pudb', 'pudb Documentation', author, 'pudb', 'One line description of project.', 'Miscellaneous'), ] autoclass_content = "both" pudb-2017.1.4/doc/index.rst000066400000000000000000000005031315257663400153200ustar00rootroot00000000000000Welcome to pudb's documentation! ================================ .. include:: ../README.rst Table of Contents ----------------- .. toctree:: :maxdepth: 2 :caption: Contents: starting usage shells misc Indices and Tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` pudb-2017.1.4/doc/misc.rst000066400000000000000000000025241315257663400151510ustar00rootroot00000000000000Installing ---------- Install PuDB using the command:: pip install pudb If you are using Python 2.5, PuDB version 2013.5.1 is the last version to support that version of Python. urwid 1.1.1 works with Python 2.5, newer versions do not. FAQ --- **Q: I navigated to the Variables/Stack/Breakpoints view. How do I get back to the source view?** A: Press your left arrow key. **Q: Where are breakpoints stored?** A: All PuDB information is stored in a location specified by the `XDG Base Directory Specification `_. Usually, it is ``~/.config/pudb``. Breakpoints are stored in a file called ``saved-breakpoints``. Also in this location are the shell history from the ``!`` shell (``shell-history``) and the PuDB settings (``pudb.cfg``). **Q: I killed PuDB and now my terminal is broken. How do I fix it?** A: Type the ``reset`` command (even if you cannot see what you are typing, it should work). If this happens on a regular basis, please report it as a bug. License and Dependencies ------------------------ PuDB is distributed under the MIT license. It relies on the following excellent pieces of software: * Ian Ward's `urwid `_ console UI library * Georg Brandl's `pygments `_ syntax highlighter .. include:: ../LICENSE pudb-2017.1.4/doc/shells.rst000066400000000000000000000053751315257663400155170ustar00rootroot00000000000000Shells ====== Internal shell -------------- At any point while debugging, press ``Ctrl-x`` to switch to the built in interactive shell. From here, you can execute Python commands at the current point of the debugger. Press ``Ctrl-x`` again to move back to the debugger. Keyboard shortcuts defined in the internal shell: +--------------------+--------------------+ |Enter |Execute the current | | |command | +--------------------+--------------------+ |Ctrl-v |Insert a newline | | |(for multiline | | |commands) | +--------------------+--------------------+ |Ctrl-n/p |Browse command | | |history | +--------------------+--------------------+ |Up/down arrow |Select history | +--------------------+--------------------+ |TAB |Tab completion | +--------------------+--------------------+ |+/- |grow/shrink the | | |shell (when a | | |history item is | | |selected) | +--------------------+--------------------+ |_/= |minimize/maximize | | |the shell (when a | | |history item is | | |selected) | +--------------------+--------------------+ External shells --------------- To open the external shell, press the ``!`` key while debugging. Unlike the internal shell, external shells close the debugger UI while the shell is active. Press ``Ctrl-d`` at any time to exit the shell and return to the debugger. To configure the shell used by PuDB, open the settings (``Ctrl-p``) and select the shell. PuDB supports the following external shells. - Internal (same as pressing ``Ctrl-x``). This is the default. - Classic (similar to the default ``python`` interactive shell) - `IPython `_ - `bpython `_ - `ptpython `_ Custom shells ------------- To define a custom external shell, create a file with a function ``pudb_shell(_globals, _locals)`` at the module level. Then, in the settings (``Ctrl-p``), select "Custom" under the shell settings, and add the path to the file. Here is an example custom shell file: .. literalinclude:: ../example-shell.py :language: python Note, many shells do not allow passing in globals and locals dictionaries separately. In this case, you can merge the two with .. code-block:: python from pudb.shell import SetPropagatingDict ns = SetPropagatingDict([_locals, _globals], _locals) Here is more information on ``SetPropagatingDict``: .. autoclass:: pudb.shell.SetPropagatingDict pudb-2017.1.4/doc/starting.rst000066400000000000000000000027431315257663400160540ustar00rootroot00000000000000Getting Started --------------- To start debugging, simply insert:: from pudb import set_trace; set_trace() A shorter alternative to this is:: import pudb; pu.db Or, if pudb is already imported, just this will suffice:: pu.db Insert either of these snippets into the piece of code you want to debug, or run the entire script with:: pudb my-script.py or, in Python 3:: pudb3 my-script.py This is equivalent to:: python -m pudb.run my-script.py which is useful if you want to run PuDB in a version of Python other than the one you most recently installed PuDB with. Remote debugging ^^^^^^^^^^^^^^^^ Rudimentary remote debugging is also supported:: from pudb.remote import set_trace set_trace(term_size=(80, 24)) At this point, the debugger will look for a free port and wait for a telnet connection:: pudb:6899: Please telnet into 127.0.0.1 6899. pudb:6899: Waiting for client... Usage with pytest ^^^^^^^^^^^^^^^^^ To use PuDB with `pytest `_, consider using the `pytest-pudb `_ plugin. Alternatively, as of version 2017.1.2, pudb can be used to debug test failures in `pytest `_, by running the test runner like so:: $ pytest --pdbcls pudb.debugger:Debugger --pdb --capture=no Note the need to pass --capture=no (or its synonym -s) as otherwise pytest tries to manage the standard streams itself. (contributed by Antony Lee) pudb-2017.1.4/doc/upload-docs.sh000077500000000000000000000001211315257663400162240ustar00rootroot00000000000000#! /bin/sh rsync --verbose --archive --delete _build/html/* doc-upload:doc/pudb pudb-2017.1.4/doc/usage.rst000066400000000000000000000030031315257663400153130ustar00rootroot00000000000000Starting the debugger without breaking -------------------------------------- To start the debugger without actually pausing use:: from pudb import set_trace; set_trace(paused=False) at the top of your code. This will start the debugger without breaking, and run it until a predefined breakpoint is hit. You can also press ``b`` on a ``set_trace`` call inside the debugger, and it will prevent it from stopping there. Interrupt Handlers ------------------ ``set_trace`` sets ``SIGINT`` (i.e., ``Ctrl-c``) to run ``set_trace``, so that typing ``Ctrl-c`` while your code is running will break the code and start debugging. See the docstring of ``set_interrupt_handler`` for more information. Note that this only works in the main thread. Programming PuDB ---------------- At the programming language level, PuDB displays the same interface as Python's built-in `pdb module `_. Just replace ``pdb`` with ``pudb``. (One exception: ``run`` is called ``runstatement``.) Controlling How Values Get Shown -------------------------------- * Set a custom stringifer in the preferences. An example file might look like this:: def pudb_stringifier(obj): return "HI" * Add a :meth:`safely_stringify_for_pudb` to the type. A stringifier is expected to *never* raise an exception. If an exception is raised, pudb will silently fall back to its built-in stringification behavior. A stringifier that takes a long time will further stall the debugger UI while it runs. pudb-2017.1.4/example-shell.py000066400000000000000000000034661315257663400160370ustar00rootroot00000000000000""" This file shows how you can define a custom shell for PuDB. This is the shell used when pressing the ! key in the debugger (it does not affect the Ctrl-x shell that is built into PuDB). To create a custom shell, create a file like this one with a function called pudb_shell(_globals, _locals) defined at the module level. Note that the file will be execfile'd. Then, go to the PuDB preferences window (type Ctrl-p inside of PuDB) and add the path to the file in the "Custom" field under the "Shell" heading. The example in this file """ # Define this a function with this name and signature at the module level. def pudb_shell(_globals, _locals): """ This example shell runs a classic Python shell. It is based on run_classic_shell in pudb.shell. """ # Many shells only let you pass in a single locals dictionary, rather than # separate globals and locals dictionaries. In this case, you can use # pudb.shell.SetPropagatingDict to automatically merge the two into a # single dictionary. It does this in such a way that assignments propogate # to _locals, so that when the debugger is at the module level, variables # can be reassigned in the shell. from pudb.shell import SetPropagatingDict ns = SetPropagatingDict([_locals, _globals], _locals) try: import readline import rlcompleter HAVE_READLINE = True except ImportError: HAVE_READLINE = False if HAVE_READLINE: readline.set_completer( rlcompleter.Completer(ns).complete) readline.parse_and_bind("tab: complete") readline.clear_history() from code import InteractiveConsole cons = InteractiveConsole(ns) cons.interact("Press Ctrl-D to return to the debugger") # When the function returns, control will be returned to the debugger. pudb-2017.1.4/example-stringifier.py000066400000000000000000000071251315257663400172510ustar00rootroot00000000000000#!/usr/bin/env python """ This file shows how you can define a custom stringifier for PuDB. A stringifier is a function that is called on the variables in the namespace for display in the variables list. The default is type()*, as this is fast and cannot fail. PuDB also includes built-in options for using str() and repr(). Note that str() and repr() will be slower than type(), which is especially noticable when you have many varialbes, or some of your variables have very large string/repr representations. Also note that if you just want to change the type for one or two variables, you can do that by selecting the variable in the variables list and pressing Enter, or by pressing t, s, or r. To define a custom stringifier, create a file like this one with a function called pudb_stringifier() at the module level. pudb_stringifier(obj) should return a string value for an object (note that str() will always be called on the result). Note that the file will be execfile'd. Then, go to the PuDB preferences window (type Ctrl-p inside of PuDB), and add the path to the file in the "Custom" field under the "Variable Stringifier" heading. The example in this file returns the string value, unless it take more than 500 ms (1 second in Python 2.5-) to compute, in which case it falls back to the type. TIP: Run "python -m pudb.run example-stringifier.py and set this file to be your stringifier in the settings to see how it works. You can use custom stringifiers to do all sorts of things: callbacks, custom views on variables of interest without having to use a watch variable or the expanded view, etc. * - Actually, the default is a mix between type() and str(). str() is used for a handful of "safe" types for which it is guaranteed to be fast and not to fail. """ import time import signal import sys import math class TimeOutError(Exception): pass def timeout(signum, frame, time): raise TimeOutError("Timed out after %d seconds" % time) def run_with_timeout(code, time, globals=None): """ Evaluate ``code``, timing out after ``time`` seconds. In Python 2.5 and lower, ``time`` is rounded up to the nearest integer. The return value is whatever ``code`` returns. """ # Set the signal handler and a ``time``-second alarm signal.signal(signal.SIGALRM, lambda s, f: timeout(s, f, time)) if sys.version_info > (2, 5): signal.setitimer(signal.ITIMER_REAL, time) else: # The above only exists in Python 2.6+ # Otherwise, we have to use this, which only supports integer arguments # Use math.ceil to round a float up. time = int(math.ceil(time)) signal.alarm(time) r = eval(code, globals) signal.alarm(0) # Disable the alarm return r def pudb_stringifier(obj): """ This is the custom stringifier. It returns str(obj), unless it take more than a second to compute, in which case it falls back to type(obj). """ try: return run_with_timeout("str(obj)", 0.5, {'obj':obj}) except TimeOutError: return (type(obj), "(str too slow to compute)") # Example usage class FastString(object): def __str__(self): return "This was fast to compute." class SlowString(object): def __str__(self): time.sleep(10) # Return the string value after ten seconds return "This was slow to compute." fast = FastString() slow = SlowString() # If you are running this in PuDB, set this file as your custom stringifier in # the prefs (Ctrl-p) and run to here. Notice how fast shows the string value, # but slow shows the type, as the string value takes too long to compute. pudb-2017.1.4/example-theme.py000066400000000000000000000017371315257663400160310ustar00rootroot00000000000000# Supported 16 color values: # 'h0' (color number 0) through 'h15' (color number 15) # or # 'default' (use the terminal's default foreground), # 'black', 'dark red', 'dark green', 'brown', 'dark blue', # 'dark magenta', 'dark cyan', 'light gray', 'dark gray', # 'light red', 'light green', 'yellow', 'light blue', # 'light magenta', 'light cyan', 'white' # # Supported 256 color values: # 'h0' (color number 0) through 'h255' (color number 255) # # 256 color chart: http://en.wikipedia.org/wiki/File:Xterm_color_chart.png # # "setting_name": (foreground_color, background_color), # See pudb/theme.py # (https://github.com/inducer/pudb/blob/master/pudb/theme.py) to see what keys # there are. # Note, be sure to test your theme in both curses and raw mode (see the bottom # of the preferences window). Curses mode will be used with screen or tmux. palette.update({ "source": (add_setting("black", "underline"), "dark green"), "comment": ("h250", "default") }) pudb-2017.1.4/manual-tests/000077500000000000000000000000001315257663400153315ustar00rootroot00000000000000pudb-2017.1.4/manual-tests/.not-actually-ci-tests000066400000000000000000000000001315257663400214650ustar00rootroot00000000000000pudb-2017.1.4/manual-tests/test-api.py000066400000000000000000000000671315257663400174340ustar00rootroot00000000000000def f(): fail from pudb import runcall runcall(f) pudb-2017.1.4/manual-tests/test-encoding.py000066400000000000000000000001301315257663400204400ustar00rootroot00000000000000def f(encoding=None): print 'ENCODING:', encoding from pudb import runcall runcall(f) pudb-2017.1.4/manual-tests/test-postmortem.py000066400000000000000000000001331315257663400210660ustar00rootroot00000000000000def f(): fail try: f() except: from pudb import post_mortem post_mortem() pudb-2017.1.4/pudb/000077500000000000000000000000001315257663400136465ustar00rootroot00000000000000pudb-2017.1.4/pudb/__init__.py000066400000000000000000000200671315257663400157640ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function NUM_VERSION = (2017, 1, 4) VERSION = ".".join(str(nv) for nv in NUM_VERSION) __version__ = VERSION from pudb.py3compat import raw_input, PY3 from pudb.settings import load_config, save_config CONFIG = load_config() save_config(CONFIG) class PudbShortcuts(object): @property def db(self): import sys dbg = _get_debugger() import threading if isinstance(threading.current_thread(), threading._MainThread): set_interrupt_handler() dbg.set_trace(sys._getframe().f_back) @property def go(self): import sys dbg = _get_debugger() import threading if isinstance(threading.current_thread(), threading._MainThread): set_interrupt_handler() dbg.set_trace(sys._getframe().f_back, paused=False) if PY3: import builtins builtins.__dict__["pu"] = PudbShortcuts() else: import __builtin__ __builtin__.__dict__["pu"] = PudbShortcuts() CURRENT_DEBUGGER = [] def _get_debugger(**kwargs): if not CURRENT_DEBUGGER: from pudb.debugger import Debugger dbg = Debugger(**kwargs) CURRENT_DEBUGGER.append(dbg) return dbg else: return CURRENT_DEBUGGER[0] import signal # noqa DEFAULT_SIGNAL = signal.SIGINT del signal def runscript(mainpyfile, args=None, pre_run="", steal_output=False): dbg = _get_debugger(steal_output=steal_output) # Note on saving/restoring sys.argv: it's a good idea when sys.argv was # modified by the script being debugged. It's a bad idea when it was # changed by the user from the command line. The best approach would be to # have a "restart" command which would allow explicit specification of # command line arguments. import sys if args is not None: prev_sys_argv = sys.argv[:] sys.argv = [mainpyfile] + args # replace pudb's dir with script's dir in front of module search path. from os.path import dirname prev_sys_path = sys.path[:] sys.path[0] = dirname(mainpyfile) while True: if pre_run: from subprocess import call retcode = call(pre_run, close_fds=True, shell=True) if retcode: print("*** WARNING: pre-run process exited with code %d." % retcode) raw_input("[Hit Enter]") status_msg = "" try: dbg._runscript(mainpyfile) except SystemExit: se = sys.exc_info()[1] status_msg = "The debuggee exited normally with " \ "status code %s.\n\n" % se.code except: dbg.post_mortem = True dbg.interaction(None, sys.exc_info()) while True: import urwid pre_run_edit = urwid.Edit("", pre_run) if not CONFIG["prompt_on_quit"]: return result = dbg.ui.call_with_ui(dbg.ui.dialog, urwid.ListBox(urwid.SimpleListWalker([urwid.Text( "Your PuDB session has ended.\n\n%s" "Would you like to quit PuDB or restart your program?\n" "You may hit 'q' to quit." % status_msg), urwid.Text("\n\nIf you decide to restart, this command " "will be run prior to actually restarting:"), urwid.AttrMap(pre_run_edit, "value") ])), [ ("Restart", "restart"), ("Examine", "examine"), ("Quit", "quit"), ], focus_buttons=True, bind_enter_esc=False, title="Finished", extra_bindings=[ ("q", "quit"), ("esc", "examine"), ]) if result == "quit": return if result == "examine": dbg.post_mortem = True dbg.interaction(None, sys.exc_info(), show_exc_dialog=False) if result == "restart": break pre_run = pre_run_edit.get_edit_text() dbg.restart() if args is not None: sys.argv = prev_sys_argv sys.path = prev_sys_path def runstatement(statement, globals=None, locals=None): _get_debugger().run(statement, globals, locals) def runeval(expression, globals=None, locals=None): return _get_debugger().runeval(expression, globals, locals) def runcall(*args, **kwds): return _get_debugger().runcall(*args, **kwds) def set_trace(paused=True): """ Start the debugger If paused=False (the default is True), the debugger will not stop here (same as immediately pressing 'c' to continue). """ import sys dbg = _get_debugger() import threading if isinstance(threading.current_thread(), threading._MainThread): set_interrupt_handler() dbg.set_trace(sys._getframe().f_back, paused=paused) start = set_trace def _interrupt_handler(signum, frame): from pudb import _get_debugger _get_debugger().set_trace(frame, as_breakpoint=False) def set_interrupt_handler(interrupt_signal=DEFAULT_SIGNAL): """ Set up an interrupt handler, to activate PuDB when Python receives the signal `interrupt_signal`. By default it is SIGINT (i.e., Ctrl-c). To use a different signal, pass it as the argument to this function, like `set_interrupt_handler(signal.SIGALRM)`. You can then break your code with `kill -ALRM pid`, where `pid` is the process ID of the Python process. Note that PuDB will still use SIGINT once it is running to allow breaking running code. If that is an issue, you can change the default signal by hooking `pudb.DEFAULT_SIGNAL`, like >>> import pudb >>> import signal >>> pudb.DEFAULT_SIGNAL = signal.SIGALRM Note, this may not work if you use threads or subprocesses. Note, this only works when called from the main thread. """ import signal old_handler = signal.getsignal(interrupt_signal) if old_handler is not signal.default_int_handler \ and old_handler != signal.SIG_DFL and old_handler != _interrupt_handler: # Since we don't currently have support for a non-default signal handlers, # let's avoid undefined-behavior territory and just show a warning. from warnings import warn if old_handler is None: # This is the documented meaning of getsignal()->None. old_handler = 'not installed from python' return warn("A non-default handler for signal %d is already installed (%s). " "Skipping pudb interrupt support." % (interrupt_signal, old_handler)) import threading if not isinstance(threading.current_thread(), threading._MainThread): from warnings import warn # Setting signals from a non-main thread will not work return warn("Setting the interrupt handler can only be done on the main " "thread. The interrupt handler was NOT installed.") try: signal.signal(interrupt_signal, _interrupt_handler) except ValueError: from pudb.lowlevel import format_exception import sys from warnings import warn warn("setting interrupt handler on signal %d failed: %s" % (interrupt_signal, "".join(format_exception(sys.exc_info())))) def post_mortem(tb=None, e_type=None, e_value=None): if tb is None: import sys exc_info = sys.exc_info() else: exc_info = (e_type, e_value, tb) tb = exc_info[2] while tb.tb_next is not None: tb = tb.tb_next dbg = _get_debugger() dbg.reset() dbg.interaction(tb.tb_frame, exc_info) def pm(): import sys try: e_type = sys.last_type e_value = sys.last_value tb = sys.last_traceback except AttributeError: # No exception on record. Do nothing. return post_mortem(tb, e_type, e_value) if __name__ == "__main__": print("You now need to type 'python -m pudb.run'. Sorry.") # vim: foldmethod=marker:expandtab:softtabstop=4 pudb-2017.1.4/pudb/__main__.py000066400000000000000000000002071315257663400157370ustar00rootroot00000000000000 from __future__ import absolute_import, division, print_function if __name__ == "__main__": from pudb.run import main main() pudb-2017.1.4/pudb/b.py000066400000000000000000000010531315257663400144400ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function import sys from pudb import _get_debugger, set_interrupt_handler def __myimport__(name, *args, **kwargs): # noqa: N802 if name == 'pudb.b': set_trace() return __origimport__(name, *args, **kwargs) # noqa: F821 # Will only be run on first import __builtins__['__origimport__'] = __import__ __builtins__['__import__'] = __myimport__ def set_trace(): dbg = _get_debugger() set_interrupt_handler() dbg.set_trace(sys._getframe().f_back.f_back) set_trace() pudb-2017.1.4/pudb/debugger.py000066400000000000000000002545441315257663400160220ustar00rootroot00000000000000#! /usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function import urwid import bdb import gc import os import sys from functools import partial from types import TracebackType from pudb.lowlevel import decode_lines from pudb.settings import load_config, save_config from pudb.py3compat import PY3, raw_input, execfile CONFIG = load_config() save_config(CONFIG) HELP_TEXT = """\ Welcome to PuDB, the Python Urwid debugger. ------------------------------------------- (This help screen is scrollable. Hit Page Down to see more.) Keys: Ctrl-p - edit preferences n - step over ("next") s - step into c - continue r/f - finish current function t - run to cursor e - show traceback [post-mortem or in exception state] H - move to current line (bottom of stack) u - move up one stack frame d - move down one stack frame o - show console/output screen b - toggle breakpoint m - open module j/k - up/down Ctrl-u/d - page up/down h/l - scroll left/right g/G - start/end L - show (file/line) location / go to line / - search ,/. - search next/previous V - focus variables S - focus stack B - focus breakpoint list C - focus code f1/?/H - show this help screen q - quit Ctrl-c - when in continue mode, break back to PuDB Ctrl-l - redraw screen Shell-related: ! - open the external shell (configured in the settings) Ctrl-x - toggle the internal shell focus +/- - grow/shrink inline shell (active in command line history) _/= - minimize/maximize inline shell (active in command line history) Ctrl-v - insert newline Ctrl-n/p - browse command line history Tab - yes, there is (simple) tab completion Sidebar-related (active in sidebar): +/- - grow/shrink sidebar _/= - minimize/maximize sidebar [/] - grow/shrink relative size of active sidebar box Keys in variables list: \ - expand/collapse t/r/s/c - show type/repr/str/custom for this variable h - toggle highlighting @ - toggle repetition at top * - cycle attribute visibility: public/_private/__dunder__ m - toggle method visibility w - toggle line wrapping n/insert - add new watch expression enter - edit options (also to delete) Keys in stack list: enter - jump to frame Keys in breakpoints view: enter - edit breakpoint d - delete breakpoint e - enable/disable breakpoint License: -------- PuDB is licensed to you under the MIT/X Consortium license: Copyright (c) 2009-16 Andreas Kloeckner and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ # {{{ debugger interface class Debugger(bdb.Bdb): def __init__(self, stdin=None, stdout=None, term_size=None, steal_output=False): bdb.Bdb.__init__(self) self.ui = DebuggerUI(self, stdin=stdin, stdout=stdout, term_size=term_size) self.steal_output = steal_output self.setup_state() if steal_output: raise NotImplementedError("output stealing") if PY3: from io import StringIO else: from cStringIO import StringIO self.stolen_output = sys.stderr = sys.stdout = StringIO() sys.stdin = StringIO("") # avoid spurious hangs from pudb.settings import load_breakpoints for bpoint_descr in load_breakpoints(): self.set_break(*bpoint_descr) # These (dispatch_line and set_continue) are copied from bdb with the # patch from https://bugs.python.org/issue16482 applied. See # https://github.com/inducer/pudb/pull/90. def dispatch_line(self, frame): if self.stop_here(frame) or self.break_here(frame): self.user_line(frame) if self.quitting: raise bdb.BdbQuit # Do not re-install the local trace when we are finished debugging, # see issues 16482 and 7238. if not sys.gettrace(): return None return self.trace_dispatch def set_continue(self): # Don't stop except at breakpoints or when finished self._set_stopinfo(self.botframe, None, -1) if not self.breaks: # no breakpoints; run without debugger overhead sys.settrace(None) frame = sys._getframe().f_back while frame: del frame.f_trace if frame is self.botframe: break frame = frame.f_back def set_trace(self, frame=None, as_breakpoint=None, paused=True): """Start debugging from `frame`. If frame is not specified, debugging starts from caller's frame. Unlike Bdb.set_trace(), this does not call self.reset(), which causes the debugger to enter bdb source code. This also implements treating set_trace() calls as breakpoints in the PuDB UI. If as_breakpoint=True (the default), this call will be treated like a breakpoint in the UI (you can press 'b' on it to disable breaking here). If paused=False, the debugger will not break here. """ if as_breakpoint is None: if not paused: as_breakpoint = False else: as_breakpoint = True if frame is None: frame = thisframe = sys._getframe().f_back else: thisframe = frame # See pudb issue #52. If this works well enough we should upstream to # stdlib bdb.py. #self.reset() while frame: frame.f_trace = self.trace_dispatch self.botframe = frame frame = frame.f_back thisframe_info = ( self.canonic(thisframe.f_code.co_filename), thisframe.f_lineno) if thisframe_info not in self.set_traces or self.set_traces[thisframe_info]: if as_breakpoint: self.set_traces[thisframe_info] = True if self.ui.source_code_provider is not None: self.ui.set_source_code_provider( self.ui.source_code_provider, force_update=True) if paused: self.set_step() else: self.set_continue() sys.settrace(self.trace_dispatch) else: return def save_breakpoints(self): from pudb.settings import save_breakpoints save_breakpoints([ bp for fn, bp_lst in self.get_all_breaks().items() for lineno in bp_lst for bp in self.get_breaks(fn, lineno) if not bp.temporary]) def enter_post_mortem(self, exc_tuple): self.post_mortem = True def setup_state(self): self.bottom_frame = None self.mainpyfile = '' self._wait_for_mainpyfile = False self.current_bp = None self.post_mortem = False # Mapping of (filename, lineno) to bool. If True, will stop on the # set_trace() call at that location. self.set_traces = {} def restart(self): from linecache import checkcache checkcache() self.ui.set_source_code_provider(NullSourceCodeProvider()) self.setup_state() def do_clear(self, arg): self.clear_bpbynumber(int(arg)) def set_frame_index(self, index): self.curindex = index if index < 0 or index >= len(self.stack): return self.curframe, lineno = self.stack[index] filename = self.curframe.f_code.co_filename import linecache if not linecache.getlines(filename): code = self.curframe.f_globals.get("_MODULE_SOURCE_CODE") if code is not None: self.ui.set_current_line(lineno, DirectSourceCodeProvider( self.curframe.f_code.co_name, code)) else: self.ui.set_current_line(lineno, NullSourceCodeProvider()) else: self.ui.set_current_line(lineno, FileSourceCodeProvider(self, filename)) self.ui.update_var_view() self.ui.update_stack() self.ui.stack_list._w.set_focus(self.ui.translate_ui_stack_index(index)) def move_up_frame(self): if self.curindex > 0: self.set_frame_index(self.curindex-1) def move_down_frame(self): if self.curindex < len(self.stack)-1: self.set_frame_index(self.curindex+1) def get_shortened_stack(self, frame, tb): stack, index = self.get_stack(frame, tb) for i, (s_frame, lineno) in enumerate(stack): if s_frame is self.bottom_frame and index >= i: stack = stack[i:] index -= i return stack, index def interaction(self, frame, exc_tuple=None, show_exc_dialog=True): if exc_tuple is None: tb = None elif isinstance(exc_tuple, TracebackType): # For API compatibility with other debuggers, the second variable # can be a traceback object. In that case, we need to retrieve the # corresponding exception tuple. tb = exc_tuple exc, = (exc for exc in gc.get_referrers(tb) if getattr(exc, "__traceback__", None) is tb) exc_tuple = type(exc), exc, tb else: tb = exc_tuple[2] if frame is None and tb is not None: frame = tb.tb_frame found_bottom_frame = False walk_frame = frame while True: if walk_frame is self.bottom_frame: found_bottom_frame = True break if walk_frame is None: break walk_frame = walk_frame.f_back if not found_bottom_frame and not self.post_mortem: return self.stack, index = self.get_shortened_stack(frame, tb) if self.post_mortem: index = len(self.stack)-1 self.set_frame_index(index) self.ui.call_with_ui(self.ui.interaction, exc_tuple, show_exc_dialog=show_exc_dialog) def get_stack_situation_id(self): return str(id(self.stack[self.curindex][0].f_code)) def user_call(self, frame, argument_list): """This method is called when there is the remote possibility that we ever need to stop in this function.""" if self._wait_for_mainpyfile: return if self.stop_here(frame): self.interaction(frame) def user_line(self, frame): """This function is called when we stop or break at this line.""" if "__exc_tuple__" in frame.f_locals: del frame.f_locals['__exc_tuple__'] if self._wait_for_mainpyfile: if (self.mainpyfile != self.canonic(frame.f_code.co_filename) or frame.f_lineno <= 0): return self._wait_for_mainpyfile = False self.bottom_frame = frame if self.get_break(self.canonic(frame.f_code.co_filename), frame.f_lineno): self.current_bp = ( self.canonic(frame.f_code.co_filename), frame.f_lineno) else: self.current_bp = None self.ui.update_breakpoints() self.interaction(frame) def user_return(self, frame, return_value): """This function is called when a return trap is set here.""" if frame.f_code.co_name == '': return frame.f_locals['__return__'] = return_value if self._wait_for_mainpyfile: if (self.mainpyfile != self.canonic(frame.f_code.co_filename) or frame.f_lineno <= 0): return self._wait_for_mainpyfile = False self.bottom_frame = frame if "__exc_tuple__" not in frame.f_locals: self.interaction(frame) def user_exception(self, frame, exc_tuple): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" frame.f_locals['__exc_tuple__'] = exc_tuple if not self._wait_for_mainpyfile: self.interaction(frame, exc_tuple) def _runscript(self, filename): # 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. globals_ = {"__name__": "__main__", "__file__": filename} locals_ = globals_ # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 self.mainpyfile = self.canonic(filename) if PY3: statement = 'exec(compile(open("%s").read(), "%s", "exec"))' % ( filename, filename) else: statement = 'execfile( "%s")' % filename # Set up an interrupt handler from pudb import set_interrupt_handler set_interrupt_handler() self.run(statement, globals=globals_, locals=locals_) # }}} # UI stuff -------------------------------------------------------------------- from pudb.ui_tools import make_hotkey_markup, labelled_value, \ SelectableText, SignalWrap, StackFrame, BreakpointFrame from pudb.var_view import FrameVarInfoKeeper # {{{ display setup try: import curses except ImportError: curses = None from urwid.raw_display import Screen as RawScreen try: from urwid.curses_display import Screen as CursesScreen except ImportError: CursesScreen = None class ThreadsafeScreenMixin(object): "A Screen subclass that doesn't crash when running from a non-main thread." def signal_init(self): "Initialize signal handler, ignoring errors silently." try: super(ThreadsafeScreenMixin, self).signal_init() except ValueError: pass def signal_restore(self): "Restore default signal handler, ignoring errors silently." try: super(ThreadsafeScreenMixin, self).signal_restore() except ValueError: pass class ThreadsafeRawScreen(ThreadsafeScreenMixin, RawScreen): pass class ThreadsafeFixedSizeRawScreen(ThreadsafeScreenMixin, RawScreen): def __init__(self, **kwargs): self._term_size = kwargs.pop("term_size", None) super(ThreadsafeFixedSizeRawScreen, self).__init__(**kwargs) def get_cols_rows(self): if self._term_size is not None: return self._term_size else: return 80, 24 if curses is not None: class ThreadsafeCursesScreen(ThreadsafeScreenMixin, RawScreen): pass # }}} # {{{ source code providers class SourceCodeProvider(object): def __ne__(self, other): return not (self == other) class NullSourceCodeProvider(SourceCodeProvider): def __eq__(self, other): return type(self) == type(other) def identifier(self): return "" def get_breakpoint_source_identifier(self): return None def clear_cache(self): pass def get_lines(self, debugger_ui): from pudb.source_view import SourceLine return [ SourceLine(debugger_ui, ""), SourceLine(debugger_ui, ""), SourceLine(debugger_ui, "If this is generated code and you would " "like the source code to show up here,"), SourceLine(debugger_ui, "add it to linecache.cache, like"), SourceLine(debugger_ui, ""), SourceLine(debugger_ui, " import linecache"), SourceLine(debugger_ui, " linecache.cache[filename] = " "(size, mtime, lines, fullname)"), SourceLine(debugger_ui, ""), SourceLine(debugger_ui, "You can also set the attribute " "_MODULE_SOURCE_CODE in the module in which this function"), SourceLine(debugger_ui, "was compiled to a string containing " "the code."), ] class FileSourceCodeProvider(SourceCodeProvider): def __init__(self, debugger, file_name): self.file_name = debugger.canonic(file_name) def __eq__(self, other): return type(self) == type(other) and self.file_name == other.file_name def identifier(self): return self.file_name def get_breakpoint_source_identifier(self): return self.file_name def clear_cache(self): from linecache import clearcache clearcache() def get_lines(self, debugger_ui): from pudb.source_view import SourceLine, format_source if self.file_name == "": return [SourceLine(debugger_ui, self.file_name)] breakpoints = debugger_ui.debugger.get_file_breaks(self.file_name)[:] breakpoints = [lineno for lineno in breakpoints if any(bp.enabled for bp in debugger_ui.debugger.get_breaks(self.file_name, lineno))] breakpoints += [i for f, i in debugger_ui.debugger.set_traces if f == self.file_name and debugger_ui.debugger.set_traces[f, i]] try: from linecache import getlines lines = getlines(self.file_name) return format_source( debugger_ui, list(decode_lines(lines)), set(breakpoints)) except: from pudb.lowlevel import format_exception debugger_ui.message("Could not load source file '%s':\n\n%s" % ( self.file_name, "".join(format_exception(sys.exc_info()))), title="Source Code Load Error") return [SourceLine(debugger_ui, "Error while loading '%s'." % self.file_name)] class DirectSourceCodeProvider(SourceCodeProvider): def __init__(self, func_name, code): self.function_name = func_name self.code = code def __eq__(self, other): return ( type(self) == type(other) and self.function_name == other.function_name and self.code is other.code) def identifier(self): return "" % self.function_name def get_breakpoint_source_identifier(self): return None def clear_cache(self): pass def get_lines(self, debugger_ui): from pudb.source_view import format_source lines = self.code.splitlines(True) return format_source(debugger_ui, list(decode_lines(lines)), set()) # }}} class DebuggerUI(FrameVarInfoKeeper): # {{{ constructor def __init__(self, dbg, stdin, stdout, term_size): FrameVarInfoKeeper.__init__(self) self.debugger = dbg from urwid import AttrMap from pudb.ui_tools import SearchController self.search_controller = SearchController(self) self.last_module_filter = "" # {{{ build ui # {{{ left/source column self.source = urwid.SimpleListWalker([]) self.source_list = urwid.ListBox(self.source) self.source_sigwrap = SignalWrap(self.source_list) self.source_attr = urwid.AttrMap(self.source_sigwrap, "source") self.source_hscroll_start = 0 self.cmdline_history = [] self.cmdline_history_position = -1 self.cmdline_contents = urwid.SimpleFocusListWalker([]) self.cmdline_list = urwid.ListBox(self.cmdline_contents) self.cmdline_edit = urwid.Edit([ ("command line prompt", ">>> ") ]) cmdline_edit_attr = urwid.AttrMap(self.cmdline_edit, "command line edit") self.cmdline_edit_sigwrap = SignalWrap( cmdline_edit_attr, is_preemptive=True) def clear_cmdline_history(btn): del self.cmdline_contents[:] self.cmdline_edit_bar = urwid.Columns([ self.cmdline_edit_sigwrap, ("fixed", 10, AttrMap( urwid.Button("Clear", clear_cmdline_history), "command line clear button", "command line focused button")) ]) self.cmdline_pile = urwid.Pile([ ("flow", urwid.Text("Command line: [Ctrl-X]")), ("weight", 1, urwid.AttrMap(self.cmdline_list, "command line output")), ("flow", self.cmdline_edit_bar), ]) self.cmdline_sigwrap = SignalWrap( urwid.AttrMap(self.cmdline_pile, None, "focused sidebar") ) self.lhs_col = urwid.Pile([ ("weight", 5, self.source_attr), ("weight", 1, self.cmdline_sigwrap), ]) # }}} # {{{ right column self.locals = urwid.SimpleListWalker([]) self.var_list = SignalWrap( urwid.ListBox(self.locals)) self.stack_walker = urwid.SimpleListWalker([]) self.stack_list = SignalWrap( urwid.ListBox(self.stack_walker)) self.bp_walker = urwid.SimpleListWalker([]) self.bp_list = SignalWrap( urwid.ListBox(self.bp_walker)) self.rhs_col = urwid.Pile([ ("weight", float(CONFIG["variables_weight"]), AttrMap(urwid.Pile([ ("flow", urwid.Text(make_hotkey_markup("_Variables:"))), AttrMap(self.var_list, "variables"), ]), None, "focused sidebar"),), ("weight", float(CONFIG["stack_weight"]), AttrMap(urwid.Pile([ ("flow", urwid.Text(make_hotkey_markup("_Stack:"))), AttrMap(self.stack_list, "stack"), ]), None, "focused sidebar"),), ("weight", float(CONFIG["breakpoints_weight"]), AttrMap(urwid.Pile([ ("flow", urwid.Text(make_hotkey_markup("_Breakpoints:"))), AttrMap(self.bp_list, "breakpoint"), ]), None, "focused sidebar"),), ]) self.rhs_col_sigwrap = SignalWrap(self.rhs_col) # }}} self.columns = urwid.Columns( [ ("weight", 1, self.lhs_col), ("weight", float(CONFIG["sidebar_width"]), self.rhs_col_sigwrap), ], dividechars=1) self.caption = urwid.Text("") header = urwid.AttrMap(self.caption, "header") self.top = SignalWrap(urwid.Frame( urwid.AttrMap(self.columns, "background"), header)) # }}} def change_rhs_box(name, index, direction, w, size, key): from pudb.settings import save_config _, weight = self.rhs_col.item_types[index] if direction < 0: if weight > 1/5: weight /= 1.25 else: if weight < 5: weight *= 1.25 CONFIG[name+"_weight"] = weight save_config(CONFIG) self.rhs_col.item_types[index] = "weight", weight self.rhs_col._invalidate() # {{{ variables listeners def change_var_state(w, size, key): var, pos = self.var_list._w.get_focus() iinfo = self.get_frame_var_info(read_only=False) \ .get_inspect_info(var.id_path, read_only=False) if key == "\\": iinfo.show_detail = not iinfo.show_detail elif key == "t": iinfo.display_type = "type" elif key == "r": iinfo.display_type = "repr" elif key == "s": iinfo.display_type = "str" elif key == "c": iinfo.display_type = CONFIG["custom_stringifier"] elif key == "h": iinfo.highlighted = not iinfo.highlighted elif key == "@": iinfo.repeated_at_top = not iinfo.repeated_at_top elif key == "*": levels = ["public", "private", "all", "public"] iinfo.access_level = levels[levels.index(iinfo.access_level)+1] elif key == "w": iinfo.wrap = not iinfo.wrap elif key == "m": iinfo.show_methods = not iinfo.show_methods self.update_var_view() def edit_inspector_detail(w, size, key): var, pos = self.var_list._w.get_focus() if var is None: return fvi = self.get_frame_var_info(read_only=False) iinfo = fvi.get_inspect_info(var.id_path, read_only=False) buttons = [ ("OK", True), ("Cancel", False), ] if var.watch_expr is not None: watch_edit = urwid.Edit([ ("label", "Watch expression: ") ], var.watch_expr.expression) id_segment = [urwid.AttrMap(watch_edit, "value"), urwid.Text("")] buttons.extend([None, ("Delete", "del")]) title = "Watch Expression Options" else: id_segment = [ labelled_value("Identifier Path: ", var.id_path), urwid.Text(""), ] title = "Variable Inspection Options" rb_grp_show = [] rb_show_type = urwid.RadioButton(rb_grp_show, "Show Type", iinfo.display_type == "type") rb_show_repr = urwid.RadioButton(rb_grp_show, "Show repr()", iinfo.display_type == "repr") rb_show_str = urwid.RadioButton(rb_grp_show, "Show str()", iinfo.display_type == "str") rb_show_custom = urwid.RadioButton( rb_grp_show, "Show custom (set in prefs)", iinfo.display_type == CONFIG["custom_stringifier"]) rb_grp_access = [] rb_access_public = urwid.RadioButton(rb_grp_access, "Public members", iinfo.access_level == "public") rb_access_private = urwid.RadioButton( rb_grp_access, "Public and private members", iinfo.access_level == "private") rb_access_all = urwid.RadioButton( rb_grp_access, "All members (including __dunder__)", iinfo.access_level == "all") wrap_checkbox = urwid.CheckBox("Line Wrap", iinfo.wrap) expanded_checkbox = urwid.CheckBox("Expanded", iinfo.show_detail) highlighted_checkbox = urwid.CheckBox("Highlighted", iinfo.highlighted) repeated_at_top_checkbox = urwid.CheckBox( "Repeated at top", iinfo.repeated_at_top) show_methods_checkbox = urwid.CheckBox( "Show methods", iinfo.show_methods) lb = urwid.ListBox(urwid.SimpleListWalker( id_segment + rb_grp_show + [urwid.Text("")] + rb_grp_access + [urwid.Text("")] + [ wrap_checkbox, expanded_checkbox, highlighted_checkbox, repeated_at_top_checkbox, show_methods_checkbox, ])) result = self.dialog(lb, buttons, title=title) if result is True: iinfo.show_detail = expanded_checkbox.get_state() iinfo.wrap = wrap_checkbox.get_state() iinfo.highlighted = highlighted_checkbox.get_state() iinfo.repeated_at_top = repeated_at_top_checkbox.get_state() iinfo.show_methods = show_methods_checkbox.get_state() if rb_show_type.get_state(): iinfo.display_type = "type" elif rb_show_repr.get_state(): iinfo.display_type = "repr" elif rb_show_str.get_state(): iinfo.display_type = "str" elif rb_show_custom.get_state(): iinfo.display_type = CONFIG["custom_stringifier"] if rb_access_public.get_state(): iinfo.access_level = "public" elif rb_access_private.get_state(): iinfo.access_level = "private" elif rb_access_all.get_state(): iinfo.access_level = "all" if var.watch_expr is not None: var.watch_expr.expression = watch_edit.get_edit_text() elif result == "del": for i, watch_expr in enumerate(fvi.watches): if watch_expr is var.watch_expr: del fvi.watches[i] self.update_var_view() def insert_watch(w, size, key): watch_edit = urwid.Edit([ ("label", "Watch expression: ") ]) if self.dialog( urwid.ListBox(urwid.SimpleListWalker([ urwid.AttrMap(watch_edit, "value") ])), [ ("OK", True), ("Cancel", False), ], title="Add Watch Expression"): from pudb.var_view import WatchExpression we = WatchExpression(watch_edit.get_edit_text()) fvi = self.get_frame_var_info(read_only=False) fvi.watches.append(we) self.update_var_view() self.var_list.listen("\\", change_var_state) self.var_list.listen("t", change_var_state) self.var_list.listen("r", change_var_state) self.var_list.listen("s", change_var_state) self.var_list.listen("c", change_var_state) self.var_list.listen("h", change_var_state) self.var_list.listen("@", change_var_state) self.var_list.listen("*", change_var_state) self.var_list.listen("w", change_var_state) self.var_list.listen("m", change_var_state) self.var_list.listen("enter", edit_inspector_detail) self.var_list.listen("n", insert_watch) self.var_list.listen("insert", insert_watch) self.var_list.listen("[", partial(change_rhs_box, 'variables', 0, -1)) self.var_list.listen("]", partial(change_rhs_box, 'variables', 0, 1)) # }}} # {{{ stack listeners def examine_frame(w, size, key): _, pos = self.stack_list._w.get_focus() self.debugger.set_frame_index(self.translate_ui_stack_index(pos)) self.stack_list.listen("enter", examine_frame) def move_stack_top(w, size, key): self.debugger.set_frame_index(len(self.debugger.stack)-1) def move_stack_up(w, size, key): self.debugger.move_up_frame() def move_stack_down(w, size, key): self.debugger.move_down_frame() self.stack_list.listen("H", move_stack_top) self.stack_list.listen("u", move_stack_up) self.stack_list.listen("d", move_stack_down) self.stack_list.listen("[", partial(change_rhs_box, 'stack', 1, -1)) self.stack_list.listen("]", partial(change_rhs_box, 'stack', 1, 1)) # }}} # {{{ breakpoint listeners def save_breakpoints(w, size, key): self.debugger.save_breakpoints() def delete_breakpoint(w, size, key): bp_source_identifier = \ self.source_code_provider.get_breakpoint_source_identifier() if bp_source_identifier is None: self.message( "Cannot currently delete a breakpoint here--" "source code does not correspond to a file location. " "(perhaps this is generated code)") bp_list = self._get_bp_list() if bp_list: _, pos = self.bp_list._w.get_focus() bp = bp_list[pos] if bp_source_identifier == bp.file and bp.line-1 < len(self.source): self.source[bp.line-1].set_breakpoint(False) err = self.debugger.clear_break(bp.file, bp.line) if err: self.message("Error clearing breakpoint:\n" + err) else: self.update_breakpoints() def enable_disable_breakpoint(w, size, key): bp_entry, pos = self.bp_list._w.get_focus() if bp_entry is None: return bp = self._get_bp_list()[pos] bp.enabled = not bp.enabled sline = self.source[bp.line-1] sline.set_breakpoint(bp.enabled) self.update_breakpoints() def examine_breakpoint(w, size, key): bp_entry, pos = self.bp_list._w.get_focus() if bp_entry is None: return bp = self._get_bp_list()[pos] 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) ign_count_edit = urwid.IntEdit([ ("label", "Ignore the next N times: ") ], bp.ignore) lb = urwid.ListBox(urwid.SimpleListWalker([ labelled_value("File: ", bp.file), labelled_value("Line: ", bp.line), labelled_value("Hits: ", bp.hits), urwid.Text(""), enabled_checkbox, urwid.AttrMap(cond_edit, "value", "value"), urwid.AttrMap(ign_count_edit, "value", "value"), ])) result = self.dialog(lb, [ ("OK", True), ("Cancel", False), None, ("Delete", "del"), ("Location", "loc"), ], title="Edit Breakpoint") if result is True: bp.enabled = enabled_checkbox.get_state() bp.ignore = int(ign_count_edit.value()) cond = cond_edit.get_edit_text() if cond: bp.cond = cond else: bp.cond = None elif result == "loc": self.show_line(bp.line, FileSourceCodeProvider(self.debugger, bp.file)) self.columns.set_focus(0) elif result == "del": bp_source_identifier = \ self.source_code_provider.get_breakpoint_source_identifier() if bp_source_identifier is None: self.message( "Cannot currently delete a breakpoint here--" "source code does not correspond to a file location. " "(perhaps this is generated code)") if bp_source_identifier == bp.file: self.source[bp.line-1].set_breakpoint(False) err = self.debugger.clear_break(bp.file, bp.line) if err: self.message("Error clearing breakpoint:\n" + err) else: self.update_breakpoints() self.bp_list.listen("enter", examine_breakpoint) self.bp_list.listen("d", delete_breakpoint) self.bp_list.listen("s", save_breakpoints) self.bp_list.listen("e", enable_disable_breakpoint) self.bp_list.listen("[", partial(change_rhs_box, 'breakpoints', 2, -1)) self.bp_list.listen("]", partial(change_rhs_box, 'breakpoints', 2, 1)) # }}} # {{{ source listeners def end(): self.debugger.save_breakpoints() self.quit_event_loop = True def next(w, size, key): 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): 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() lineno = pos+1 bp_source_identifier = \ self.source_code_provider.get_breakpoint_source_identifier() if bp_source_identifier is None: self.message( "Cannot currently set a breakpoint here--" "source code does not correspond to a file location. " "(perhaps this is generated code)") from pudb.lowlevel import get_breakpoint_invalid_reason invalid_reason = get_breakpoint_invalid_reason( bp_source_identifier, lineno) if invalid_reason is not None: self.message( "Cannot run to the line you indicated, " "for the following reason:\n\n" + invalid_reason) else: err = self.debugger.set_break( bp_source_identifier, pos+1, temporary=True) if err: self.message("Error dealing with breakpoint:\n" + err) self.debugger.set_continue() end() def move_home(w, size, key): self.source.set_focus(0) def move_end(w, size, key): self.source.set_focus(len(self.source)-1) def go_to_line(w, size, key): _, line = self.source.get_focus() lineno_edit = urwid.IntEdit([ ("label", "Line number: ") ], line+1) if self.dialog( urwid.ListBox(urwid.SimpleListWalker([ labelled_value("File :", self.source_code_provider.identifier()), urwid.AttrMap(lineno_edit, "value") ])), [ ("OK", True), ("Cancel", False), ], title="Go to Line Number"): lineno = min(max(0, int(lineno_edit.value())-1), len(self.source)-1) self.source.set_focus(lineno) def move_down(w, size, key): w.keypress(size, "down") def move_up(w, size, key): w.keypress(size, "up") def page_down(w, size, key): w.keypress(size, "page down") def page_up(w, size, key): w.keypress(size, "page up") def scroll_left(w, size, key): self.source_hscroll_start = max( 0, self.source_hscroll_start - 4) for sl in self.source: sl._invalidate() def scroll_right(w, size, key): self.source_hscroll_start += 4 for sl in self.source: sl._invalidate() def search(w, size, key): self.search_controller.open_search_ui() def search_next(w, size, key): self.search_controller.perform_search(dir=1, update_search_start=True) def search_previous(w, size, key): self.search_controller.perform_search(dir=-1, update_search_start=True) def toggle_breakpoint(w, size, key): bp_source_identifier = \ self.source_code_provider.get_breakpoint_source_identifier() if bp_source_identifier: sline, pos = self.source.get_focus() lineno = pos+1 existing_breaks = self.debugger.get_breaks( bp_source_identifier, lineno) if existing_breaks: err = None for bp in existing_breaks: if not bp.enabled: bp.enable() sline.set_breakpoint(True) # Unsure about this. Are multiple breakpoints even # possible? break else: err = self.debugger.clear_break(bp_source_identifier, lineno) sline.set_breakpoint(False) else: file_lineno = (bp_source_identifier, lineno) if file_lineno in self.debugger.set_traces: self.debugger.set_traces[file_lineno] = \ not self.debugger.set_traces[file_lineno] sline.set_breakpoint(self.debugger.set_traces[file_lineno]) return from pudb.lowlevel import get_breakpoint_invalid_reason invalid_reason = get_breakpoint_invalid_reason( bp_source_identifier, pos+1) if invalid_reason is not None: do_set = not self.dialog( urwid.ListBox( urwid.SimpleListWalker([ urwid.Text( "The breakpoint you just set may be " "invalid, for the following reason:\n\n" + invalid_reason), ])), [ ("Cancel", True), ("Set Anyway", False), ], title="Possibly Invalid Breakpoint", focus_buttons=True) else: do_set = True if do_set: err = self.debugger.set_break(bp_source_identifier, pos+1) sline.set_breakpoint(True) else: err = None if err: self.message("Error dealing with breakpoint:\n" + err) self.update_breakpoints() else: self.message( "Cannot currently set a breakpoint here--" "source code does not correspond to a file location. " "(perhaps this is generated code)") def pick_module(w, size, key): from os.path import splitext import sys def mod_exists(mod): if not hasattr(mod, "__file__"): return False filename = mod.__file__ base, ext = splitext(filename) ext = ext.lower() from os.path import exists if ext == ".pyc": return exists(base+".py") else: return ext == ".py" new_mod_text = SelectableText("-- update me --") new_mod_entry = urwid.AttrMap(new_mod_text, None, "focused selectable") def build_filtered_mod_list(filt_string=""): modules = sorted(name # mod_exists may change the size of sys.modules, # causing this to crash. Copy to a list. for name, mod in list(sys.modules.items()) if mod_exists(mod)) result = [urwid.AttrMap(SelectableText(mod), None, "focused selectable") for mod in modules if filt_string in mod] new_mod_text.set_text("<<< IMPORT MODULE '%s' >>>" % filt_string) result.append(new_mod_entry) return result def show_mod(mod): filename = self.debugger.canonic(mod.__file__) base, ext = splitext(filename) if ext == ".pyc": ext = ".py" filename = base+".py" self.set_source_code_provider( FileSourceCodeProvider(self.debugger, filename)) self.source_list.set_focus(0) 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 filt_edit = FilterEdit([("label", "Filter: ")], self.last_module_filter) mod_list = urwid.SimpleListWalker( build_filtered_mod_list(filt_edit.get_edit_text())) lb = urwid.ListBox(mod_list) w = urwid.Pile([ ("flow", urwid.AttrMap(filt_edit, "value")), ("fixed", 1, urwid.SolidFill()), urwid.AttrMap(lb, "selectable")]) while True: result = self.dialog(w, [ ("OK", True), ("Cancel", False), ("Reload", "reload"), ], title="Pick Module") self.last_module_filter = filt_edit.get_edit_text() if result is True: widget, pos = lb.get_focus() if widget is new_mod_entry: new_mod_name = filt_edit.get_edit_text() try: __import__(str(new_mod_name)) except: from pudb.lowlevel import format_exception self.message("Could not import module '%s':\n\n%s" % ( new_mod_name, "".join( format_exception(sys.exc_info()))), title="Import Error") else: show_mod(__import__(str(new_mod_name))) break else: show_mod(sys.modules[widget.base_widget.get_text()[0]]) break elif result is False: break elif result == "reload": widget, pos = lb.get_focus() if widget is not new_mod_entry: mod_name = widget.base_widget.get_text()[0] mod = sys.modules[mod_name] if PY3: import importlib importlib.reload(mod) else: reload(mod) # noqa (undef on Py3) self.message("'%s' was successfully reloaded." % mod_name) if self.source_code_provider is not None: self.source_code_provider.clear_cache() self.set_source_code_provider(self.source_code_provider, force_update=True) _, pos = self.stack_list._w.get_focus() self.debugger.set_frame_index( self.translate_ui_stack_index(pos)) self.source_sigwrap.listen("n", next) self.source_sigwrap.listen("s", step) self.source_sigwrap.listen("f", finish) self.source_sigwrap.listen("r", finish) self.source_sigwrap.listen("c", cont) self.source_sigwrap.listen("t", run_to_cursor) self.source_sigwrap.listen("j", move_down) self.source_sigwrap.listen("k", move_up) self.source_sigwrap.listen("ctrl d", page_down) self.source_sigwrap.listen("ctrl u", page_up) self.source_sigwrap.listen("ctrl f", page_down) self.source_sigwrap.listen("ctrl b", page_up) self.source_sigwrap.listen("h", scroll_left) self.source_sigwrap.listen("l", scroll_right) self.source_sigwrap.listen("/", search) self.source_sigwrap.listen(",", search_previous) self.source_sigwrap.listen(".", search_next) self.source_sigwrap.listen("home", move_home) self.source_sigwrap.listen("end", move_end) self.source_sigwrap.listen("g", move_home) self.source_sigwrap.listen("G", move_end) self.source_sigwrap.listen("L", go_to_line) self.source_sigwrap.listen("b", toggle_breakpoint) self.source_sigwrap.listen("m", pick_module) self.source_sigwrap.listen("H", move_stack_top) self.source_sigwrap.listen("u", move_stack_up) self.source_sigwrap.listen("d", move_stack_down) # }}} # {{{ command line listeners def cmdline_get_namespace(): curframe = self.debugger.curframe from pudb.shell import SetPropagatingDict return SetPropagatingDict( [curframe.f_locals, curframe.f_globals], curframe.f_locals) def add_cmdline_content(s, attr): s = s.rstrip("\n") from pudb.ui_tools import SelectableText self.cmdline_contents.append( urwid.AttrMap(SelectableText(s), attr, "focused "+attr)) # scroll to end of last entry self.cmdline_list.set_focus_valign("bottom") self.cmdline_list.set_focus(len(self.cmdline_contents) - 1, coming_from="above") def cmdline_tab_complete(w, size, key): from rlcompleter import Completer text = self.cmdline_edit.edit_text pos = self.cmdline_edit.edit_pos chopped_text = text[:pos] suffix = text[pos:] # stolen from readline in the Python interactive shell delimiters = " \t\n`~!@#$%^&*()-=+[{]}\\|;:\'\",<>/?" complete_start_index = max( chopped_text.rfind(delim_i) for delim_i in delimiters) if complete_start_index == -1: prefix = "" else: prefix = chopped_text[:complete_start_index+1] chopped_text = chopped_text[complete_start_index+1:] state = 0 chopped_completions = [] completer = Completer(cmdline_get_namespace()) while True: completion = completer.complete(chopped_text, state) if not isinstance(completion, str): break chopped_completions.append(completion) state += 1 def common_prefix(a, b): for i, (a_i, b_i) in enumerate(zip(a, b)): if a_i != b_i: return a[:i] return a[:max(len(a), len(b))] common_compl_prefix = None for completion in chopped_completions: if common_compl_prefix is None: common_compl_prefix = completion else: common_compl_prefix = common_prefix( common_compl_prefix, completion) completed_chopped_text = common_compl_prefix if completed_chopped_text is None: return if ( len(completed_chopped_text) == len(chopped_text) and len(chopped_completions) > 1): add_cmdline_content( " ".join(chopped_completions), "command line output") return self.cmdline_edit.edit_text = \ prefix+completed_chopped_text+suffix self.cmdline_edit.edit_pos = len(prefix) + len(completed_chopped_text) def cmdline_append_newline(w, size, key): self.cmdline_edit.insert_text("\n") def cmdline_exec(w, size, key): cmd = self.cmdline_edit.get_edit_text() if not cmd: # blank command -> refuse service return add_cmdline_content(">>> " + cmd, "command line input") if not self.cmdline_history or cmd != self.cmdline_history[-1]: self.cmdline_history.append(cmd) self.cmdline_history_position = -1 prev_sys_stdin = sys.stdin prev_sys_stdout = sys.stdout prev_sys_stderr = sys.stderr if PY3: from io import StringIO else: from cStringIO import StringIO sys.stdin = None sys.stderr = sys.stdout = StringIO() try: # Don't use cmdline_get_namespace() here, it breaks things in # Python 2 (issue #166). eval(compile(cmd, "", 'single'), self.debugger.curframe.f_globals, self.debugger.curframe.f_locals) except: tp, val, tb = sys.exc_info() import traceback tblist = traceback.extract_tb(tb) del tblist[:1] tb_lines = traceback.format_list(tblist) if tb_lines: tb_lines.insert(0, "Traceback (most recent call last):\n") tb_lines[len(tb_lines):] = traceback.format_exception_only(tp, val) add_cmdline_content("".join(tb_lines), "command line error") else: self.cmdline_edit.set_edit_text("") finally: if sys.stdout.getvalue(): add_cmdline_content(sys.stdout.getvalue(), "command line output") sys.stdin = prev_sys_stdin sys.stdout = prev_sys_stdout sys.stderr = prev_sys_stderr def cmdline_history_browse(direction): if self.cmdline_history_position == -1: self.cmdline_history_position = len(self.cmdline_history) self.cmdline_history_position += direction if 0 <= self.cmdline_history_position < len(self.cmdline_history): self.cmdline_edit.edit_text = \ self.cmdline_history[self.cmdline_history_position] else: self.cmdline_history_position = -1 self.cmdline_edit.edit_text = "" self.cmdline_edit.edit_pos = len(self.cmdline_edit.edit_text) def cmdline_history_prev(w, size, key): cmdline_history_browse(-1) def cmdline_history_next(w, size, key): cmdline_history_browse(1) def cmdline_start_of_line(w, size, key): self.cmdline_edit.edit_pos = 0 def cmdline_end_of_line(w, size, key): self.cmdline_edit.edit_pos = len(self.cmdline_edit.edit_text) def cmdline_del_word(w, size, key): pos = self.cmdline_edit.edit_pos before, after = ( self.cmdline_edit.edit_text[:pos], self.cmdline_edit.edit_text[pos:]) before = before[::-1] before = before.lstrip() i = 0 while i < len(before): if not before[i].isspace(): i += 1 else: break self.cmdline_edit.edit_text = before[i:][::-1] + after self.cmdline_edit.edit_post = len(before[i:]) def cmdline_del_to_start_of_line(w, size, key): pos = self.cmdline_edit.edit_pos self.cmdline_edit.edit_text = self.cmdline_edit.edit_text[pos:] self.cmdline_edit.edit_pos = 0 def toggle_cmdline_focus(w, size, key): self.columns.set_focus(self.lhs_col) if self.lhs_col.get_focus() is self.cmdline_sigwrap: self.lhs_col.set_focus(self.source_attr) else: self.cmdline_pile.set_focus(self.cmdline_edit_bar) self.lhs_col.set_focus(self.cmdline_sigwrap) self.cmdline_edit_sigwrap.listen("tab", cmdline_tab_complete) self.cmdline_edit_sigwrap.listen("ctrl v", cmdline_append_newline) self.cmdline_edit_sigwrap.listen("enter", cmdline_exec) self.cmdline_edit_sigwrap.listen("ctrl n", cmdline_history_next) self.cmdline_edit_sigwrap.listen("ctrl p", cmdline_history_prev) self.cmdline_edit_sigwrap.listen("esc", toggle_cmdline_focus) self.cmdline_edit_sigwrap.listen("ctrl d", toggle_cmdline_focus) self.cmdline_edit_sigwrap.listen("ctrl a", cmdline_start_of_line) self.cmdline_edit_sigwrap.listen("ctrl e", cmdline_end_of_line) self.cmdline_edit_sigwrap.listen("ctrl w", cmdline_del_word) self.cmdline_edit_sigwrap.listen("ctrl u", cmdline_del_to_start_of_line) self.top.listen("ctrl x", toggle_cmdline_focus) # {{{ command line sizing def max_cmdline(w, size, key): self.lhs_col.item_types[-1] = "weight", 5 self.lhs_col._invalidate() def min_cmdline(w, size, key): self.lhs_col.item_types[-1] = "weight", 1/2 self.lhs_col._invalidate() def grow_cmdline(w, size, key): _, weight = self.lhs_col.item_types[-1] if weight < 5: weight *= 1.25 self.lhs_col.item_types[-1] = "weight", weight self.lhs_col._invalidate() def shrink_cmdline(w, size, key): _, weight = self.lhs_col.item_types[-1] if weight > 1/2: weight /= 1.25 self.lhs_col.item_types[-1] = "weight", weight self.lhs_col._invalidate() self.cmdline_sigwrap.listen("=", max_cmdline) self.cmdline_sigwrap.listen("+", grow_cmdline) self.cmdline_sigwrap.listen("_", min_cmdline) self.cmdline_sigwrap.listen("-", shrink_cmdline) # }}} # }}} # {{{ sidebar sizing def max_sidebar(w, size, key): from pudb.settings import save_config weight = 5 CONFIG["sidebar_width"] = weight save_config(CONFIG) self.columns.column_types[1] = "weight", weight self.columns._invalidate() def min_sidebar(w, size, key): from pudb.settings import save_config weight = 1/5 CONFIG["sidebar_width"] = weight save_config(CONFIG) self.columns.column_types[1] = "weight", weight self.columns._invalidate() def grow_sidebar(w, size, key): from pudb.settings import save_config _, weight = self.columns.column_types[1] if weight < 5: weight *= 1.25 CONFIG["sidebar_width"] = weight save_config(CONFIG) self.columns.column_types[1] = "weight", weight self.columns._invalidate() def shrink_sidebar(w, size, key): from pudb.settings import save_config _, weight = self.columns.column_types[1] if weight > 1/5: weight /= 1.25 CONFIG["sidebar_width"] = weight save_config(CONFIG) self.columns.column_types[1] = "weight", weight self.columns._invalidate() self.rhs_col_sigwrap.listen("=", max_sidebar) self.rhs_col_sigwrap.listen("+", grow_sidebar) self.rhs_col_sigwrap.listen("_", min_sidebar) self.rhs_col_sigwrap.listen("-", shrink_sidebar) # }}} # {{{ top-level listeners def show_output(w, size, key): self.screen.stop() raw_input("Hit Enter to return:") self.screen.start() def reload_breakpoints(w, size, key): self.debugger.clear_all_breaks() from pudb.settings import load_breakpoints for bpoint_descr in load_breakpoints(): dbg.set_break(*bpoint_descr) self.update_breakpoints() def show_traceback(w, size, key): if self.current_exc_tuple is not None: from pudb.lowlevel import format_exception result = self.dialog( urwid.ListBox(urwid.SimpleListWalker([urwid.Text( "".join(format_exception(self.current_exc_tuple)))])), [ ("Close", "close"), ("Location", "location") ], title="Exception Viewer", focus_buttons=True, bind_enter_esc=False) if result == "location": self.debugger.set_frame_index(len(self.debugger.stack)-1) else: self.message("No exception available.") def run_external_cmdline(w, size, key): self.screen.stop() curframe = self.debugger.curframe import pudb.shell as shell if CONFIG["shell"] == "ipython" and shell.have_ipython(): runner = shell.run_ipython_shell elif CONFIG["shell"] == "bpython" and shell.HAVE_BPYTHON: runner = shell.run_bpython_shell elif CONFIG["shell"] == "ptpython" and shell.HAVE_PTPYTHON: runner = shell.run_ptpython_shell elif CONFIG["shell"] == "ptipython" and shell.HAVE_PTIPYTHON: runner = shell.run_ptipython_shell elif CONFIG["shell"] == "classic": runner = shell.run_classic_shell else: try: if not shell.custom_shell_dict: # Only execfile once from os.path import expanduser execfile( expanduser(CONFIG["shell"]), shell.custom_shell_dict) except: print("Error when importing custom shell:") from traceback import print_exc print_exc() print("Falling back to classic shell") runner = shell.run_classic_shell else: if "pudb_shell" not in shell.custom_shell_dict: print("%s does not contain a function named pudb_shell at " "the module level." % CONFIG["shell"]) print("Falling back to classic shell") runner = shell.run_classic_shell else: runner = shell.custom_shell_dict['pudb_shell'] runner(curframe.f_globals, curframe.f_locals) self.screen.start() self.update_var_view() def run_cmdline(w, size, key): if CONFIG["shell"] == "internal": return toggle_cmdline_focus(w, size, key) else: return run_external_cmdline(w, size, key) def focus_code(w, size, key): self.columns.set_focus(self.lhs_col) self.lhs_col.set_focus(self.source_attr) class RHColumnFocuser: def __init__(self, idx): self.idx = idx def __call__(subself, w, size, key): # noqa self.columns.set_focus(self.rhs_col_sigwrap) self.rhs_col.set_focus(self.rhs_col.widget_list[subself.idx]) def quit(w, size, key): self.debugger.set_quit() end() def do_edit_config(w, size, key): self.run_edit_config() def redraw_screen(w, size, key): self.screen.clear() def help(w, size, key): self.message(HELP_TEXT, title="PuDB Help") self.top.listen("o", show_output) self.top.listen("ctrl r", reload_breakpoints) self.top.listen("!", run_cmdline) self.top.listen("e", show_traceback) self.top.listen("C", focus_code) self.top.listen("V", RHColumnFocuser(0)) self.top.listen("S", RHColumnFocuser(1)) self.top.listen("B", RHColumnFocuser(2)) self.top.listen("q", quit) self.top.listen("ctrl p", do_edit_config) self.top.listen("ctrl l", redraw_screen) self.top.listen("f1", help) self.top.listen("?", help) # }}} # {{{ setup want_curses_display = ( CONFIG["display"] == "curses" or ( CONFIG["display"] == "auto" and not ( os.environ.get("TERM", "").startswith("xterm") or os.environ.get("TERM", "").startswith("rxvt") ))) if (want_curses_display and not (stdin is not None or stdout is not None) and CursesScreen is not None): self.screen = ThreadsafeCursesScreen() else: screen_kwargs = {} if stdin is not None: screen_kwargs["input"] = stdin if stdout is not None: screen_kwargs["output"] = stdout if term_size is not None: screen_kwargs["term_size"] = term_size if screen_kwargs: self.screen = ThreadsafeFixedSizeRawScreen(**screen_kwargs) else: self.screen = ThreadsafeRawScreen() del want_curses_display if curses: try: curses.setupterm() except: # Something went wrong--oh well. Nobody will die if their # 256 color support breaks. Just carry on without it. # https://github.com/inducer/pudb/issues/78 pass else: color_support = curses.tigetnum('colors') if color_support == 256 and isinstance(self.screen, RawScreen): self.screen.set_terminal_properties(256) self.setup_palette(self.screen) self.show_count = 0 self.source_code_provider = None self.current_line = None self.quit_event_loop = False # }}} # }}} # {{{ UI helpers def translate_ui_stack_index(self, index): # note: self-inverse if CONFIG["current_stack_frame"] == "top": return len(self.debugger.stack)-1-index elif CONFIG["current_stack_frame"] == "bottom": return index else: raise ValueError("invalid value for 'current_stack_frame' pref") def message(self, msg, title="Message", **kwargs): self.call_with_ui(self.dialog, urwid.ListBox(urwid.SimpleListWalker([urwid.Text(msg)])), [("OK", True)], title=title, **kwargs) def run_edit_config(self): from pudb.settings import edit_config, save_config edit_config(self, CONFIG) save_config(CONFIG) def dialog(self, content, buttons_and_results, title=None, bind_enter_esc=True, focus_buttons=False, extra_bindings=[]): class ResultSetter: def __init__(subself, res): # noqa subself.res = res def __call__(subself, btn): # noqa self.quit_event_loop = [subself.res] Attr = urwid.AttrMap # noqa if bind_enter_esc: content = SignalWrap(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) button_widgets = [] for btn_descr in buttons_and_results: if btn_descr is None: button_widgets.append(urwid.Text("")) else: btn_text, btn_result = btn_descr button_widgets.append( Attr(urwid.Button(btn_text, ResultSetter(btn_result)), "button", "focused button")) w = urwid.Columns([ content, ("fixed", 15, urwid.ListBox(urwid.SimpleListWalker(button_widgets))), ], dividechars=1) if focus_buttons: w.set_focus_column(1) if title is not None: w = urwid.Pile([ ("flow", urwid.AttrMap( urwid.Text(title, align="center"), "dialog title")), ("fixed", 1, urwid.SolidFill()), w]) class ResultSetter: def __init__(subself, res): # noqa subself.res = res def __call__(subself, w, size, key): # noqa self.quit_event_loop = [subself.res] w = SignalWrap(w) for key, binding in extra_bindings: if isinstance(binding, str): w.listen(key, ResultSetter(binding)) else: w.listen(key, binding) 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): may_use_fancy_formats = not hasattr(urwid.escape, "_fg_attr_xterm") from pudb.theme import get_palette screen.register_palette( get_palette(may_use_fancy_formats, CONFIG["theme"])) def show_exception_dialog(self, exc_tuple): from pudb.lowlevel import format_exception tb_txt = "".join(format_exception(exc_tuple)) while True: res = self.dialog( urwid.ListBox(urwid.SimpleListWalker([urwid.Text( "The program has terminated abnormally because of " "an exception.\n\n" "A full traceback is below. You may recall this " "traceback at any time using the 'e' key. " "The debugger has entered post-mortem mode and will " "prevent further state changes.\n\n" + tb_txt)])), title="Program Terminated for Uncaught Exception", buttons_and_results=[ ("OK", True), ("Save traceback", "save"), ]) if res in [True, False]: break if res == "save": try: n = 0 from os.path import exists while True: if n: fn = "traceback-%d.txt" % n else: fn = "traceback.txt" if not exists(fn): outf = open(fn, "w") try: outf.write(tb_txt) finally: outf.close() self.message("Traceback saved as %s." % fn, title="Success") break n += 1 except Exception: io_tb_txt = "".join(format_exception(sys.exc_info())) self.message( "An error occurred while trying to write " "the traceback:\n\n" + io_tb_txt, title="I/O error") # }}} # {{{ UI enter/exit def show(self): if self.show_count == 0: self.screen.start() self.show_count += 1 def hide(self): self.show_count -= 1 if self.show_count == 0: self.screen.stop() def call_with_ui(self, f, *args, **kwargs): self.show() try: return f(*args, **kwargs) finally: self.hide() # }}} # {{{ interaction def event_loop(self, toplevel=None): prev_quit_loop = self.quit_event_loop try: import pygments # noqa except ImportError: if not hasattr(self, "pygments_message_shown"): self.pygments_message_shown = True self.message("Package 'pygments' not found. " "Syntax highlighting disabled.") WELCOME_LEVEL = "e033" # noqa if CONFIG["seen_welcome"] < WELCOME_LEVEL: CONFIG["seen_welcome"] = WELCOME_LEVEL from pudb import VERSION self.message("Welcome to PudB %s!\n\n" "PuDB is a full-screen, console-based visual debugger for " "Python. Its goal is to provide all the niceties of modern " "GUI-based debuggers in a more lightweight and " "keyboard-friendly package. " "PuDB allows you to debug code right where you write and test " "it--in a terminal. If you've worked with the excellent " "(but nowadays ancient) DOS-based Turbo Pascal or C tools, " "PuDB's UI might look familiar.\n\n" "If you're new here, welcome! The help screen " "(invoked by hitting '?' after this message) should get you " "on your way.\n" "\nChanges in version 2017.1.4:\n\n" "- Bug fixes.\n" "\nChanges in version 2017.1.3:\n\n" "- Add handling of safely_stringify_for_pudb to allow custom \n" " per-type stringification.\n" "- Add support for custom shells.\n" "- Better support for 2-wide characters in the var view.\n" "- Bug fixes.\n" "\nChanges in version 2017.1.2:\n\n" "- Bug fixes.\n" "\nChanges in version 2017.1.1:\n\n" "- IMPORTANT: 2017.1 and possibly earlier versions had a \n" " bug with exponential growth of shell history for the \n" " 'classic' shell, which (among other problems) could lead\n" " to slow startup of the classic shell. Check the file\n\n" " ~/.config/pudb/shell-history\n\n" " for size (and useful content) and delete/trim as needed.\n" "\nChanges in version 2017.1:\n\n" "- Many, many bug fixes (thank you to all who contributed!)\n" "\nChanges in version 2016.2:\n\n" "- UI improvements for disabled breakpoints.\n" "- Bug fixes.\n" "\nChanges in version 2016.1:\n\n" "- Fix module browser on Py3.\n" "\nChanges in version 2015.4:\n\n" "- Support for (somewhat rudimentary) remote debugging\n" " through a telnet connection.\n" "- Fix debugging of generated code in Python 3.\n" "\nChanges in version 2015.3:\n\n" "- Disable set_trace lines from the UI (Aaron Meurer)\n" "- Better control over attribute visibility (Ned Batchelder)\n" "\nChanges in version 2015.2:\n\n" "- ptpython support (P. Varet)\n" "- Improved rxvt support (Louper Rouch)\n" "- More keyboard shortcuts in the command line" "(Alex Sheluchin)\n" "\nChanges in version 2015.1:\n\n" "- Add solarized theme (Rinat Shigapov)\n" "- More keyboard shortcuts in the command line" "(Alexander Corwin)\n" "\nChanges in version 2014.1:\n\n" "- Make prompt-on-quit optional (Mike Burr)\n" "- Make tab completion in the built-in shell saner\n" "- Fix handling of unicode source\n" " (reported by Morten Nielsen and Buck Golemon)\n" "\nChanges in version 2013.5.1:\n\n" "- Fix loading of saved breakpoint conditions " "(Antoine Dechaume)\n" "- Fixes for built-in command line\n" "- Theme updates\n" "\nChanges in version 2013.5:\n\n" "- Add command line window\n" "- Uses curses display driver when appropriate\n" "\nChanges in version 2013.4:\n\n" "- Support for debugging generated code\n" "\nChanges in version 2013.3.5:\n\n" "- IPython fixes (Aaron Meurer)\n" "- Py2/3 configuration fixes (Somchai Smythe)\n" "- PyPy fixes (Julian Berman)\n" "\nChanges in version 2013.3.4:\n\n" "- Don't die if curses doesn't like what stdin/out are\n" " connected to.\n" "\nChanges in version 2013.3.3:\n\n" "- As soon as pudb is loaded, you can break to the debugger by\n" " evaluating the expression 'pu.db', where 'pu' is a new \n" " 'builtin' that pudb has rudely shoved into the interpreter.\n" "\nChanges in version 2013.3.2:\n\n" "- Don't attempt to do signal handling if a signal handler\n" " is already set (Fix by Buck Golemon).\n" "\nChanges in version 2013.3.1:\n\n" "- Don't ship {ez,distribute}_setup at all.\n" " It breaks more than it helps.\n" "\nChanges in version 2013.3:\n\n" "- Switch to setuptools as a setup helper.\n" "\nChanges in version 2013.2:\n\n" "- Even more bug fixes.\n" "\nChanges in version 2013.1:\n\n" "- Ctrl-C will now break to the debugger in a way that does\n" " not terminate the program\n" "- Lots of bugs fixed\n" "\nChanges in version 2012.3:\n\n" "- Python 3 support (contributed by Brad Froehle)\n" "- Better search box behavior (suggested by Ram Rachum)\n" "- Made it possible to go back and examine state from " "'finished' window. (suggested by Aaron Meurer)\n" "\nChanges in version 2012.2.1:\n\n" "- Don't touch config files during install.\n" "\nChanges in version 2012.2:\n\n" "- Add support for BPython as a shell.\n" "- You can now run 'python -m pudb script.py' on Py 2.6+.\n" " '-m pudb.run' still works--but it's four " "keystrokes longer! :)\n" "\nChanges in version 2012.1:\n\n" "- Work around an API change in IPython 0.12.\n" "\nChanges in version 2011.3.1:\n\n" "- Work-around for bug in urwid >= 1.0.\n" "\nChanges in version 2011.3:\n\n" "- Finer-grained string highlighting " "(contributed by Aaron Meurer)\n" "- Prefs tweaks, instant-apply, top-down stack " "(contributed by Aaron Meurer)\n" "- Size changes in sidebar boxes (contributed by Aaron Meurer)\n" "- New theme 'midnight' (contributed by Aaron Meurer)\n" "- Support for IPython 0.11 (contributed by Chris Farrow)\n" "- Suport for custom stringifiers " "(contributed by Aaron Meurer)\n" "- Line wrapping in variables view " "(contributed by Aaron Meurer)\n" "\nChanges in version 2011.2:\n\n" "- Fix for post-mortem debugging (contributed by 'Sundance')\n" "\nChanges in version 2011.1:\n\n" "- Breakpoints saved between sessions\n" "- A new 'dark vim' theme\n" "(both contributed by Naveen Michaud-Agrawal)\n" "\nChanges in version 0.93:\n\n" "- Stored preferences (no more pesky IPython prompt!)\n" "- Themes\n" "- Line numbers (optional)\n" % VERSION) from pudb.settings import save_config save_config(CONFIG) self.run_edit_config() try: if toplevel is None: toplevel = self.top self.size = self.screen.get_cols_rows() self.quit_event_loop = False while not self.quit_event_loop: canvas = toplevel.render(self.size, focus=True) self.screen.draw_screen(self.size, canvas) keys = self.screen.get_input() for k in keys: if k == "window resize": 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 interaction(self, exc_tuple, show_exc_dialog=True): self.current_exc_tuple = exc_tuple from pudb import VERSION caption = [(None, "PuDB %s - ?:help n:next s:step into b:breakpoint " "!:python command line" % VERSION)] if self.debugger.post_mortem: if show_exc_dialog and exc_tuple is not None: self.show_exception_dialog(exc_tuple) caption.extend([ (None, " "), ("warning", "[POST-MORTEM MODE]") ]) elif exc_tuple is not None: caption.extend([ (None, " "), ("warning", "[PROCESSING EXCEPTION - hit 'e' to examine]") ]) self.caption.set_text(caption) self.event_loop() def set_source_code_provider(self, source_code_provider, force_update=False): if self.source_code_provider != source_code_provider or force_update: self.source[:] = source_code_provider.get_lines(self) self.source_code_provider = source_code_provider self.current_line = None def show_line(self, line, source_code_provider=None): """Updates the UI so that a certain line is currently in view.""" changed_file = False if source_code_provider is not None: changed_file = self.source_code_provider != source_code_provider self.set_source_code_provider(source_code_provider) line -= 1 if line >= 0 and line < len(self.source): self.source_list.set_focus(line) if changed_file: self.source_list.set_focus_valign("middle") def set_current_line(self, line, source_code_provider): """Updates the UI to show the line currently being executed.""" if self.current_line is not None: self.current_line.set_current(False) self.show_line(line, source_code_provider) line -= 1 if line >= 0 and line < len(self.source): self.current_line = self.source[line] self.current_line.set_current(True) def update_var_view(self, locals=None, globals=None): if locals is None: locals = self.debugger.curframe.f_locals if globals is None: globals = self.debugger.curframe.f_globals from pudb.var_view import make_var_view self.locals[:] = make_var_view( self.get_frame_var_info(read_only=True), locals, globals) def _get_bp_list(self): return [bp for fn, bp_lst in self.debugger.get_all_breaks().items() 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(fname)[-10:]+"/"+name return name def update_breakpoints(self): self.bp_walker[:] = [ BreakpointFrame(self.debugger.current_bp == (bp.file, bp.line), self._format_fname(bp.file), bp) for bp in self._get_bp_list()] def update_stack(self): def make_frame_ui(frame_lineno): frame, lineno = frame_lineno code = frame.f_code class_name = None if code.co_argcount and code.co_varnames[0] == "self": try: class_name = frame.f_locals["self"].__class__.__name__ except: pass return StackFrame(frame is self.debugger.curframe, code.co_name, class_name, self._format_fname(code.co_filename), lineno) frame_uis = [make_frame_ui(fl) for fl in self.debugger.stack] if CONFIG["current_stack_frame"] == "top": frame_uis = frame_uis[::-1] elif CONFIG["current_stack_frame"] == "bottom": pass else: raise ValueError("invalid value for 'current_stack_frame' pref") self.stack_walker[:] = frame_uis # }}} # vim: foldmethod=marker:expandtab:softtabstop=4 pudb-2017.1.4/pudb/ipython.py000066400000000000000000000023441315257663400157150ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function, with_statement import sys import os from IPython.core.magic import register_line_magic from IPython import get_ipython def pudb(line): """ Debug a script (like %run -d) in the IPython process, using PuDB. Usage: %pudb test.py [args] Run script test.py under PuDB. """ # Get the running instance if not line.strip(): print(pudb.__doc__) return from IPython.utils.process import arg_split args = arg_split(line) path = os.path.abspath(args[0]) args = args[1:] if not os.path.isfile(path): from IPython.core.error import UsageError raise UsageError("%%pudb: file %s does not exist" % path) from pudb import runscript runscript(path, args) register_line_magic(pudb) def debugger(self, force=False): """Call the PuDB debugger.""" from IPython.utils.warn import error if not (force or self.call_pdb): return if not hasattr(sys, 'last_traceback'): error('No traceback has been produced, nothing to debug.') return from pudb import pm with self.readline_no_record: pm() ip = get_ipython() ip.__class__.debugger = debugger pudb-2017.1.4/pudb/lowlevel.py000066400000000000000000000130521315257663400160520ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function from pudb.py3compat import PY3 # {{{ breakpoint validity def generate_executable_lines_for_code(code): l = code.co_firstlineno yield l if PY3: for c in code.co_lnotab[1::2]: l += c yield l else: for c in code.co_lnotab[1::2]: l += ord(c) yield l def get_executable_lines_for_file(filename): # inspired by rpdb2 from linecache import getlines codes = [compile("".join(getlines(filename)), filename, "exec", dont_inherit=1)] from types import CodeType execable_lines = set() while codes: code = codes.pop() execable_lines |= set(generate_executable_lines_for_code(code)) codes.extend(const for const in code.co_consts if isinstance(const, CodeType)) return execable_lines def get_breakpoint_invalid_reason(filename, lineno): # simple logic stolen from pdb import linecache line = linecache.getline(filename, lineno) if not line: return "Line is beyond end of file." try: executable_lines = get_executable_lines_for_file(filename) except SyntaxError: return "File failed to compile." if lineno not in executable_lines: return "No executable statement found in line." def lookup_module(filename): """Helper function for break/clear parsing -- may be overridden. lookupmodule() translates (possibly incomplete) file or module name into an absolute file name. """ # stolen from pdb import os import sys if os.path.isabs(filename) and os.path.exists(filename): return filename f = os.path.join(sys.path[0], filename) if os.path.exists(f): # and self.canonic(f) == self.mainpyfile: return f root, ext = os.path.splitext(filename) if ext == '': filename = filename + '.py' if os.path.isabs(filename): return filename for dirname in sys.path: while os.path.islink(dirname): dirname = os.readlink(dirname) fullname = os.path.join(dirname, filename) if os.path.exists(fullname): return fullname return None # }}} # {{{ file encoding detection # the main idea stolen from Python 3.1's tokenize.py, by Ka-Ping Yee import re cookie_re = re.compile("^\s*#.*coding[:=]\s*([-\w.]+)") from codecs import lookup, BOM_UTF8 if PY3: BOM_UTF8 = BOM_UTF8.decode() def detect_encoding(lines): """ The detect_encoding() function is used to detect the encoding that should be used to decode a Python source file. It requires one argment, lines, iterable lines stream. It will read a maximum of two lines, and return the encoding used (as a string) and a list of any lines (left as bytes) it has read in. It detects the encoding from the presence of a utf-8 bom or an encoding cookie as specified in pep-0263. If both a bom and a cookie are present, but disagree, a SyntaxError will be raised. If the encoding cookie is an invalid charset, raise a SyntaxError. If no encoding is specified, then the default of 'utf-8' will be returned. """ bom_found = False line_iterator = iter(lines) def read_or_stop(): try: return next(line_iterator) except StopIteration: return '' def find_cookie(line): try: if PY3: line_string = line else: line_string = line.decode('ascii') except UnicodeDecodeError: return None matches = cookie_re.findall(line_string) if not matches: return None encoding = matches[0] try: codec = lookup(encoding) except LookupError: # This behaviour mimics the Python interpreter raise SyntaxError("unknown encoding: " + encoding) if bom_found and codec.name != 'utf-8': # This behaviour mimics the Python interpreter raise SyntaxError('encoding problem: utf-8') return encoding first = read_or_stop() if first.startswith(BOM_UTF8): bom_found = True first = first[3:] if not first: return 'utf-8', [] encoding = find_cookie(first) if encoding: return encoding, [first] second = read_or_stop() if not second: return 'utf-8', [first] encoding = find_cookie(second) if encoding: return encoding, [first, second] return 'utf-8', [first, second] def decode_lines(lines): source_enc, _ = detect_encoding(lines) for line in lines: if hasattr(line, "decode"): yield line.decode(source_enc) else: yield line # }}} # {{{ traceback formatting class StringExceptionValueWrapper: def __init__(self, string_val): self.string_val = string_val def __str__(self): return self.string_val __context__ = None __cause__ = None def format_exception(exc_tuple): # Work around http://bugs.python.org/issue17413 # See also https://github.com/inducer/pudb/issues/61 from traceback import format_exception if PY3: exc_type, exc_value, exc_tb = exc_tuple if isinstance(exc_value, str): exc_value = StringExceptionValueWrapper(exc_value) exc_tuple = exc_type, exc_value, exc_tb return format_exception( *exc_tuple, **dict(chain=hasattr(exc_value, "__context__"))) else: return format_exception(*exc_tuple) # }}} # vim: foldmethod=marker pudb-2017.1.4/pudb/py3compat.py000066400000000000000000000013621315257663400161410ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function import sys PY3 = sys.version_info[0] >= 3 if PY3: raw_input = input xrange = range integer_types = (int,) string_types = (str,) text_type = str def execfile(fname, globs, locs=None): exec(compile(open(fname).read(), fname, 'exec'), globs, locs or globs) else: raw_input = raw_input xrange = xrange integer_types = (int, long) # noqa: F821 string_types = (basestring,) # noqa: F821 text_type = unicode # noqa: F821 execfile = execfile try: import builtins from configparser import ConfigParser except ImportError: import __builtin__ as builtins # noqa: F401 from ConfigParser import ConfigParser # noqa: F401 pudb-2017.1.4/pudb/remote.py000066400000000000000000000117331315257663400155200ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals # mostly stolen from celery.contrib.rdb import errno import os import socket import sys import fcntl import termios import struct from pudb.debugger import Debugger __all__ = ['PUDB_RDB_HOST', 'PUDB_RDB_PORT', 'default_port', 'debugger', 'set_trace'] default_port = 6899 PUDB_RDB_HOST = os.environ.get('PUDB_RDB_HOST') or '127.0.0.1' PUDB_RDB_PORT = int(os.environ.get('PUDB_RDB_PORT') or default_port) #: Holds the currently active debugger. _current = [None] _frame = getattr(sys, '_getframe') NO_AVAILABLE_PORT = """\ {self.ident}: Couldn't find an available port. Please specify one using the PUDB_RDB_PORT environment variable. """ BANNER = """\ {self.ident}: Please telnet into {self.host} {self.port}. {self.ident}: Waiting for client... """ SESSION_STARTED = '{self.ident}: Now in session with {self.remote_addr}.' SESSION_ENDED = '{self.ident}: Session with {self.remote_addr} ended.' class RemoteDebugger(Debugger): me = 'pudb' _prev_outs = None _sock = None def __init__(self, host=PUDB_RDB_HOST, port=PUDB_RDB_PORT, port_search_limit=100, out=sys.stdout, term_size=None): self.active = True self.out = out self._prev_handles = sys.stdin, sys.stdout self._sock, this_port = self.get_avail_port( host, port, port_search_limit) self._sock.setblocking(1) self._sock.listen(1) self.ident = '{0}:{1}'.format(self.me, this_port) self.host = host self.port = this_port self.say(BANNER.format(self=self)) self._client, address = self._sock.accept() self._client.setblocking(1) self.remote_addr = ':'.join(str(v) for v in address) self.say(SESSION_STARTED.format(self=self)) # makefile ignores encoding if there's no buffering. raw_sock_file = self._client.makefile("rwb", 0) import codecs if sys.version_info[0] < 3: sock_file = codecs.StreamRecoder( raw_sock_file, codecs.getencoder("utf-8"), codecs.getdecoder("utf-8"), codecs.getreader("utf-8"), codecs.getwriter("utf-8")) else: sock_file = codecs.StreamReaderWriter( raw_sock_file, codecs.getreader("utf-8"), codecs.getwriter("utf-8")) self._handle = sys.stdin = sys.stdout = sock_file import telnetlib as tn raw_sock_file.write(tn.IAC + tn.WILL + tn.SGA) resp = raw_sock_file.read(3) assert resp == tn.IAC + tn.DO + tn.SGA raw_sock_file.write(tn.IAC + tn.WILL + tn.ECHO) resp = raw_sock_file.read(3) assert resp == tn.IAC + tn.DO + tn.ECHO Debugger.__init__(self, stdin=self._handle, stdout=self._handle, term_size=term_size) def get_avail_port(self, host, port, search_limit=100, skew=+0): this_port = None for i in range(search_limit): _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) this_port = port + i try: _sock.bind((host, this_port)) except socket.error as exc: if exc.errno in [errno.EADDRINUSE, errno.EINVAL]: continue raise else: return _sock, this_port else: raise Exception(NO_AVAILABLE_PORT.format(self=self)) def say(self, m): print(m, file=self.out) def _close_session(self): self.stdin, self.stdout = sys.stdin, sys.stdout = self._prev_handles self._handle.close() self._client.close() self._sock.close() self.active = False self.say(SESSION_ENDED.format(self=self)) def do_continue(self, arg): self._close_session() self.set_continue() return 1 do_c = do_cont = do_continue def do_quit(self, arg): self._close_session() self.set_quit() return 1 def set_quit(self): # this raises a BdbQuit exception that we are unable to catch. sys.settrace(None) def debugger(term_size=None, host=PUDB_RDB_HOST, port=PUDB_RDB_PORT): """Return the current debugger instance (if any), or creates a new one.""" rdb = _current[0] if rdb is None or not rdb.active: rdb = _current[0] = RemoteDebugger(host=host, port=port, term_size=term_size) return rdb def set_trace(frame=None, term_size=None, host=PUDB_RDB_HOST, port=PUDB_RDB_PORT): """Set breakpoint at current location, or a specified frame""" if frame is None: frame = _frame().f_back if term_size is None: try: # Getting terminal size s = struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234')) term_size = (s[1], s[0]) except: term_size = (80, 24) return debugger(term_size=term_size, host=host, port=port).set_trace(frame) pudb-2017.1.4/pudb/run.py000066400000000000000000000016751315257663400150350ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function def main(): import sys from optparse import OptionParser parser = OptionParser( usage="usage: %prog [options] SCRIPT-TO-RUN [SCRIPT-ARGUMENTS]") parser.add_option("-s", "--steal-output", action="store_true"), parser.add_option("--pre-run", metavar="COMMAND", help="Run command before each program run", default="") parser.disable_interspersed_args() options, args = parser.parse_args() if len(args) < 1: parser.print_help() sys.exit(2) mainpyfile = args[0] from os.path import exists if not exists(mainpyfile): print('Error: %s does not exist' % mainpyfile) sys.exit(1) sys.argv = args from pudb import runscript runscript(mainpyfile, pre_run=options.pre_run, steal_output=options.steal_output) if __name__ == '__main__': main() pudb-2017.1.4/pudb/settings.py000066400000000000000000000420361315257663400160650ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function import os import sys from pudb.py3compat import ConfigParser from pudb.lowlevel import lookup_module, get_breakpoint_invalid_reason # minor LGPL violation: stolen from python-xdg _home = os.environ.get('HOME', None) xdg_data_home = os.environ.get('XDG_DATA_HOME', os.path.join(_home, '.local', 'share') if _home else None) XDG_CONFIG_HOME = os.environ.get('XDG_CONFIG_HOME', os.path.join(_home, '.config') if _home else None) if XDG_CONFIG_HOME: XDG_CONFIG_DIRS = [XDG_CONFIG_HOME] else: XDG_CONFIG_DIRS = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':') def get_save_config_path(*resource): if XDG_CONFIG_HOME is None: return None if not resource: resource = [XDG_CONF_RESOURCE] resource = os.path.join(*resource) assert not resource.startswith('/') path = os.path.join(XDG_CONFIG_HOME, resource) if not os.path.isdir(path): os.makedirs(path, 448) # 0o700 return path # end LGPL violation CONF_SECTION = "pudb" XDG_CONF_RESOURCE = "pudb" CONF_FILE_NAME = "pudb.cfg" SAVED_BREAKPOINTS_FILE_NAME = "saved-breakpoints-%d.%d" % sys.version_info[:2] BREAKPOINTS_FILE_NAME = "breakpoints-%d.%d" % sys.version_info[:2] def load_config(): from os.path import join, isdir cparser = ConfigParser() conf_dict = {} try: cparser.read([ join(cdir, XDG_CONF_RESOURCE, CONF_FILE_NAME) for cdir in XDG_CONFIG_DIRS if isdir(cdir)]) if cparser.has_section(CONF_SECTION): conf_dict.update(dict(cparser.items(CONF_SECTION))) except: pass conf_dict.setdefault("shell", "internal") conf_dict.setdefault("theme", "classic") conf_dict.setdefault("line_numbers", False) conf_dict.setdefault("seen_welcome", "a") conf_dict.setdefault("sidebar_width", 0.5) conf_dict.setdefault("variables_weight", 1) conf_dict.setdefault("stack_weight", 1) conf_dict.setdefault("breakpoints_weight", 1) conf_dict.setdefault("current_stack_frame", "top") conf_dict.setdefault("stringifier", "type") conf_dict.setdefault("custom_theme", "") conf_dict.setdefault("custom_stringifier", "") conf_dict.setdefault("custom_shell", "") conf_dict.setdefault("wrap_variables", True) conf_dict.setdefault("display", "auto") conf_dict.setdefault("prompt_on_quit", True) def normalize_bool_inplace(name): try: if conf_dict[name].lower() in ["0", "false", "off"]: conf_dict[name] = False else: conf_dict[name] = True except: pass normalize_bool_inplace("line_numbers") normalize_bool_inplace("wrap_variables") normalize_bool_inplace("prompt_on_quit") return conf_dict def save_config(conf_dict): from os.path import join cparser = ConfigParser() cparser.add_section(CONF_SECTION) for key in sorted(conf_dict): cparser.set(CONF_SECTION, key, str(conf_dict[key])) try: save_path = get_save_config_path() if not save_path: return outf = open(join(save_path, CONF_FILE_NAME), "w") cparser.write(outf) outf.close() except: pass def edit_config(ui, conf_dict): import urwid old_conf_dict = conf_dict.copy() def _update_theme(): ui.setup_palette(ui.screen) ui.screen.clear() def _update_line_numbers(): for sl in ui.source: sl._invalidate() def _update_prompt_on_quit(): pass def _update_current_stack_frame(): ui.update_stack() def _update_stringifier(): import pudb.var_view pudb.var_view.custom_stringifier_dict = {} ui.update_var_view() def _update_wrap_variables(): ui.update_var_view() def _update_config(check_box, new_state, option_newvalue): option, newvalue = option_newvalue new_conf_dict = {option: newvalue} if option == "theme": # only activate if the new state of the radio button is 'on' if new_state: if newvalue is None: # Select the custom theme entry dialog lb.set_focus(lb_contents.index(theme_edit_list_item)) return conf_dict.update(theme=newvalue) _update_theme() elif option == "line_numbers": new_conf_dict["line_numbers"] = not check_box.get_state() conf_dict.update(new_conf_dict) _update_line_numbers() elif option == "prompt_on_quit": new_conf_dict["prompt_on_quit"] = not check_box.get_state() conf_dict.update(new_conf_dict) _update_prompt_on_quit() elif option == "current_stack_frame": # only activate if the new state of the radio button is 'on' if new_state: conf_dict.update(new_conf_dict) _update_current_stack_frame() elif option == "stringifier": # only activate if the new state of the radio button is 'on' if new_state: if newvalue is None: lb.set_focus(lb_contents.index(stringifier_edit_list_item)) return conf_dict.update(stringifier=newvalue) _update_stringifier() elif option == "wrap_variables": new_conf_dict["wrap_variables"] = not check_box.get_state() conf_dict.update(new_conf_dict) _update_wrap_variables() heading = urwid.Text("This is the preferences screen for PuDB. " "Hit Ctrl-P at any time to get back to it.\n\n" "Configuration settings are saved in " "$HOME/.config/pudb or $XDG_CONFIG_HOME/pudb " "environment variable. If both variables are not set " "configurations settings will not be saved.\n") cb_line_numbers = urwid.CheckBox("Show Line Numbers", bool(conf_dict["line_numbers"]), on_state_change=_update_config, user_data=("line_numbers", None)) cb_prompt_on_quit = urwid.CheckBox("Prompt before quitting", bool(conf_dict["prompt_on_quit"]), on_state_change=_update_config, user_data=("prompt_on_quit", None)) # {{{ shells shell_info = urwid.Text("This is the shell that will be " "used when you hit '!'.\n") shells = ["internal", "classic", "ipython", "bpython", "ptpython", "ptipython"] known_shell = conf_dict["shell"] in shells shell_edit = urwid.Edit(edit_text=conf_dict["custom_shell"]) shell_edit_list_item = urwid.AttrMap(shell_edit, "value") shell_rb_group = [] shell_rbs = [ urwid.RadioButton(shell_rb_group, name, conf_dict["shell"] == name) for name in shells]+[ urwid.RadioButton(shell_rb_group, "Custom:", not known_shell, on_state_change=_update_config, user_data=("shell", None)), shell_edit_list_item, urwid.Text("\nTo use a custom shell, see example-shell.py " "in the pudb distribution. Enter the full path to a " "file like it in the box above. '~' will be expanded " "to your home directory. The file should contain a " "function called pudb_shell(_globals, _locals) " "at the module level. See the PuDB documentation for " "more information."), ] # }}} # {{{ themes from pudb.theme import THEMES known_theme = conf_dict["theme"] in THEMES theme_rb_group = [] theme_edit = urwid.Edit(edit_text=conf_dict["custom_theme"]) theme_edit_list_item = urwid.AttrMap(theme_edit, "value") theme_rbs = [ urwid.RadioButton(theme_rb_group, name, conf_dict["theme"] == name, on_state_change=_update_config, user_data=("theme", name)) for name in THEMES]+[ urwid.RadioButton(theme_rb_group, "Custom:", not known_theme, on_state_change=_update_config, user_data=("theme", None)), theme_edit_list_item, urwid.Text("\nTo use a custom theme, see example-theme.py in the " "pudb distribution. Enter the full path to a file like it in " "the box above. '~' will be expanded to your home directory. " "Note that a custom theme will not be applied until you close " "this dialog."), ] # }}} # {{{ stack stack_rb_group = [] stack_opts = ["top", "bottom"] stack_info = urwid.Text("Show the current stack frame at the\n") stack_rbs = [ urwid.RadioButton(stack_rb_group, name, conf_dict["current_stack_frame"] == name, on_state_change=_update_config, user_data=("current_stack_frame", name)) for name in stack_opts ] # }}} # {{{ stringifier stringifier_opts = ["type", "str", "repr"] known_stringifier = conf_dict["stringifier"] in stringifier_opts stringifier_rb_group = [] stringifier_edit = urwid.Edit(edit_text=conf_dict["custom_stringifier"]) stringifier_info = urwid.Text("This is the default function that will be " "called on variables in the variables list. Note that you can change " "this on a per-variable basis by selecting a variable and hitting Enter " "or by typing t/s/r. Note that str and repr will be slower than type " "and have the potential to crash PuDB.\n") stringifier_edit_list_item = urwid.AttrMap(stringifier_edit, "value") stringifier_rbs = [ urwid.RadioButton(stringifier_rb_group, name, conf_dict["stringifier"] == name, on_state_change=_update_config, user_data=("stringifier", name)) for name in stringifier_opts ]+[ urwid.RadioButton(stringifier_rb_group, "Custom:", not known_stringifier, on_state_change=_update_config, user_data=("stringifier", None)), stringifier_edit_list_item, urwid.Text("\nTo use a custom stringifier, see " "example-stringifier.py in the pudb distribution. Enter the " "full path to a file like it in the box above. " "'~' will be expanded to your home directory. " "The file should contain a function called pudb_stringifier() " "at the module level, which should take a single argument and " "return the desired string form of the object passed to it. " "Note that if you choose a custom stringifier, the variables " "view will not be updated until you close this dialog."), ] # }}} # {{{ wrap variables cb_wrap_variables = urwid.CheckBox("Wrap variables", bool(conf_dict["wrap_variables"]), on_state_change=_update_config, user_data=("wrap_variables", None)) wrap_variables_info = urwid.Text("\nNote that you can change this option on " "a per-variable basis by selecting the " "variable and pressing 'w'.") # }}} # {{{ display display_info = urwid.Text("What driver is used to talk to your terminal. " "'raw' has the most features (colors and highlighting), " "but is only correct for " "XTerm and terminals like it. 'curses' " "has fewer " "features, but it will work with just about any terminal. 'auto' " "will attempt to pick between the two based on availability and " "the $TERM environment variable.\n\n" "Changing this setting requires a restart of PuDB.") displays = ["auto", "raw", "curses"] display_rb_group = [] display_rbs = [ urwid.RadioButton(display_rb_group, name, conf_dict["display"] == name) for name in displays] # }}} lb_contents = ( [heading] + [urwid.AttrMap(urwid.Text("Line Numbers:\n"), "group head")] + [cb_line_numbers] + [urwid.AttrMap(urwid.Text("\nPrompt on quit:\n"), "group head")] + [cb_prompt_on_quit] + [urwid.AttrMap(urwid.Text("\nShell:\n"), "group head")] + [shell_info] + shell_rbs + [urwid.AttrMap(urwid.Text("\nTheme:\n"), "group head")] + theme_rbs + [urwid.AttrMap(urwid.Text("\nStack Order:\n"), "group head")] + [stack_info] + stack_rbs + [urwid.AttrMap(urwid.Text("\nVariable Stringifier:\n"), "group head")] + [stringifier_info] + stringifier_rbs + [urwid.AttrMap(urwid.Text("\nWrap Variables:\n"), "group head")] + [cb_wrap_variables] + [wrap_variables_info] + [urwid.AttrMap(urwid.Text("\nDisplay driver:\n"), "group head")] + [display_info] + display_rbs ) lb = urwid.ListBox(urwid.SimpleListWalker(lb_contents)) if ui.dialog(lb, [ ("OK", True), ("Cancel", False), ], title="Edit Preferences"): # Only update the settings here that instant-apply (above) doesn't take # care of. # if we had a custom theme, it wasn't updated live if theme_rb_group[-1].state: newvalue = theme_edit.get_edit_text() conf_dict.update(theme=newvalue, custom_theme=newvalue) _update_theme() # Ditto for custom stringifiers if stringifier_rb_group[-1].state: newvalue = stringifier_edit.get_edit_text() conf_dict.update(stringifier=newvalue, custom_stringifier=newvalue) _update_stringifier() if shell_rb_group[-1].state: newvalue = shell_edit.get_edit_text() conf_dict.update(shell=newvalue, custom_shell=newvalue) else: for shell, shell_rb in zip(shells, shell_rbs): if shell_rb.get_state(): conf_dict["shell"] = shell for display, display_rb in zip(displays, display_rbs): if display_rb.get_state(): conf_dict["display"] = display else: # The user chose cancel, revert changes conf_dict.update(old_conf_dict) _update_theme() # _update_line_numbers() is equivalent to _update_theme() _update_current_stack_frame() _update_stringifier() # {{{ breakpoint saving def parse_breakpoints(lines): # b [ (filename:lineno | function) [, "condition"] ] breakpoints = [] for arg in lines: if not arg: continue arg = arg[1:] filename = None lineno = None cond = None comma = arg.find(',') if comma > 0: # parse stuff after comma: "condition" cond = arg[comma+1:].lstrip() arg = arg[:comma].rstrip() colon = arg.rfind(':') funcname = None if colon > 0: filename = arg[:colon].strip() f = lookup_module(filename) if not f: continue else: filename = f arg = arg[colon+1:].lstrip() try: lineno = int(arg) except ValueError: continue else: continue if get_breakpoint_invalid_reason(filename, lineno) is None: breakpoints.append((filename, lineno, False, cond, funcname)) return breakpoints def get_breakpoints_file_name(): from os.path import join save_path = get_save_config_path() if not save_path: return None else: return join(save_path, SAVED_BREAKPOINTS_FILE_NAME) def load_breakpoints(): """ Loads and check saved breakpoints out from files Returns: list of tuples """ from os.path import join, isdir file_names = [] for cdir in XDG_CONFIG_DIRS: if isdir(cdir): for name in [SAVED_BREAKPOINTS_FILE_NAME, BREAKPOINTS_FILE_NAME]: file_names.append(join(cdir, XDG_CONF_RESOURCE, name)) lines = [] for fname in file_names: try: rc_file = open(fname, "rt") except IOError: pass else: lines.extend([l.strip() for l in rc_file.readlines()]) rc_file.close() return parse_breakpoints(lines) def save_breakpoints(bp_list): """ :arg bp_list: a list of `bdb.Breakpoint` objects """ save_path = get_breakpoints_file_name() if not save_path: return histfile = open(get_breakpoints_file_name(), 'w') bp_list = set([(bp.file, bp.line, bp.cond) for bp in bp_list]) for bp in bp_list: line = "b %s:%d" % (bp[0], bp[1]) if bp[2]: line += ", %s" % bp[2] line += "\n" histfile.write(line) histfile.close() # }}} # vim:foldmethod=marker pudb-2017.1.4/pudb/shell.py000066400000000000000000000161641315257663400153370ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function try: import bpython # noqa # Access a property to verify module exists in case # there's a demand loader wrapping module imports # See https://github.com/inducer/pudb/issues/177 bpython.__version__ except ImportError: HAVE_BPYTHON = False else: HAVE_BPYTHON = True try: from ptpython.ipython import embed as ptipython_embed except ImportError: HAVE_PTIPYTHON = False else: HAVE_PTIPYTHON = True try: from ptpython.repl import embed as ptpython_embed, run_config except ImportError: HAVE_PTPYTHON = False else: HAVE_PTPYTHON = True try: import readline import rlcompleter HAVE_READLINE = True except ImportError: HAVE_READLINE = False # {{{ combined locals/globals dict class SetPropagatingDict(dict): """ Combine dict into one, with assignments affecting a target dict The source dicts are combined so that early dicts in the list have higher precedence. Typical usage is ``SetPropagatingDict([locals, globals], locals)``. This is used for functions like ``rlcompleter.Completer`` and ``code.InteractiveConsole``, which only take a single dictionary. This way, changes made to it are propagated to locals. Note that assigning to locals only actually works at the module level, when ``locals()`` is the same as ``globals()``, so propagation doesn't happen at all if the debugger is inside a function frame. """ def __init__(self, source_dicts, target_dict): dict.__init__(self) for s in source_dicts[::-1]: self.update(s) self.target_dict = target_dict def __setitem__(self, key, value): dict.__setitem__(self, key, value) self.target_dict[key] = value def __delitem__(self, key): dict.__delitem__(self, key) del self.target_dict[key] # }}} custom_shell_dict = {} def run_classic_shell(globals, locals, first_time=[True]): if first_time: banner = "Hit Ctrl-D to return to PuDB." first_time.pop() else: banner = "" ns = SetPropagatingDict([locals, globals], locals) from pudb.settings import get_save_config_path from os.path import join hist_file = join( get_save_config_path(), "shell-history") if HAVE_READLINE: readline.set_completer( rlcompleter.Completer(ns).complete) readline.parse_and_bind("tab: complete") readline.clear_history() try: readline.read_history_file(hist_file) except IOError: pass from code import InteractiveConsole cons = InteractiveConsole(ns) cons.interact(banner) if HAVE_READLINE: readline.write_history_file(hist_file) def run_bpython_shell(globals, locals): ns = SetPropagatingDict([locals, globals], locals) import bpython.cli bpython.cli.main(args=[], locals_=ns) # {{{ ipython def have_ipython(): # IPython has started being obnoxious on import, only import # if absolutely needed. # https://github.com/ipython/ipython/issues/9435 try: import IPython # Access a property to verify module exists in case # there's a demand loader wrapping module imports # See https://github.com/inducer/pudb/issues/177 IPython.core except (ImportError, ValueError): # Old IPythons versions (0.12?) may fail to import with # ValueError: fallback required, but not specified # https://github.com/inducer/pudb/pull/135 return False else: return True def ipython_version(): if have_ipython(): from IPython import version_info return version_info else: return None def run_ipython_shell_v10(globals, locals, first_time=[True]): '''IPython shell from IPython version 0.10''' if first_time: banner = "Hit Ctrl-D to return to PuDB." first_time.pop() else: banner = "" # avoid IPython's namespace litter ns = locals.copy() from IPython.Shell import IPShell IPShell(argv=[], user_ns=ns, user_global_ns=globals) \ .mainloop(banner=banner) def _update_ipython_ns(shell, globals, locals): '''Update the IPython 0.11 namespace at every visit''' shell.user_ns = locals.copy() try: shell.user_global_ns = globals except AttributeError: class DummyMod(object): "A dummy module used for IPython's interactive namespace." pass user_module = DummyMod() user_module.__dict__ = globals shell.user_module = user_module shell.init_user_ns() shell.init_completer() def run_ipython_shell_v11(globals, locals, first_time=[True]): '''IPython shell from IPython version 0.11''' if first_time: banner = "Hit Ctrl-D to return to PuDB." first_time.pop() else: banner = "" try: # IPython 1.0 got rid of the frontend intermediary, and complains with # a deprecated warning when you use it. from IPython.terminal.interactiveshell import TerminalInteractiveShell from IPython.terminal.ipapp import load_default_config except ImportError: from IPython.frontend.terminal.interactiveshell import \ TerminalInteractiveShell from IPython.frontend.terminal.ipapp import load_default_config # XXX: in the future it could be useful to load a 'pudb' config for the # user (if it exists) that could contain the user's macros and other # niceities. config = load_default_config() shell = TerminalInteractiveShell.instance(config=config, banner2=banner) # XXX This avoids a warning about not having unique session/line numbers. # See the HistoryManager.writeout_cache method in IPython.core.history. shell.history_manager.new_session() # Save the originating namespace old_locals = shell.user_ns old_globals = shell.user_global_ns # Update shell with current namespace _update_ipython_ns(shell, globals, locals) args = [] if ipython_version() < (5, 0, 0): args.append(banner) else: print(banner) shell.mainloop(*args) # Restore originating namespace _update_ipython_ns(shell, old_globals, old_locals) def run_ipython_shell(globals, locals): import IPython if have_ipython() and hasattr(IPython, 'Shell'): return run_ipython_shell_v10(globals, locals) else: return run_ipython_shell_v11(globals, locals) # }}} def run_ptpython_shell(globals, locals): # Use the default ptpython history import os history_filename = os.path.expanduser('~/.ptpython/history') ptpython_embed(globals=globals.copy(), locals=locals.copy(), history_filename=history_filename, configure=run_config) def run_ptipython_shell(globals, locals): # Use the default ptpython history import os history_filename = os.path.expanduser('~/.ptpython/history') ptipython_embed(globals=globals.copy(), locals=locals.copy(), history_filename=history_filename, configure=run_config) # vim: foldmethod=marker pudb-2017.1.4/pudb/source_view.py000066400000000000000000000267441315257663400165670ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function import urwid TABSTOP = 8 class SourceLine(urwid.FlowWidget): def __init__(self, dbg_ui, text, line_nr='', attr=None, has_breakpoint=False): self.dbg_ui = dbg_ui self.text = text self.attr = attr self.line_nr = line_nr self.has_breakpoint = has_breakpoint self.is_current = False self.highlight = False def selectable(self): return True def set_current(self, is_current): self.is_current = is_current self._invalidate() def set_highlight(self, highlight): self.highlight = highlight self._invalidate() def set_breakpoint(self, has_breakpoint): self.has_breakpoint = has_breakpoint self._invalidate() def rows(self, size, focus=False): return 1 def render(self, size, focus=False): from pudb.debugger import CONFIG render_line_nr = CONFIG["line_numbers"] maxcol = size[0] hscroll = self.dbg_ui.source_hscroll_start # attrs is a list of words like 'focused' and 'breakpoint' attrs = [] if self.is_current: crnt = ">" attrs.append("current") else: crnt = " " if self.has_breakpoint: bp = "*" attrs.append("breakpoint") else: bp = " " if focus: attrs.append("focused") elif self.highlight: if not self.has_breakpoint: attrs.append("highlighted") text = self.text if not attrs and self.attr is not None: attr = self.attr + [("source", None)] else: attr = [(" ".join(attrs+["source"]), None)] from urwid.util import apply_target_encoding, trim_text_attr_cs # build line prefix --------------------------------------------------- line_prefix = "" line_prefix_attr = [] if render_line_nr and self.line_nr: line_prefix_attr = [("line number", len(self.line_nr))] line_prefix = self.line_nr line_prefix = crnt+bp+line_prefix line_prefix_attr = [("source", 1), ("breakpoint marker", 1)] \ + line_prefix_attr # assume rendered width is same as len line_prefix_len = len(line_prefix) encoded_line_prefix, line_prefix_cs = apply_target_encoding(line_prefix) assert len(encoded_line_prefix) == len(line_prefix) # otherwise we'd have to adjust line_prefix_attr... :/ # shipout, encoding --------------------------------------------------- cs = [] encoded_text_segs = [] encoded_attr = [] i = 0 for seg_attr, seg_len in attr: if seg_len is None: # means: gobble up remainder of text and rest of line # and fill with attribute l = hscroll+maxcol remaining_text = text[i:] encoded_seg_text, seg_cs = apply_target_encoding( remaining_text + l*" ") encoded_attr.append((seg_attr, len(remaining_text)+l)) else: unencoded_seg_text = text[i:i+seg_len] encoded_seg_text, seg_cs = apply_target_encoding(unencoded_seg_text) adjustment = len(encoded_seg_text) - len(unencoded_seg_text) encoded_attr.append((seg_attr, seg_len + adjustment)) i += seg_len encoded_text_segs.append(encoded_seg_text) cs.extend(seg_cs) encoded_text = b"".join(encoded_text_segs) encoded_text, encoded_attr, cs = trim_text_attr_cs( encoded_text, encoded_attr, cs, hscroll, hscroll+maxcol-line_prefix_len) encoded_text = encoded_line_prefix + encoded_text encoded_attr = line_prefix_attr + encoded_attr cs = line_prefix_cs + cs return urwid.TextCanvas([encoded_text], [encoded_attr], [cs], maxcol=maxcol) def keypress(self, size, key): return key def format_source(debugger_ui, lines, breakpoints): lineno_format = "%%%dd " % (len(str(len(lines)))) try: import pygments # noqa except ImportError: return [SourceLine(debugger_ui, line.rstrip("\n\r").expandtabs(TABSTOP), lineno_format % (i+1), None, has_breakpoint=i+1 in breakpoints) for i, line in enumerate(lines)] else: from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatter import Formatter import pygments.token as t result = [] argument_parser = ArgumentParser(t) # NOTE: Tokens of the form t.Token. are not native # Pygments token types; they are user defined token # types. # # t.Token is a Pygments token creator object # (see http://pygments.org/docs/tokens/) # # The user defined token types get assigned by # one of several translation operations at the # beginning of add_snippet(). # ATTR_MAP = { # noqa: N806 t.Token: "source", t.Keyword.Namespace: "namespace", t.Token.Argument: "argument", t.Token.Dunder: "dunder", t.Token.Keyword2: 'keyword2', t.Keyword: "keyword", t.Literal: "literal", t.Name.Exception: "exception", t.Name.Function: "name", t.Name.Class: "name", t.Name.Builtin: "builtin", t.Name.Builtin.Pseudo: "pseudo", t.Punctuation: "punctuation", t.Operator: "operator", t.String: "string", # XXX: Single and Double don't actually work yet. # See https://bitbucket.org/birkenfeld/pygments-main/issue/685 t.String.Double: "doublestring", t.String.Single: "singlestring", t.String.Backtick: "backtick", t.String.Doc: "docstring", t.Comment: "comment", } # Token translation table. Maps token types and their # associated strings to new token types. ATTR_TRANSLATE = { # noqa: N806 t.Keyword: { 'class': t.Token.Keyword2, 'def': t.Token.Keyword2, 'exec': t.Token.Keyword2, 'lambda': t.Token.Keyword2, 'print': t.Token.Keyword2, }, t.Operator: { '.': t.Token, }, t.Name.Builtin.Pseudo: { 'self': t.Token, }, t.Name.Builtin: { 'object': t.Name.Class, }, } class UrwidFormatter(Formatter): def __init__(subself, **options): # noqa: N805 Formatter.__init__(subself, **options) subself.current_line = "" subself.current_attr = [] subself.lineno = 1 def format(subself, tokensource, outfile): # noqa: N805 def add_snippet(ttype, s): if not s: return # Find function arguments. When found, change their # ttype to t.Token.Argument new_ttype = argument_parser.parse_token(ttype, s) if new_ttype: ttype = new_ttype # Translate tokens if ttype in ATTR_TRANSLATE: if s in ATTR_TRANSLATE[ttype]: ttype = ATTR_TRANSLATE[ttype][s] # Translate dunder method tokens if ttype == ( t.Name.Function and s.startswith('__') and s.endswith('__') ): ttype = t.Token.Dunder while ttype not 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(debugger_ui, subself.current_line, lineno_format % subself.lineno, subself.current_attr, has_breakpoint=subself.lineno in breakpoints)) subself.current_line = "" subself.current_attr = [] subself.lineno += 1 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(l.expandtabs(TABSTOP) for l in lines), PythonLexer(stripnl=False), UrwidFormatter()) return result class ParseState(object): '''States for the ArgumentParser class''' idle = 1 found_function = 2 found_open_paren = 3 class ArgumentParser(object): '''Parse source code tokens and identify function arguments. This parser implements a state machine which accepts Pygments tokens, delivered sequentially from the beginning of a source file to its end. parse_token() processes each token (and its associated string) and returns None if that token does not require modification. When it finds a token which represents a function argument, it returns the correct token type for that item (the caller should then replace the associated item's token type with the returned type) ''' def __init__(self, pygments_token): self.t = pygments_token self.state = ParseState.idle self.paren_level = 0 def parse_token(self, token, s): '''Parse token. Return None or replacement token type''' if self.state == ParseState.idle: if token is self.t.Name.Function: self.state = ParseState.found_function self.paren_level = 0 elif self.state == ParseState.found_function: if token is self.t.Punctuation and s == '(': self.state = ParseState.found_open_paren self.paren_level = 1 else: if ((token is self.t.Name) or (token is self.t.Name.Builtin.Pseudo and s == 'self')): return self.t.Token.Argument elif token is self.t.Punctuation and s == ')': self.paren_level -= 1 elif token is self.t.Punctuation and s == '(': self.paren_level += 1 if self.paren_level == 0: self.state = ParseState.idle return None pudb-2017.1.4/pudb/theme.py000066400000000000000000001142141315257663400153250ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function THEMES = [ "classic", "vim", "dark vim", "midnight", "solarized", "agr-256", "monokai", "monokai-256" ] from pudb.py3compat import execfile, raw_input import urwid def get_palette(may_use_fancy_formats, theme="classic"): if may_use_fancy_formats: def add_setting(color, setting): return color+","+setting else: def add_setting(color, setting): return color # ------------------------------------------------------------------------------ # Reference for some palette items: # # "namespace" : "import", "from", "using" # "operator" : "+", "-", "=" etc. # NOTE: Does not include ".", which is assigned the type "source" # "argument" : Function arguments # "builtin" : "range", "dict", "set", "list", etc. # "pseudo" : "None", "True", "False" # NOTE: Does not include "self", which is assigned the # type "source" # "dunder" : Class method names of the form ____ within # a class definition # "exception" : Exception names # "keyword" : All keywords except those specifically assigned to "keyword2" # ("from", "and", "break", "is", "try", "pass", etc.) # "keyword2" : "class", "def", "exec", "lambda", "print" # ------------------------------------------------------------------------------ inheritance_map = ( # Style Inherits from # ---------- ---------- ("namespace", "keyword"), ("operator", "source"), ("argument", "source"), ("builtin", "source"), ("pseudo", "source"), ("dunder", "name"), ("exception", "source"), ("keyword2", "keyword") ) palette_dict = { # The following styles are initialized to "None". Themes # (including custom Themes) may set them as needed. # If they are not set by a theme, then they will # inherit from other styles in accordance with # the inheritance_map. "namespace": None, "operator": None, "argument": None, "builtin": None, "pseudo": None, "dunder": None, "exception": None, "keyword2": None, # {{{ ui "header": ("black", "light gray", "standout"), "selectable": ("black", "dark cyan"), "focused selectable": ("black", "dark green"), "button": (add_setting("white", "bold"), "dark blue"), "focused button": ("light cyan", "black"), "dialog title": (add_setting("white", "bold"), "dark cyan"), "background": ("black", "light gray"), "hotkey": (add_setting("black", "underline"), "light gray", "underline"), "focused sidebar": (add_setting("yellow", "bold"), "light gray", "standout"), "warning": (add_setting("white", "bold"), "dark red", "standout"), "label": ("black", "light gray"), "value": (add_setting("yellow", "bold"), "dark blue"), "fixed value": ("light gray", "dark blue"), "group head": (add_setting("dark blue", "bold"), "light gray"), "search box": ("black", "dark cyan"), "search not found": ("white", "dark red"), # }}} # {{{ shell "command line edit": (add_setting("yellow", "bold"), "dark blue"), "command line prompt": (add_setting("white", "bold"), "dark blue"), "command line output": ("light cyan", "dark blue"), "command line input": (add_setting("light cyan", "bold"), "dark blue"), "command line error": (add_setting("light red", "bold"), "dark blue"), "focused command line output": ("black", "dark green"), "focused command line input": ( add_setting("light cyan", "bold"), "dark green"), "focused command line error": ("black", "dark green"), "command line clear button": (add_setting("white", "bold"), "dark blue"), "command line focused button": ("light cyan", "black"), # }}} # {{{ source "breakpoint": ("black", "dark cyan"), "disabled breakpoint": ("dark gray", "dark cyan"), "focused breakpoint": ("black", "dark green"), "focused disabled breakpoint": ("dark gray", "dark green"), "current breakpoint": (add_setting("white", "bold"), "dark cyan"), "disabled current breakpoint": ( add_setting("dark gray", "bold"), "dark cyan"), "focused current breakpoint": ( add_setting("white", "bold"), "dark green", "bold"), "focused disabled current breakpoint": ( add_setting("dark gray", "bold"), "dark green", "bold"), "source": (add_setting("yellow", "bold"), "dark blue"), "focused source": ("black", "dark green"), "highlighted source": ("black", "dark magenta"), "current source": ("black", "dark cyan"), "current focused source": (add_setting("white", "bold"), "dark cyan"), "current highlighted source": ("white", "dark cyan"), # {{{ highlighting "line number": ("light gray", "dark blue"), "keyword": (add_setting("white", "bold"), "dark blue"), "name": ("light cyan", "dark blue"), "literal": ("light magenta, bold", "dark blue"), "string": (add_setting("light magenta", "bold"), "dark blue"), "doublestring": (add_setting("light magenta", "bold"), "dark blue"), "singlestring": (add_setting("light magenta", "bold"), "dark blue"), "docstring": (add_setting("light magenta", "bold"), "dark blue"), "punctuation": ("light gray", "dark blue"), "comment": ("light gray", "dark blue"), # }}} # }}} # {{{ breakpoints "breakpoint marker": ("dark red", "dark blue"), "breakpoint source": (add_setting("yellow", "bold"), "dark red"), "breakpoint focused source": ("black", "dark red"), "current breakpoint source": ("black", "dark red"), "current breakpoint focused source": ("white", "dark red"), # }}} # {{{ variables view "variables": ("black", "dark cyan"), "variable separator": ("dark cyan", "light gray"), "var label": ("dark blue", "dark cyan"), "var value": ("black", "dark cyan"), "focused var label": ("dark blue", "dark green"), "focused var value": ("black", "dark green"), "highlighted var label": ("white", "dark cyan"), "highlighted var value": ("black", "dark cyan"), "focused highlighted var label": ("white", "dark green"), "focused highlighted var value": ("black", "dark green"), "return label": ("white", "dark blue"), "return value": ("black", "dark cyan"), "focused return label": ("light gray", "dark blue"), "focused return value": ("black", "dark green"), # }}} # {{{ stack "stack": ("black", "dark cyan"), "frame name": ("black", "dark cyan"), "focused frame name": ("black", "dark green"), "frame class": ("dark blue", "dark cyan"), "focused frame class": ("dark blue", "dark green"), "frame location": ("light cyan", "dark cyan"), "focused frame location": ("light cyan", "dark green"), "current frame name": (add_setting("white", "bold"), "dark cyan"), "focused current frame name": (add_setting("white", "bold"), "dark green", "bold"), "current frame class": ("dark blue", "dark cyan"), "focused current frame class": ("dark blue", "dark green"), "current frame location": ("light cyan", "dark cyan"), "focused current frame location": ("light cyan", "dark green"), # }}} } if theme == "classic": pass elif theme == "vim": # {{{ vim theme palette_dict.update({ "source": ("black", "default"), "keyword": ("brown", "default"), "kw_namespace": ("dark magenta", "default"), "literal": ("black", "default"), "string": ("dark red", "default"), "doublestring": ("dark red", "default"), "singlestring": ("dark red", "default"), "docstring": ("dark red", "default"), "punctuation": ("black", "default"), "comment": ("dark blue", "default"), "classname": ("dark cyan", "default"), "name": ("dark cyan", "default"), "line number": ("dark gray", "default"), "breakpoint marker": ("dark red", "default"), # {{{ shell "command line edit": ("black", "default"), "command line prompt": (add_setting("black", "bold"), "default"), "command line output": (add_setting("black", "bold"), "default"), "command line input": ("black", "default"), "command line error": (add_setting("light red", "bold"), "default"), "focused command line output": ("black", "dark green"), "focused command line input": (add_setting("light cyan", "bold"), "dark green"), "focused command line error": ("black", "dark green"), # }}} }) # }}} elif theme == "dark vim": # {{{ dark vim palette_dict.update({ "header": ("black", "light gray", "standout"), # {{{ variables view "variables": ("black", "dark gray"), "variable separator": ("dark cyan", "light gray"), "var label": ("light gray", "dark gray"), "var value": ("white", "dark gray"), "focused var label": ("light gray", "light blue"), "focused var value": ("white", "light blue"), "highlighted var label": ("light gray", "dark green"), "highlighted var value": ("white", "dark green"), "focused highlighted var label": ("light gray", "light blue"), "focused highlighted var value": ("white", "light blue"), "return label": ("light gray", "dark gray"), "return value": ("light cyan", "dark gray"), "focused return label": ("yellow", "light blue"), "focused return value": ("white", "light blue"), # }}} # {{{ stack view "stack": ("black", "dark gray"), "frame name": ("light gray", "dark gray"), "focused frame name": ("light gray", "light blue"), "frame class": ("dark blue", "dark gray"), "focused frame class": ("dark blue", "light blue"), "frame location": ("white", "dark gray"), "focused frame location": ("white", "light blue"), "current frame name": (add_setting("white", "bold"), "dark gray"), "focused current frame name": (add_setting("white", "bold"), "light blue", "bold"), "current frame class": ("dark blue", "dark gray"), "focused current frame class": ("dark blue", "dark green"), "current frame location": ("light cyan", "dark gray"), "focused current frame location": ("light cyan", "light blue"), # }}} # {{{ breakpoint view "breakpoint": ("light gray", "dark gray"), "disabled breakpoint": ("black", "dark gray"), "focused breakpoint": ("light gray", "light blue"), "focused disabled breakpoint": ("black", "light blue"), "current breakpoint": (add_setting("white", "bold"), "dark gray"), "disabled current breakpoint": ("black", "dark gray"), "focused current breakpoint": (add_setting("white", "bold"), "light blue"), "focused disabled current breakpoint": ("black", "light blue"), # }}} # {{{ ui widgets "selectable": ("light gray", "dark gray"), "focused selectable": ("white", "light blue"), "button": ("light gray", "dark gray"), "focused button": ("white", "light blue"), "background": ("black", "light gray"), "hotkey": (add_setting("black", "underline"), "light gray", "underline"), "focused sidebar": ("light blue", "light gray", "standout"), "warning": (add_setting("white", "bold"), "dark red", "standout"), "label": ("black", "light gray"), "value": ("white", "dark gray"), "fixed value": ("light gray", "dark gray"), "search box": ("white", "dark gray"), "search not found": ("white", "dark red"), "dialog title": (add_setting("white", "bold"), "dark gray"), # }}} # {{{ source view "breakpoint marker": ("dark red", "black"), "breakpoint source": ("light gray", "dark red"), "breakpoint focused source": ("black", "dark red"), "current breakpoint source": ("black", "dark red"), "current breakpoint focused source": ("white", "dark red"), # }}} # {{{ highlighting "source": ("white", "black"), "focused source": ("white", "light blue"), "highlighted source": ("black", "dark magenta"), "current source": ("black", "light gray"), "current focused source": ("white", "dark cyan"), "current highlighted source": ("white", "dark cyan"), "line number": ("dark gray", "black"), "keyword": ("yellow", "black"), "literal": ("dark magenta", "black"), "string": ("dark magenta", "black"), "doublestring": ("dark magenta", "black"), "singlestring": ("dark magenta", "black"), "docstring": ("dark magenta", "black"), "name": ("light cyan", "black"), "punctuation": ("yellow", "black"), "comment": ("light blue", "black"), # }}} # {{{ shell "command line edit": ("white", "black"), "command line prompt": (add_setting("yellow", "bold"), "black"), "command line output": (add_setting("yellow", "bold"), "black"), "command line input": ("white", "black"), "command line error": (add_setting("light red", "bold"), "black"), "focused command line output": ("black", "light blue"), "focused command line input": (add_setting("light cyan", "bold"), "light blue"), "focused command line error": ("black", "light blue"), # }}} }) # }}} elif theme == "midnight": # {{{ midnight # Based on XCode's midnight theme # Looks best in a console with green text against black background palette_dict.update({ "variables": ("white", "default"), "var label": ("light blue", "default"), "var value": ("white", "default"), "stack": ("white", "default"), "frame name": ("white", "default"), "frame class": ("dark blue", "default"), "frame location": ("light cyan", "default"), "current frame name": (add_setting("white", "bold"), "default"), "current frame class": ("dark blue", "default"), "current frame location": ("light cyan", "default"), "focused frame name": ("black", "dark green"), "focused frame class": (add_setting("white", "bold"), "dark green"), "focused frame location": ("dark blue", "dark green"), "focused current frame name": ("black", "dark green"), "focused current frame class": ( add_setting("white", "bold"), "dark green"), "focused current frame location": ("dark blue", "dark green"), "search box": ("default", "default"), "breakpoint": ("white", "default"), "disabled breakpoint": ("dark gray", "default"), "focused breakpoint": ("black", "dark green"), "focused disabled breakpoint": ("dark gray", "dark green"), "current breakpoint": (add_setting("white", "bold"), "default"), "disabled current breakpoint": ( add_setting("dark gray", "bold"), "default"), "focused current breakpoint": ( add_setting("white", "bold"), "dark green", "bold"), "focused disabled current breakpoint": ( add_setting("dark gray", "bold"), "dark green", "bold"), "source": ("white", "default"), "highlighted source": ("white", "light cyan"), "current source": ("white", "light gray"), "current focused source": ("white", "brown"), "line number": ("light gray", "default"), "keyword": ("dark magenta", "default"), "name": ("white", "default"), "literal": ("dark cyan", "default"), "string": ("dark red", "default"), "doublestring": ("dark red", "default"), "singlestring": ("light blue", "default"), "docstring": ("light red", "default"), "backtick": ("light green", "default"), "punctuation": ("white", "default"), "comment": ("dark green", "default"), "classname": ("dark cyan", "default"), "funcname": ("white", "default"), "breakpoint marker": ("dark red", "default"), # {{{ shell "command line edit": ("white", "default"), "command line prompt": (add_setting("white", "bold"), "default"), "command line output": (add_setting("white", "bold"), "default"), "command line input": (add_setting("white", "bold"), "default"), "command line error": (add_setting("light red", "bold"), "default"), "focused command line output": ("black", "dark green"), "focused command line input": ( add_setting("white", "bold"), "dark green"), "focused command line error": ("black", "dark green"), "command line clear button": (add_setting("white", "bold"), "default"), "command line focused button": ("black", "light gray"), # White # doesn't work in curses mode # }}} }) # }}} elif theme == "solarized": # {{{ solarized palette_dict.update({ # UI "header": ("black", "light blue", "standout"), "focused sidebar": ("yellow", "light blue", "standout"), "group head": ("black", "light blue"), "background": ("black", "light blue"), "label": ("black", "light blue"), "value": ("white", "dark blue"), "fixed value": ("black", "light blue"), "variables": ("light blue", "default"), "var label": ("dark blue", "default"), "var value": ("light blue", "default"), "focused var label": ("white", "dark blue"), "focused var value": ("black", "dark blue"), "highlighted var label": ("white", "light green"), "highlighted var value": ("white", "light green"), "focused highlighted var label": ("white", "light green"), "focused highlighted var value": ("white", "light green"), "stack": ("light blue", "default"), "frame name": ("dark blue", "default"), "frame class": ("light blue", "default"), "frame location": ("light green", "default"), "focused frame name": ("white", "dark blue"), "focused frame class": ("black", "dark blue"), "focused frame location": ("dark gray", "dark blue"), "focused current frame name": ("white", "light green"), "focused current frame class": ("black", "light green"), "focused current frame location": ("dark gray", "light green"), "current frame name": ("white", "light green"), "current frame class": ("black", "light green"), "current frame location": ("dark gray", "light green"), # breakpoints "breakpoint": ("light blue", "default"), "disabled breakpoint": ("light gray", "default"), "focused breakpoint": ("white", "light green"), "focused disabled breakpoint": ("light gray", "light green"), "current breakpoint": ("white", "dark blue"), "disabled current breakpoint": ("light gray", "dark blue"), "focused current breakpoint": ("white", "light green"), "focused disabled current breakpoint": ("light gray", "light green"), # source "breakpoint source": ("light blue", "black"), "current breakpoint source": ("black", "light green"), "breakpoint focused source": ("dark gray", "dark blue"), "current breakpoint focused source": ("black", "light green"), "breakpoint marker": ("dark red", "default"), "search box": ("default", "default"), "source": ("light blue", "default"), "current source": ("light gray", "light blue"), "current focused source": ("light gray", "light blue"), "focused source": ("dark gray", "dark blue"), "current highlighted source": ("black", "dark cyan"), "highlighted source": ("light blue", "black"), "line number": ("light blue", "default"), "keyword": ("dark green", "default"), "name": ("light blue", "default"), "literal": ("dark cyan", "default"), "string": ("dark cyan", "default"), "doublestring": ("dark cyan", "default"), "singlestring": ("light blue", "default"), "docstring": ("dark cyan", "default"), "backtick": ("light green", "default"), "punctuation": ("light blue", "default"), "comment": ("light green", "default"), "classname": ("dark blue", "default"), "funcname": ("dark blue", "default"), # shell "command line edit": ("light blue", "default"), "command line prompt": ("light blue", "default"), "command line output": ("light blue", "default"), "command line input": ("light blue", "default"), "command line error": ("dark red", "default"), "focused command line output": ("black", "light green"), "focused command line input": ("black", "light green"), "focused command line error": ("dark red", "light blue"), "command line clear button": ("light blue", "default"), "command line focused button": ("black", "light blue"), }) # }}} elif theme == "agr-256": # {{{ agr-256 palette_dict.update({ "header": ("h235", "h252", "standout"), # {{{ variables view "variables": ("h235", "h233"), "variable separator": ("h23", "h252"), "var label": ("h111", "h233"), "var value": ("h255", "h233"), "focused var label": ("h192", "h24"), "focused var value": ("h192", "h24"), "highlighted var label": ("h252", "h22"), "highlighted var value": ("h255", "h22"), "focused highlighted var label": ("h252", "h64"), "focused highlighted var value": ("h255", "h64"), "return label": ("h113", "h233"), "return value": ("h113", "h233"), "focused return label": (add_setting("h192", "bold"), "h24"), "focused return value": ("h192", "h24"), # }}} # {{{ stack view "stack": ("h235", "h233"), "frame name": ("h192", "h233"), "focused frame name": ("h192", "h24"), "frame class": ("h111", "h233"), "focused frame class": ("h192", "h24"), "frame location": ("h252", "h233"), "focused frame location": ("h192", "h24"), "current frame name": ("h255", "h22"), "focused current frame name": ("h255", "h64"), "current frame class": ("h111", "h22"), "focused current frame class": ("h255", "h64"), "current frame location": ("h252", "h22"), "focused current frame location": ("h255", "h64"), # }}} # {{{ breakpoint view "breakpoint": ("h80", "h233"), "disabled breakpoint": ("h60", "h233"), "focused breakpoint": ("h192", "h24"), "focused disabled breakpoint": ("h182", "h24"), "current breakpoint": (add_setting("h255", "bold"), "h22"), "disabled current breakpoint": (add_setting("h016", "bold"), "h22"), "focused current breakpoint": (add_setting("h255", "bold"), "h64"), "focused disabled current breakpoint": ( add_setting("h016", "bold"), "h64"), # }}} # {{{ ui widgets "selectable": ("h252", "h235"), "focused selectable": ("h255", "h24"), "button": ("h252", "h235"), "focused button": ("h255", "h24"), "background": ("h235", "h252"), "hotkey": (add_setting("h235", "underline"), "h252", "underline"), "focused sidebar": ("h23", "h252", "standout"), "warning": (add_setting("h255", "bold"), "h124", "standout"), "label": ("h235", "h252"), "value": ("h255", "h17"), "fixed value": ("h252", "h17"), "group head": (add_setting("h25", "bold"), "h252"), "search box": ("h255", "h235"), "search not found": ("h255", "h124"), "dialog title": (add_setting("h255", "bold"), "h235"), # }}} # {{{ source view "breakpoint marker": ("h160", "h235"), "breakpoint source": ("h252", "h124"), "breakpoint focused source": ("h192", "h124"), "current breakpoint source": ("h192", "h124"), "current breakpoint focused source": ( add_setting("h192", "bold"), "h124"), # }}} # {{{ highlighting "source": ("h255", "h235"), "focused source": ("h192", "h24"), "highlighted source": ("h252", "h22"), "current source": (add_setting("h252", "bold"), "h23"), "current focused source": (add_setting("h192", "bold"), "h23"), "current highlighted source": ("h255", "h22"), "line number": ("h241", "h235"), "keyword": ("h111", "h235"), "literal": ("h173", "h235"), "string": ("h113", "h235"), "doublestring": ("h113", "h235"), "singlestring": ("h113", "h235"), "docstring": ("h113", "h235"), "name": ("h192", "h235"), "punctuation": ("h223", "h235"), "comment": ("h246", "h235"), # }}} # {{{ shell "command line edit": ("h255", "h233"), "command line prompt": (add_setting("h192", "bold"), "h233"), "command line output": ("h80", "h233"), "command line input": ("h255", "h233"), "command line error": ("h160", "h233"), "focused command line output": (add_setting("h192", "bold"), "h24"), "focused command line input": ("h255", "h24"), "focused command line error": ("h235", "h24"), "command line clear button": (add_setting("h255", "bold"), "h233"), "command line focused button": ("h255", "h24"), # }}} }) # }}} elif theme == "monokai": # {{{ midnight # Based on XCode's midnight theme # Looks best in a console with green text against black background palette_dict.update({ "variables": ("white", "default"), "var label": ("light blue", "default"), "var value": ("white", "default"), "stack": ("white", "default"), "frame name": ("white", "default"), "frame class": ("dark blue", "default"), "frame location": ("light cyan", "default"), "current frame name": (add_setting("white", "bold"), "default"), "current frame class": ("dark blue", "default"), "current frame location": ("light cyan", "default"), "focused frame name": ("black", "dark green"), "focused frame class": (add_setting("white", "bold"), "dark green"), "focused frame location": ("dark blue", "dark green"), "focused current frame name": ("black", "dark green"), "focused current frame class": ( add_setting("white", "bold"), "dark green"), "focused current frame location": ("dark blue", "dark green"), "search box": ("default", "default"), "breakpoint": ("white", "default"), "disabled breakpoint": ("dark gray", "default"), "focused breakpoint": ("black", "dark green"), "focused disabled breakpoint": ("dark gray", "dark green"), "current breakpoint": (add_setting("white", "bold"), "default"), "disabled current breakpoint": ( add_setting("dark gray", "bold"), "default"), "focused current breakpoint": ( add_setting("white", "bold"), "dark green", "bold"), "focused disabled current breakpoint": ( add_setting("dark gray", "bold"), "dark green", "bold"), "source": ("white", "default"), "highlighted source": ("white", "light cyan"), "current source": ("white", "light gray"), "current focused source": ("white", "brown"), "line number": ("dark gray", "black"), "keyword2": ("light cyan", "black"), "name": ("light green", "black"), "literal": ("light magenta", "black"), "namespace": ("light red", "black"), "operator": ("light red", "black"), "argument": ("brown", "black"), "builtin": ("light cyan", "black"), "pseudo": ("light magenta", "black"), "dunder": ("light cyan", "black"), "exception": ("light cyan", "black"), "keyword": ("light red", "black"), "string": ("dark red", "default"), "doublestring": ("dark red", "default"), "singlestring": ("light blue", "default"), "docstring": ("light red", "default"), "backtick": ("light green", "default"), "punctuation": ("white", "default"), "comment": ("dark green", "default"), "classname": ("dark cyan", "default"), "funcname": ("white", "default"), "breakpoint marker": ("dark red", "default"), # {{{ shell "command line edit": ("white", "default"), "command line prompt": (add_setting("white", "bold"), "default"), "command line output": (add_setting("white", "bold"), "default"), "command line input": (add_setting("white", "bold"), "default"), "command line error": (add_setting("light red", "bold"), "default"), "focused command line output": ("black", "dark green"), "focused command line input": ( add_setting("white", "bold"), "dark green"), "focused command line error": ("black", "dark green"), "command line clear button": (add_setting("white", "bold"), "default"), "command line focused button": ("black", "light gray"), # White # doesn't work in curses mode # }}} }) # }}} elif theme == "monokai-256": # {{{ monokai-256 palette_dict.update({ "header": ("h235", "h252", "standout"), # {{{ variables view "variables": ("h235", "h233"), "variable separator": ("h23", "h252"), "var label": ("h111", "h233"), "var value": ("h255", "h233"), "focused var label": ("h237", "h172"), "focused var value": ("h237", "h172"), "highlighted var label": ("h252", "h22"), "highlighted var value": ("h255", "h22"), "focused highlighted var label": ("h252", "h64"), "focused highlighted var value": ("h255", "h64"), "return label": ("h113", "h233"), "return value": ("h113", "h233"), "focused return label": (add_setting("h192", "bold"), "h24"), "focused return value": ("h237", "h172"), # }}} # {{{ stack view "stack": ("h235", "h233"), "frame name": ("h192", "h233"), "focused frame name": ("h237", "h172"), "frame class": ("h111", "h233"), "focused frame class": ("h237", "h172"), "frame location": ("h252", "h233"), "focused frame location": ("h237", "h172"), "current frame name": ("h255", "h22"), "focused current frame name": ("h255", "h64"), "current frame class": ("h111", "h22"), "focused current frame class": ("h255", "h64"), "current frame location": ("h252", "h22"), "focused current frame location": ("h255", "h64"), # }}} # {{{ breakpoint view "breakpoint": ("h80", "h233"), "disabled breakpoint": ("h60", "h233"), "focused breakpoint": ("h237", "h172"), "focused disabled breakpoint": ("h182", "h24"), "current breakpoint": (add_setting("h255", "bold"), "h22"), "disabled current breakpoint": (add_setting("h016", "bold"), "h22"), "focused current breakpoint": (add_setting("h255", "bold"), "h64"), "focused disabled current breakpoint": ( add_setting("h016", "bold"), "h64"), # }}} # {{{ ui widgets "selectable": ("h252", "h235"), "focused selectable": ("h255", "h24"), "button": ("h252", "h235"), "focused button": ("h255", "h24"), "background": ("h235", "h252"), "hotkey": (add_setting("h235", "underline"), "h252", "underline"), "focused sidebar": ("h23", "h252", "standout"), "warning": (add_setting("h255", "bold"), "h124", "standout"), "label": ("h235", "h252"), "value": ("h255", "h17"), "fixed value": ("h252", "h17"), "group head": (add_setting("h25", "bold"), "h252"), "search box": ("h255", "h235"), "search not found": ("h255", "h124"), "dialog title": (add_setting("h255", "bold"), "h235"), # }}} # {{{ source view "breakpoint marker": ("h160", "h235"), "breakpoint source": ("h252", "h124"), "breakpoint focused source": ("h192", "h124"), "current breakpoint source": ("h192", "h124"), "current breakpoint focused source": ( add_setting("h192", "bold"), "h124"), # }}} # {{{ highlighting "source": ("h255", "h235"), "focused source": ("h237", "h172"), "highlighted source": ("h252", "h22"), "current source": (add_setting("h252", "bold"), "h23"), "current focused source": (add_setting("h192", "bold"), "h23"), "current highlighted source": ("h255", "h22"), "line number": ("h241", "h235"), "keyword2": ("h51", "h235"), "name": ("h155", "h235"), "literal": ("h141", "h235"), "namespace": ("h198", "h235"), "operator": ("h198", "h235"), "argument": ("h208", "h235"), "builtin": ("h51", "h235"), "pseudo": ("h141", "h235"), "dunder": ("h51", "h235"), "exception": ("h51", "h235"), "keyword": ("h198", "h235"), "string": ("h228", "h235"), "doublestring": ("h228", "h235"), "singlestring": ("h228", "h235"), "docstring": ("h243", "h235"), "punctuation": ("h255", "h235"), "comment": ("h243", "h235"), # }}} # {{{ shell "command line edit": ("h255", "h233"), "command line prompt": (add_setting("h192", "bold"), "h233"), "command line output": ("h80", "h233"), "command line input": ("h255", "h233"), "command line error": ("h160", "h233"), "focused command line output": (add_setting("h192", "bold"), "h24"), "focused command line input": ("h255", "h24"), "focused command line error": ("h235", "h24"), "command line clear button": (add_setting("h255", "bold"), "h233"), "command line focused button": ("h255", "h24"), # }}} }) # }}} else: try: symbols = { "palette": palette_dict, "add_setting": add_setting, } from os.path import expanduser, expandvars execfile(expanduser(expandvars(theme)), symbols) except: print("Error when importing theme:") from traceback import print_exc print_exc() raw_input("Hit enter:") # Apply style inheritance for child, parent in inheritance_map: if palette_dict[child] is None: palette_dict[child] = palette_dict[parent] palette_list = [] for setting_name, color_values in palette_dict.items(): fg_color = color_values[0].lower().strip() bg_color = color_values[1].lower().strip() # Convert hNNN syntax to equivalent #RGB value # (https://github.com/wardi/urwid/issues/24) if fg_color.startswith('h') or bg_color.startswith('h'): attr = urwid.AttrSpec(fg_color, bg_color, colors=256) palette_list.append((setting_name, 'default', 'default', 'default', attr.foreground, attr.background)) else: palette_list.append((setting_name,) + color_values) return palette_list # vim: foldmethod=marker pudb-2017.1.4/pudb/ui_tools.py000066400000000000000000000220171315257663400160570ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function import urwid from urwid.util import _target_encoding, calc_width # generic urwid helpers ------------------------------------------------------- def make_canvas(txt, attr, maxcol, fill_attr=None): processed_txt = [] processed_attr = [] processed_cs = [] for line, line_attr in zip(txt, attr): # filter out zero-length attrs line_attr = [(aname, l) for aname, l in line_attr if l > 0] diff = maxcol - calc_width(line, 0, len(line)) if diff > 0: line += " "*diff line_attr.append((fill_attr, diff)) else: from urwid.util import rle_subseg line = line[:maxcol] line_attr = rle_subseg(line_attr, 0, maxcol) from urwid.util import apply_target_encoding encoded_line, line_cs = apply_target_encoding(line) # line_cs contains byte counts as requested by TextCanvas, but # line_attr still contains column counts at this point: let's fix this. def get_byte_line_attr(line, line_attr): i = 0 for label, column_count in line_attr: byte_count = len(line[i:i+column_count].encode(_target_encoding)) i += column_count yield label, byte_count line_attr = list(get_byte_line_attr(line, line_attr)) processed_txt.append(encoded_line) processed_attr.append(line_attr) processed_cs.append(line_cs) return urwid.TextCanvas( processed_txt, processed_attr, processed_cs, maxcol=maxcol) 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)), ] def labelled_value(label, value): return urwid.AttrMap(urwid.Text([ ("label", label), str(value)]), "fixed value", "fixed value") class SelectableText(urwid.Text): def selectable(self): return True def keypress(self, size, key): return key class SignalWrap(urwid.WidgetWrap): def __init__(self, w, is_preemptive=False): urwid.WidgetWrap.__init__(self, w) self.event_listeners = [] self.is_preemptive = is_preemptive def listen(self, mask, handler): self.event_listeners.append((mask, handler)) def keypress(self, size, key): result = key if self.is_preemptive: for mask, handler in self.event_listeners: if mask is None or mask == key: result = handler(self, size, key) break if result is not None: result = self._w.keypress(size, key) if result is not None and not self.is_preemptive: for mask, handler in self.event_listeners: if mask is None or mask == key: return handler(self, size, key) return result # {{{ debugger-specific stuff class StackFrame(urwid.FlowWidget): def __init__(self, is_current, name, class_name, filename, line): self.is_current = is_current self.name = name self.class_name = class_name self.filename = filename self.line = line def selectable(self): return True def rows(self, size, focus=False): return 1 def render(self, size, focus=False): maxcol = size[0] if focus: apfx = "focused " else: apfx = "" if self.is_current: apfx += "current " crnt_pfx = ">> " else: crnt_pfx = " " text = crnt_pfx+self.name attr = [(apfx+"frame name", 3+len(self.name))] if self.class_name is not None: text += " [%s]" % self.class_name attr.append((apfx+"frame class", len(self.class_name)+3)) loc = " %s:%d" % (self.filename, self.line) text += loc attr.append((apfx+"frame location", len(loc))) return make_canvas([text], [attr], maxcol, apfx+"frame location") def keypress(self, size, key): return key class BreakpointFrame(urwid.FlowWidget): def __init__(self, is_current, filename, breakpoint): self.is_current = is_current self.filename = filename self.breakpoint = breakpoint self.line = breakpoint.line # Starts at 1 self.enabled = breakpoint.enabled self.hits = breakpoint.hits def selectable(self): return True def rows(self, size, focus=False): return 1 def render(self, size, focus=False): maxcol = size[0] if focus: apfx = "focused " else: apfx = "" bp_pfx = '' if not self.enabled: apfx += "disabled " bp_pfx += "X" if self.is_current: apfx += "current " bp_pfx += ">>" bp_pfx = bp_pfx.ljust(3) hits_label = 'hits' if self.hits != 1 else 'hit' loc = " %s:%d (%s %s)" % (self.filename, self.line, self.hits, hits_label) text = bp_pfx+loc attr = [(apfx+"breakpoint", len(loc))] return make_canvas([text], [attr], maxcol, apfx+"breakpoint") def keypress(self, size, key): return key class SearchController(object): def __init__(self, ui): self.ui = ui self.highlight_line = None self.search_box = None self.last_search_string = None def cancel_highlight(self): if self.highlight_line is not None: self.highlight_line.set_highlight(False) self.highlight_line = None def cancel_search(self): self.cancel_highlight() self.hide_search_ui() def hide_search_ui(self): self.search_box = None del self.ui.lhs_col.contents[0] self.ui.lhs_col.set_focus(self.ui.lhs_col.widget_list[0]) def open_search_ui(self): lhs_col = self.ui.lhs_col if self.search_box is None: _, self.search_start = self.ui.source.get_focus() self.search_box = SearchBox(self) self.search_AttrMap = urwid.AttrMap( self.search_box, "search box") lhs_col.item_types.insert( 0, ("flow", None)) lhs_col.widget_list.insert(0, self.search_AttrMap) self.ui.columns.set_focus(lhs_col) lhs_col.set_focus(self.search_AttrMap) else: self.ui.columns.set_focus(lhs_col) lhs_col.set_focus(self.search_AttrMap) #self.search_box.restart_search() def perform_search(self, dir, s=None, start=None, update_search_start=False): self.cancel_highlight() # self.ui.lhs_col.set_focus(self.ui.lhs_col.widget_list[1]) if s is None: s = self.last_search_string if s is None: self.ui.message("No previous search term.") return False else: self.last_search_string = s if start is None: start = self.search_start case_insensitive = s.lower() == s if start > len(self.ui.source): start = 0 i = (start+dir) % len(self.ui.source) if i >= len(self.ui.source): i = 0 while i != start: sline = self.ui.source[i].text if case_insensitive: sline = sline.lower() if s in sline: sl = self.ui.source[i] sl.set_highlight(True) self.highlight_line = sl self.ui.source.set_focus(i) if update_search_start: self.search_start = i return True i = (i+dir) % len(self.ui.source) return False class SearchBox(urwid.Edit): def __init__(self, controller): urwid.Edit.__init__(self, [("label", "Search: ")], "") self.controller = controller def restart_search(self): from time import time now = time() if self.search_start_time > 5: self.set_edit_text("") self.search_time = now def keypress(self, size, key): result = urwid.Edit.keypress(self, size, key) txt = self.get_edit_text() if result is not None: if key == "esc": self.controller.cancel_search() return None elif key == "enter": if txt: self.controller.hide_search_ui() self.controller.perform_search(dir=1, s=txt, update_search_start=True) else: self.controller.cancel_search() return None else: if self.controller.perform_search(dir=1, s=txt): self.controller.search_AttrMap.set_attr_map({None: "search box"}) else: self.controller.search_AttrMap.set_attr_map( {None: "search not found"}) return result # }}} pudb-2017.1.4/pudb/var_view.py000066400000000000000000000440121315257663400160430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # {{{ constants and imports from __future__ import absolute_import, division, print_function import urwid try: import numpy HAVE_NUMPY = 1 except ImportError: HAVE_NUMPY = 0 from pudb.py3compat import PY3, execfile, raw_input, xrange, \ integer_types, string_types if PY3: ELLIPSIS = '…' else: ELLIPSIS = unicode('…', 'utf-8') # noqa: F821 from pudb.debugger import CONFIG # }}} # {{{ data class FrameVarInfo(object): def __init__(self): self.id_path_to_iinfo = {} self.watches = [] def get_inspect_info(self, id_path, read_only): if read_only: return self.id_path_to_iinfo.get( id_path, InspectInfo()) else: return self.id_path_to_iinfo.setdefault( id_path, InspectInfo()) class InspectInfo(object): def __init__(self): self.show_detail = False self.display_type = CONFIG["stringifier"] self.highlighted = False self.repeated_at_top = False self.access_level = "public" self.show_methods = False self.wrap = CONFIG["wrap_variables"] class WatchExpression(object): def __init__(self, expression): self.expression = expression class WatchEvalError(object): def __str__(self): return "" # }}} # {{{ safe types def get_str_safe_types(): import types return tuple(getattr(types, s) for s in "BuiltinFunctionType BuiltinMethodType ClassType " "CodeType FileType FrameType FunctionType GetSetDescriptorType " "LambdaType MemberDescriptorType MethodType ModuleType " "SliceType TypeType TracebackType UnboundMethodType XRangeType".split() if hasattr(types, s)) + (WatchEvalError,) STR_SAFE_TYPES = get_str_safe_types() # }}} # {{{ widget class VariableWidget(urwid.FlowWidget): def __init__(self, prefix, var_label, value_str, id_path=None, attr_prefix=None, watch_expr=None, iinfo=None): self.prefix = prefix self.var_label = var_label self.value_str = value_str self.id_path = id_path self.attr_prefix = attr_prefix or "var" self.watch_expr = watch_expr if iinfo is None: self.wrap = CONFIG["wrap_variables"] else: self.wrap = iinfo.wrap def selectable(self): return True SIZE_LIMIT = 20 def _get_text(self, size): maxcol = size[0] - len(self.prefix) # self.prefix is a padding var_label = self.var_label or '' value_str = self.value_str or '' alltext = var_label + ": " + value_str # The first line is not indented firstline = self.prefix + alltext[:maxcol] if not alltext[maxcol:]: return [firstline] fulllines, rest = divmod(len(alltext) - maxcol, maxcol - 2) restlines = [alltext[(maxcol - 2)*i + maxcol:(maxcol - 2)*i + 2*maxcol - 2] for i in xrange(fulllines + bool(rest))] return [firstline] + [" " + self.prefix + i for i in restlines] def rows(self, size, focus=False): if self.wrap: return len(self._get_text(size)) if (self.value_str is not None and self.var_label is not None and len(self.prefix) + len(self.var_label) > self.SIZE_LIMIT): return 2 else: return 1 def render(self, size, focus=False): from pudb.ui_tools import make_canvas maxcol = size[0] if focus: apfx = "focused "+self.attr_prefix+" " else: apfx = self.attr_prefix+" " var_label = self.var_label or '' if self.wrap: text = self._get_text(size) extralabel_full, extralabel_rem = divmod(len(var_label[maxcol:]), maxcol) totallen = sum([len(i) for i in text]) labellen = ( len(self.prefix) # Padding of first line + (len(self.prefix) + 2) # Padding of subsequent lines * (extralabel_full + bool(extralabel_rem)) + len(var_label) + 2 # for ": " ) _attr = [(apfx+"label", labellen), (apfx+"value", totallen - labellen)] from urwid.util import rle_subseg fullcols, rem = divmod(totallen, maxcol) attr = [rle_subseg(_attr, i*maxcol, (i + 1)*maxcol) for i in xrange(fullcols + bool(rem))] return make_canvas(text, attr, maxcol, apfx+"value") if self.value_str is not None: if self.var_label is not None: if len(self.prefix) + len(self.var_label) > self.SIZE_LIMIT: # label too long? generate separate value line text = [self.prefix + self.var_label, self.prefix+" " + self.value_str] attr = [[(apfx+"label", len(self.prefix)+len(self.var_label))], [(apfx+"value", len(self.prefix)+2+len(self.value_str))]] else: text = [self.prefix + self.var_label + ": " + self.value_str] attr = [[ (apfx+"label", len(self.prefix)+len(self.var_label)+2), (apfx+"value", len(self.value_str)), ]] else: text = [self.prefix + self.value_str] attr = [[ (apfx+"label", len(self.prefix)), (apfx+"value", len(self.value_str)), ]] else: text = [self.prefix + self.var_label] attr = [[(apfx+"label", len(self.prefix) + len(self.var_label)), ]] # Ellipses to show text was cut off #encoding = urwid.util.detected_encoding if False: # encoding[:3] == "UTF": # Unicode is supported, use single character ellipsis for i in xrange(len(text)): if len(text[i]) > maxcol: text[i] = (unicode(text[i][:maxcol-1]) # noqa: F821 + ELLIPSIS + unicode(text[i][maxcol:])) # noqa: F821 # XXX: This doesn't work. It just gives a ? # Strangely, the following does work (it gives the … # three characters from the right): # # text[i] = (unicode(text[i][:maxcol-3]) # + unicode(u'…')) + unicode(text[i][maxcol-2:]) else: for i in xrange(len(text)): if len(text[i]) > maxcol: text[i] = text[i][:maxcol-3] + "..." return make_canvas(text, attr, maxcol, apfx+"value") def keypress(self, size, key): return key # }}} custom_stringifier_dict = {} def type_stringifier(value): if HAVE_NUMPY and isinstance(value, numpy.ndarray): return "ndarray %s %s" % (value.dtype, value.shape) elif isinstance(value, STR_SAFE_TYPES): try: return str(value) except Exception: pass elif hasattr(value, "safely_stringify_for_pudb"): try: # (E.g.) Mock objects will pretend to have this # and return nonsense. result = value.safely_stringify_for_pudb() except Exception: pass else: if isinstance(result, string_types): return result return type(value).__name__ def get_stringifier(iinfo): if iinfo.display_type == "type": return type_stringifier elif iinfo.display_type == "repr": return repr elif iinfo.display_type == "str": return str else: try: if not custom_stringifier_dict: # Only execfile once from os.path import expanduser execfile(expanduser(iinfo.display_type), custom_stringifier_dict) except: print("Error when importing custom stringifier:") from traceback import print_exc print_exc() raw_input("Hit enter:") return lambda value: "ERROR: Invalid custom stringifier file." else: if "pudb_stringifier" not in custom_stringifier_dict: print("%s does not contain a function named pudb_stringifier at " "the module level." % iinfo.display_type) raw_input("Hit enter:") return lambda value: ("ERROR: Invalid custom stringifier file: " "pudb_stringifer not defined.") else: return (lambda value: str(custom_stringifier_dict["pudb_stringifier"](value))) # {{{ tree walking class ValueWalker: PREFIX = "| " def __init__(self, frame_var_info): self.frame_var_info = frame_var_info def walk_value(self, prefix, label, value, id_path=None, attr_prefix=None): if id_path is None: id_path = label iinfo = self.frame_var_info.get_inspect_info(id_path, read_only=True) if isinstance(value, integer_types + (float, complex)): self.add_item(prefix, label, repr(value), id_path, attr_prefix) elif isinstance(value, string_types): self.add_item(prefix, label, repr(value), id_path, attr_prefix) elif value is None: self.add_item(prefix, label, repr(value), id_path, attr_prefix) else: try: displayed_value = get_stringifier(iinfo)(value) except Exception: # Unfortunately, anything can happen when calling str() or # repr() on a random object. displayed_value = type_stringifier(value) \ + " (!! %s error !!)" % iinfo.display_type if iinfo.show_detail: if iinfo.access_level == "public": marker = "pub" elif iinfo.access_level == "private": marker = "pri" else: marker = "all" if iinfo.show_methods: marker += "+()" displayed_value += " [%s]" % marker self.add_item(prefix, label, displayed_value, id_path, attr_prefix) if not iinfo.show_detail: return # set --------------------------------------------------------- if isinstance(value, (set, frozenset)): for i, entry in enumerate(value): if i % 10 == 0 and i: cont_id_path = "%s.cont-%d" % (id_path, i) if not self.frame_var_info.get_inspect_info( cont_id_path, read_only=True).show_detail: self.add_item(prefix+self.PREFIX, "...", None, cont_id_path) break self.walk_value(prefix+self.PREFIX, None, entry, "%s[%d]" % (id_path, i)) if not value: self.add_item(prefix+self.PREFIX, "", None) return # containers -------------------------------------------------- key_it = None try: if PY3: key_it = value.keys() else: key_it = value.iterkeys() except: pass if key_it is None: try: l = len(value) except: pass else: try: value[0] except IndexError: key_it = [] except: pass else: key_it = xrange(l) if key_it is not None: cnt = 0 for key in key_it: if cnt % 10 == 0 and cnt: cont_id_path = "%s.cont-%d" % (id_path, cnt) if not self.frame_var_info.get_inspect_info( cont_id_path, read_only=True).show_detail: self.add_item( prefix+self.PREFIX, "...", None, cont_id_path) break self.walk_value(prefix+self.PREFIX, repr(key), value[key], "%s[%r]" % (id_path, key)) cnt += 1 if not cnt: self.add_item(prefix+self.PREFIX, "", None) return # class types ------------------------------------------------- key_its = [] try: key_its.append(dir(value)) except: pass keys = [key for ki in key_its for key in ki] keys.sort() cnt_omitted_private = cnt_omitted_methods = 0 for key in keys: if iinfo.access_level == "public": if key.startswith("_"): cnt_omitted_private += 1 continue elif iinfo.access_level == "private": if key.startswith("__") and key.endswith("__"): cnt_omitted_private += 1 continue try: attr_value = getattr(value, key) if callable(attr_value) and not iinfo.show_methods: cnt_omitted_methods += 1 continue except: attr_value = WatchEvalError() self.walk_value(prefix+self.PREFIX, ".%s" % key, attr_value, "%s.%s" % (id_path, key)) if not keys: if cnt_omitted_private: label = "" elif cnt_omitted_methods: label = "" else: label = "" self.add_item(prefix+self.PREFIX, label, None) if not key_its: self.add_item(prefix+self.PREFIX, "", None) class BasicValueWalker(ValueWalker): def __init__(self, frame_var_info): ValueWalker.__init__(self, frame_var_info) self.widget_list = [] def add_item(self, prefix, var_label, value_str, id_path=None, attr_prefix=None): iinfo = self.frame_var_info.get_inspect_info(id_path, read_only=True) if iinfo.highlighted: attr_prefix = "highlighted var" self.widget_list.append(VariableWidget(prefix, var_label, value_str, id_path, attr_prefix, iinfo=iinfo)) class WatchValueWalker(ValueWalker): def __init__(self, frame_var_info, widget_list, watch_expr): ValueWalker.__init__(self, frame_var_info) self.widget_list = widget_list self.watch_expr = watch_expr def add_item(self, prefix, var_label, value_str, id_path=None, attr_prefix=None): iinfo = self.frame_var_info.get_inspect_info(id_path, read_only=True) if iinfo.highlighted: attr_prefix = "highlighted var" self.widget_list.append( VariableWidget(prefix, var_label, value_str, id_path, attr_prefix, watch_expr=self.watch_expr, iinfo=iinfo)) class TopAndMainVariableWalker(ValueWalker): def __init__(self, frame_var_info): ValueWalker.__init__(self, frame_var_info) self.main_widget_list = [] self.top_widget_list = [] self.top_id_path_prefixes = [] def add_item(self, prefix, var_label, value_str, id_path=None, attr_prefix=None): iinfo = self.frame_var_info.get_inspect_info(id_path, read_only=True) if iinfo.highlighted: attr_prefix = "highlighted var" repeated_at_top = iinfo.repeated_at_top if repeated_at_top and id_path is not None: self.top_id_path_prefixes.append(id_path) for tipp in self.top_id_path_prefixes: if id_path is not None and id_path.startswith(tipp): repeated_at_top = True if repeated_at_top: self.top_widget_list.append(VariableWidget(prefix, var_label, value_str, id_path, attr_prefix, iinfo=iinfo)) self.main_widget_list.append(VariableWidget(prefix, var_label, value_str, id_path, attr_prefix, iinfo=iinfo)) # }}} # {{{ top level SEPARATOR = urwid.AttrMap(urwid.Text(""), "variable separator") def make_var_view(frame_var_info, locals, globals): vars = list(locals.keys()) vars.sort(key=lambda n: n.lower()) tmv_walker = TopAndMainVariableWalker(frame_var_info) ret_walker = BasicValueWalker(frame_var_info) watch_widget_list = [] for watch_expr in frame_var_info.watches: try: value = eval(watch_expr.expression, globals, locals) except: value = WatchEvalError() WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ .walk_value("", watch_expr.expression, value) if "__return__" in vars: ret_walker.walk_value("", "Return", locals["__return__"], attr_prefix="return") for var in vars: if not var[0] in "_.": tmv_walker.walk_value("", var, locals[var]) result = tmv_walker.main_widget_list if watch_widget_list: result = (watch_widget_list + [SEPARATOR] + result) if tmv_walker.top_widget_list: result = (tmv_walker.top_widget_list + [SEPARATOR] + result) if ret_walker.widget_list: result = (ret_walker.widget_list + result) return result class FrameVarInfoKeeper(object): def __init__(self): self.frame_var_info = {} def get_frame_var_info(self, read_only, ssid=None): if ssid is None: ssid = self.debugger.get_stack_situation_id() if read_only: return self.frame_var_info.get(ssid, FrameVarInfo()) else: return self.frame_var_info.setdefault(ssid, FrameVarInfo()) # }}} # vim: foldmethod=marker pudb-2017.1.4/requirements.dev.txt000066400000000000000000000002421315257663400167530ustar00rootroot00000000000000codecov==2.0.5 coverage==4.3.4 pbr==2.0.0 py==1.4.33 Pygments==2.2.0 pytest==3.0.7 pytest-cov==2.4.0 pytest-mock==1.6.0 requests==2.13.0 six==1.10.0 urwid==1.3.1 pudb-2017.1.4/setup.cfg000066400000000000000000000001551315257663400145360ustar00rootroot00000000000000[flake8] ignore = E126,E127,E128,E123,E226,E241,E242,E265,W503,E402 max-line-length=85 [wheel] universal = 0 pudb-2017.1.4/setup.py000077500000000000000000000037171315257663400144410ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup from pudb import VERSION import sys py_version_major = sys.version_info[0] if py_version_major == 3: PY_VERSION = str(py_version_major) else: PY_VERSION = '' try: readme = open("README.rst") long_description = str(readme.read()) finally: readme.close() setup(name='pudb', version=VERSION, description='A full-screen, console-based Python debugger', long_description=long_description, author='Andreas Kloeckner', author_email='inform@tiker.net', install_requires=[ "urwid>=1.1.1", "pygments>=1.0", ], test_requires=[ "pytest>=2", ], url='https://github.com/inducer/pudb', classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: Console :: Curses", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Software Development", "Topic :: Software Development :: Debuggers", "Topic :: Software Development :: Quality Assurance", "Topic :: System :: Recovery Tools", "Topic :: System :: Software Distribution", "Topic :: Terminals", "Topic :: Utilities", ], packages=["pudb"], entry_points={ 'console_scripts': ['pudb' + PY_VERSION + ' = pudb.run:main'], 'gui_script': []}, ) pudb-2017.1.4/test/000077500000000000000000000000001315257663400136735ustar00rootroot00000000000000pudb-2017.1.4/test/test_lowlevel.py000066400000000000000000000013261315257663400171370ustar00rootroot00000000000000# -*- coding: utf-8 -*- from pudb.lowlevel import detect_encoding, decode_lines from pudb.py3compat import PY3 def test_detect_encoding_nocookie(): lines = ['Test Проверка'] encoding, _ = detect_encoding(lines) assert encoding == 'utf-8' def test_detect_encoding_cookie(): lines = [ '# coding=utf-8', 'Test', 'Проверка' ] encoding, _ = detect_encoding(lines) assert encoding == 'utf-8' def test_decode_lines(): lines = [ '# coding=utf-8', 'Test', 'Проверка', ] if PY3: assert lines == list(decode_lines(lines)) else: assert [l.decode('utf-8') for l in lines] == list(decode_lines(lines)) pudb-2017.1.4/test/test_make_canvas.py000066400000000000000000000033611315257663400175570ustar00rootroot00000000000000# - encoding: utf-8 - from pudb.ui_tools import make_canvas def test_simple(): text = u'aaaaaa' canvas = make_canvas( txt=[text], attr=[[('var value', len(text))]], maxcol=len(text) + 5 ) content = list(canvas.content()) assert content == [ [('var value', None, b'aaaaaa'), (None, None, b' ' * 5)] ] def test_multiple(): canvas = make_canvas( txt=[u'Return: None'], attr=[[('return label', 8), ('return value', 4)]], maxcol=100 ) content = list(canvas.content()) assert content == [ [('return label', None, b'Return: '), ('return value', None, b'None'), (None, None, b' ' * 88)] ] def test_boundary(): text = u'aaaaaa' canvas = make_canvas( txt=[text], attr=[[('var value', len(text))]], maxcol=len(text) ) assert list(canvas.content()) == [[('var value', None, b'aaaaaa')]] def test_byte_boundary(): text = u'aaaaaaé' canvas = make_canvas( txt=[text], attr=[[('var value', len(text))]], maxcol=len(text) ) assert list(canvas.content()) == [[('var value', None, b'aaaaaa\xc3\xa9')]] def test_wide_chars(): text = u"data: '中文'" canvas = make_canvas( txt=[text], attr=[[('var label', 6), ('var value', 4)]], maxcol=47, ) assert list(canvas.content()) == [[ ('var label', None, b'data: '), ('var value', None, u"'中文'".encode('utf-8')), (None, None, b' '*(47 - 12)), # 10 chars, 2 of which are double width ]] if __name__ == "__main__": import sys if len(sys.argv) > 1: exec(sys.argv[1]) else: from py.test.cmdline import main main([__file__]) pudb-2017.1.4/test/test_settings.py000066400000000000000000000025151315257663400171470ustar00rootroot00000000000000import collections import pytest # noqa: F401 from pudb.py3compat import builtins from pudb.settings import load_breakpoints, save_breakpoints def test_load_breakpoints(mocker): fake_data = ['b /home/user/test.py:41'], ['b /home/user/test.py:50'] mock_open = mocker.mock_open() mock_open.return_value.readlines.side_effect = fake_data mocker.patch.object(builtins, 'open', mock_open) mocker.patch('pudb.settings.lookup_module', mocker.Mock(return_value='/home/user/test.py')) mocker.patch('pudb.settings.get_breakpoint_invalid_reason', mocker.Mock(return_value=None)) result = load_breakpoints() expected = [('/home/user/test.py', 41, False, None, None), ('/home/user/test.py', 50, False, None, None)] assert result == expected def test_save_breakpoints(mocker): MockBP = collections.namedtuple('MockBreakpoint', 'file line cond') mock_breakpoints = [MockBP('/home/user/test.py', 41, None), MockBP('/home/user/test.py', 50, None)] mocker.patch('pudb.settings.get_breakpoints_file_name', mocker.Mock(return_value='saved-breakpoints')) mock_open = mocker.mock_open() mocker.patch.object(builtins, 'open', mock_open) save_breakpoints(mock_breakpoints) mock_open.assert_called_with('saved-breakpoints', 'w') pudb-2017.1.4/test/test_source_code_providers.py000066400000000000000000000025211315257663400216730ustar00rootroot00000000000000import pytest # noqa: F401 from pudb.debugger import ( NullSourceCodeProvider, FileSourceCodeProvider, DirectSourceCodeProvider) from pudb.source_view import SourceLine class TestNullSourceCodeProvider: def test_get_lines(self, mocker): provider = NullSourceCodeProvider() result = provider.get_lines(mocker.Mock()) assert len(result) == 10 assert isinstance(result[0], SourceLine) class TestFileSourceCodeProvider: def test_string_file_name(self, mocker): mock_debugger = mocker.Mock() mock_debugger.canonic = mocker.Mock(return_value='') provider = FileSourceCodeProvider(mock_debugger, 'test file name') result = provider.get_lines(mocker.MagicMock()) assert len(result) == 1 assert isinstance(result[0], SourceLine) def test_get_lines(self, mocker): provider = FileSourceCodeProvider(mocker.Mock(), 'test file name') result = provider.get_lines(mocker.MagicMock()) assert len(result) == 1 assert isinstance(result[0], SourceLine) class TestDirectSourceCodeProvider: def test_get_lines(self, mocker): provider = DirectSourceCodeProvider(mocker.Mock(), 'test code') result = provider.get_lines(mocker.Mock()) assert len(result) == 1 assert isinstance(result[0], SourceLine) pudb-2017.1.4/try-the-debugger.sh000077500000000000000000000001631315257663400164310ustar00rootroot00000000000000#! /bin/sh if test "$1" = ""; then PYINTERP="python" else PYINTERP="$1" fi $PYINTERP -m pudb.run debug_me.py pudb-2017.1.4/upload_coverage.sh000066400000000000000000000002511315257663400164050ustar00rootroot00000000000000#!/bin/bash if [ -z "$COVERAGE" ]; then echo "coverage is not selected for this build" exit 0 fi echo "uploading coverage" cd test eval "codecov --token=$COVERAGE"