Hello community, here is the log from the commit of package bpython for openSUSE:Factory checked in at 2012-06-26 17:45:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/bpython (Old) and /work/SRC/openSUSE:Factory/.bpython.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "bpython", Maintainer is "[email protected]" Changes: -------- --- /work/SRC/openSUSE:Factory/bpython/bpython.changes 2011-09-23 01:52:51.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.bpython.new/bpython.changes 2012-06-26 17:45:21.000000000 +0200 @@ -1,0 +2,6 @@ +Tue Jun 26 09:53:27 UTC 2012 - [email protected] + +- Update to version 0.11: + + #204: "import math" not autocompleting on python 3.2 + +------------------------------------------------------------------- @@ -5,2 +11,2 @@ - * #197: find_modules crashes on non-readable directories - * #198: Source tarball lacks .po files + + #197: find_modules crashes on non-readable directories + + #198: Source tarball lacks .po files @@ -8 +14 @@ - * Config files are now located according to the XDG Base Directory + + Config files are now located according to the XDG Base Directory @@ -12,3 +18,3 @@ - * Fixed some issues with tuple unpacking in argspec. See issues #133 and #138. - * Fixed a crash with non-ascii filenames in import completion. See issue #139. - * Fixed a crash caused by inspect.findsource() raising an IndexError + + Fixed some issues with tuple unpacking in argspec. See issues #133 and #138. + + Fixed a crash with non-ascii filenames in import completion. See issue #139. + + Fixed a crash caused by inspect.findsource() raising an IndexError @@ -16,3 +22,3 @@ - * Non-ascii input should work now under Python 3. - * Issue #165: C-a and C-e do the right thing now in urwid. - * The short command-line option "-c config" was dropped as it conflicts with + + Non-ascii input should work now under Python 3. + + Issue #165: C-a and C-e do the right thing now in urwid. + + The short command-line option "-c config" was dropped as it conflicts with Old: ---- bpython-0.10.1.tar.gz New: ---- bpython-0.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ bpython.spec ++++++ --- /var/tmp/diff_new_pack.QL8if5/_old 2012-06-26 17:45:22.000000000 +0200 +++ /var/tmp/diff_new_pack.QL8if5/_new 2012-06-26 17:45:22.000000000 +0200 @@ -1,7 +1,7 @@ # # spec file for package bpython # -# Copyright (c) 2011 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -15,11 +15,9 @@ # Please submit bugfixes or comments via http://bugs.opensuse.org/ # - - Name: bpython -Version: 0.10.1 -Release: 1 +Version: 0.11 +Release: 0 Url: http://www.bpython-interpreter.org Summary: Fancy Curses Interface to the Python Interactive Interpreter License: MIT @@ -34,13 +32,12 @@ Requires: python-curses Requires: python-pygments Requires: python-pyparsing -%if 0%{?suse_version} +%if 0%{?suse_version} && 0%{?suse_version} <= 1110 +%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %py_requires -%if 0%{?suse_version} >= 1120 +%else BuildArch: noarch %endif -%endif -%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %description Bpython is an enhanced Python interactive interpreter that uses curses ++++++ bpython-0.10.1.tar.gz -> bpython-0.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/AUTHORS new/bpython-0.11/AUTHORS --- old/bpython-0.10.1/AUTHORS 2011-08-09 11:55:28.000000000 +0200 +++ new/bpython-0.11/AUTHORS 2012-04-04 23:06:42.000000000 +0200 @@ -12,5 +12,6 @@ * Amjith Ramanujam <amjith dot r at gmail dot com> * Simon de Vlieger <simon at ikanobori dot jp> * Marien Zwart <marien dot zwart at gmail dot com> +* Brandon Navra <brandon dot navra at gmail dot com> Many thanks for all contributions! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/CHANGELOG new/bpython-0.11/CHANGELOG --- old/bpython-0.10.1/CHANGELOG 2011-08-09 11:44:51.000000000 +0200 +++ new/bpython-0.11/CHANGELOG 2012-04-04 23:09:09.000000000 +0200 @@ -1,6 +1,19 @@ Changelog ========= +v0.11 +----- + +A bugfix/cleanup release .The fixed bugs are: + +* #204: "import math" not autocompleting on python 3.2 + +Otherwise lots of small additions to the to be replacement for our ncurses +frontend, the urwid frontend. + +I'd like to specifically thank Amjith Ramanujam for his work on history search +which was further implemented and is in working order right now. + v0.10.1 ------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/PKG-INFO new/bpython-0.11/PKG-INFO --- old/bpython-0.10.1/PKG-INFO 2011-08-09 12:22:14.000000000 +0200 +++ new/bpython-0.11/PKG-INFO 2012-04-04 23:11:06.000000000 +0200 @@ -1,11 +1,11 @@ Metadata-Version: 1.0 Name: bpython -Version: 0.10.1 +Version: 0.11 Summary: Fancy Interface to the Python Interpreter Home-page: http://www.bpython-interpreter.org/ Author: Bob Farrell, Andreas Stuehrk et al. Author-email: [email protected] License: MIT/X Description: bpython is a fancy interface to the Python - interpreter for Unix-like operating systems. + interpreter for Unix-like operating systems. Platform: UNKNOWN diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/README new/bpython-0.11/README --- old/bpython-0.10.1/README 2011-08-09 11:55:28.000000000 +0200 +++ new/bpython-0.11/README 2012-04-04 23:06:42.000000000 +0200 @@ -107,8 +107,9 @@ Configuration ============= See the sample-config file for a list of available options. -You should save your config file as ~/.bpython/config or specify -at the command line: +You should save your config file as ~/.config/bpython/config +(i.e $XDG_CONFIG_HOME/bpython/config) or specify at the +command line: bpython --config /path/to/bpython/config @@ -118,3 +119,38 @@ bitbucket: http://bitbucket.org/bobf/bpython/issues/ + +CLI Windows Support +=================== + +Dependencies +------------ +Curses + Use the appropriate version compiled by Christoph Gohlke + http://www.lfd.uci.edu/~gohlke/pythonlibs/ + +pyreadline + Use the version in the cheeseshop + http://pypi.python.org/pypi/pyreadline/ + +Recommended +----------- +Obtain the less program from GnuUtils. This makes the pager work as intended. +It can be obtained from cygwin or GnuWin32 or msys + +Current version is tested with +------------------------------ +Curses 2.2 +pyreadline 1.7 + +Curses Notes +------------ +The curses used has a bug where the colours are displayed incorrectly: + red is swapped with blue + cyan is swapped with yellow + +To correct this I have provided my windows.theme file. + +This curses implementation has 16 colors (dark and light versions of the colours) + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpdb/__init__.py new/bpython-0.11/bpdb/__init__.py --- old/bpython-0.10.1/bpdb/__init__.py 2011-08-09 11:44:51.000000000 +0200 +++ new/bpython-0.11/bpdb/__init__.py 2012-04-04 23:06:42.000000000 +0200 @@ -34,3 +34,22 @@ a debugger instance and sets the trace. """ debugger = BPdb() debugger.set_trace(sys._getframe().f_back) + +# Adopted verbatim from pdb for completeness: + +def post_mortem(t=None): + # handling the default + if t is None: + # sys.exc_info() returns (type, value, traceback) if an exception is + # being handled, otherwise it returns None + t = sys.exc_info()[2] + if t is None: + raise ValueError("A valid traceback must be passed if no " + "exception is being handled") + + p = BPdb() + p.reset() + p.interaction(None, t) + +def pm(): + post_mortem(getattr(sys, "last_traceback", None)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/__init__.py new/bpython-0.11/bpython/__init__.py --- old/bpython-0.10.1/bpython/__init__.py 2011-08-09 11:55:28.000000000 +0200 +++ new/bpython-0.11/bpython/__init__.py 2012-04-04 23:10:59.000000000 +0200 @@ -22,7 +22,7 @@ import os.path -__version__ = '0.10.1' +__version__ = '0.11' package_dir = os.path.abspath(os.path.dirname(__file__)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/cli.py new/bpython-0.11/bpython/cli.py --- old/bpython-0.10.1/bpython/cli.py 2011-08-09 11:55:28.000000000 +0200 +++ new/bpython-0.11/bpython/cli.py 2012-04-04 23:06:42.000000000 +0200 @@ -22,18 +22,38 @@ # THE SOFTWARE. # +# Modified by Brandon Navra +# Notes for Windows +# Prerequsites +# - Curses +# - pyreadline +# +# Added +# +# - Support for running on windows command prompt +# - input from numpad keys +# +# Issues +# +# - Suspend doesn't work nor does detection of resizing of screen +# - Instead the suspend key exits the program +# - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) + from __future__ import division, with_statement +import platform import os import sys import curses import math import re import time -import signal + import struct -import termios -import fcntl +if platform.system() != 'Windows': + import signal #Windows does not have job control + import termios #Windows uses curses + import fcntl #Windows uses curses import unicodedata import errno @@ -76,6 +96,7 @@ DO_RESIZE = False # --- + def getpreferredencoding(): return locale.getpreferredencoding() or sys.getdefaultencoding() @@ -136,7 +157,7 @@ curses.raw(True) try: - while not buffer.endswith('\n'): + while not buffer.endswith(('\n', '\r')): key = self.interface.get_key() if key in [curses.erasechar(), 'KEY_BACKSPACE']: y, x = self.interface.scr.getyx() @@ -147,7 +168,7 @@ elif key == chr(4) and not buffer: # C-d return '' - elif (key != '\n' and + elif (key not in ('\n', '\r') and (len(key) > 1 or unicodedata.category(key) == 'Cc')): continue sys.stdout.write(key) @@ -224,6 +245,21 @@ 'w': 7, 'd': -1, } + + if platform.system() == 'Windows': + c = dict(c.items() + + [ + ('K', 8), + ('R', 9), + ('G', 10), + ('Y', 11), + ('B', 12), + ('M', 13), + ('C', 14), + ('W', 15), + ] + ) + for i in range(63): if i > 7: j = i // 8 @@ -281,6 +317,7 @@ def addstr(self, s): """Add a string to the current input line and figure out where it should go, depending on the cursor position.""" + self.rl_history.reset() if not self.cpos: self.s += s else: @@ -301,6 +338,7 @@ def bs(self, delete_tabs=True): """Process a backspace""" + self.rl_history.reset() y, x = self.scr.getyx() if not self.s: @@ -329,6 +367,7 @@ return n def bs_word(self): + self.rl_history.reset() pos = len(self.s) - self.cpos - 1 deleted = [] # First we delete any space to the left of the cursor. @@ -536,6 +575,15 @@ self.s = self.rl_history.forward() self.print_line(self.s, clr=True) + def search(self): + """Search with the partial matches from the history object.""" + + self.cpo = 0 + self.clear_wrapped_lines() + self.rl_history.enter(self.s) + self.s = self.rl_history.back(start=False, search=True) + self.print_line(self.s, clr=True) + def get_key(self): key = '' while True: @@ -773,14 +821,22 @@ config = self.config - if key == chr(8): # C-Backspace (on my computer anyway!) + if platform.system() == 'Windows': + C_BACK = chr(127) + BACKSP = chr(8) + else: + C_BACK = chr(8) + BACKSP = chr(127) + + if key == C_BACK: # C-Backspace (on my computer anyway!) self.clrtobol() key = '\n' # Don't return; let it get handled - if key == chr(27): + + if key == chr(27): #Escape Key return '' - if key in (chr(127), 'KEY_BACKSPACE'): + if key in (BACKSP, 'KEY_BACKSPACE'): self.bs() self.complete() return '' @@ -801,6 +857,10 @@ self.undo() return '' + elif key in key_dispatch[config.search_key]: + self.search() + return '' + elif key in ('KEY_UP', ) + key_dispatch[config.up_one_line_key]: # Cursor Up/C-p self.back() @@ -893,7 +953,7 @@ self.statusbar.message(_('Cannot show source.')) return '' - elif key == '\n': + elif key in ('\n', '\r', 'PADENTER'): self.lf() return None @@ -904,9 +964,25 @@ return self.tab(back=True) elif key in key_dispatch[config.suspend_key]: - self.suspend() - return '' + if platform.system() != 'Windows': + self.suspend() + return '' + else: + self.do_exit = True + return None + elif key[0:3] == 'PAD' and not key in ('PAD0', 'PADSTOP'): + pad_keys = { + 'PADMINUS': '-', + 'PADPLUS': '+', + 'PADSLASH': '/', + 'PADSTAR': '*', + } + try: + self.addstr(pad_keys[key]) + self.print_line(self.s) + except KeyError: + return '' elif len(key) == 1 and not unicodedata.category(key) == 'Cc': self.addstr(key) self.print_line(self.s) @@ -1296,8 +1372,9 @@ def suspend(self): """Suspend the current process for shell job control.""" - curses.endwin() - os.kill(os.getpid(), signal.SIGSTOP) + if platform.system() != 'Windows': + curses.endwin() + os.kill(os.getpid(), signal.SIGSTOP) def tab(self, back=False): """Process the tab key being hit. If there's only whitespace @@ -1511,6 +1588,9 @@ self.c = c if s: + if not py3 and isinstance(s, unicode): + s = s.encode(getpreferredencoding()) + if self.c: self.win.addstr(s, self.c) else: @@ -1575,9 +1655,33 @@ So I'm not going to ask any questions. """ - h, w = struct.unpack( - "hhhh", - fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8))[0:2] + + if platform.system() != 'Windows': + import struct + h, w = struct.unpack( + "hhhh", + fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8))[0:2] + else: + from ctypes import windll, create_string_buffer + + # stdin handle is -10 + # stdout handle is -11 + # stderr handle is -12 + + h = windll.kernel32.GetStdHandle(-12) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + + if res: + import struct + (bufx, bufy, curx, cury, wattr, + left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + sizex = right - left + 1 + sizey = bottom - top + 1 + else: + sizex, sizey = stdscr.getmaxyx()# can't determine actual size - return default values + + h, w = sizey, sizex return h, w @@ -1679,10 +1783,11 @@ global colors DO_RESIZE = False - old_sigwinch_handler = signal.signal(signal.SIGWINCH, - lambda *_: sigwinch(scr)) - # redraw window after being suspended - old_sigcont_handler = signal.signal(signal.SIGCONT, lambda *_: sigcont(scr)) + if platform.system() != 'Windows': + old_sigwinch_handler = signal.signal(signal.SIGWINCH, + lambda *_: sigwinch(scr)) + # redraw window after being suspended + old_sigcont_handler = signal.signal(signal.SIGCONT, lambda *_: sigcont(scr)) stdscr = scr try: @@ -1733,8 +1838,9 @@ curses.raw(False) # Restore signal handlers - signal.signal(signal.SIGWINCH, old_sigwinch_handler) - signal.signal(signal.SIGCONT, old_sigcont_handler) + if platform.system() != 'Windows': + signal.signal(signal.SIGWINCH, old_sigwinch_handler) + signal.signal(signal.SIGCONT, old_sigcont_handler) return clirepl.getstdout() @@ -1743,6 +1849,7 @@ locale.setlocale(locale.LC_ALL, "") translations.init() + config, options, exec_args = bpython.args.parse(args) # Save stdin, stdout and stderr for later restoration diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/config.py new/bpython-0.11/bpython/config.py --- old/bpython-0.10.1/bpython/config.py 2011-08-09 11:44:51.000000000 +0200 +++ new/bpython-0.11/bpython/config.py 2012-04-04 23:06:42.000000000 +0200 @@ -6,6 +6,15 @@ from bpython.keys import cli_key_dispatch as key_dispatch +MAGIC_METHODS = ", ".join("__%s__" % s for s in [ + "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", + "nonzero", "unicode", "getattr", "setattr", "get", "set","call", "len", + "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", + "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", + "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", + "oct", "hex", "index", "coerce", "enter", "exit"] +) + class Struct(object): """Simple class for instantiating objects we can add arbitrary attributes to and use for various arbitrary things.""" @@ -45,11 +54,14 @@ 'arg_spec': True, 'auto_display_list': True, 'color_scheme': 'default', + 'complete_magic_methods' : True, + 'magic_methods' : MAGIC_METHODS, 'dedent_after': 1, 'flush_output': True, 'highlight_show_source': True, 'hist_file': '~/.pythonhist', 'hist_length': 100, + 'hist_duplicates': True, 'paste_time': 0.02, 'syntax': True, 'tab_length': 4, @@ -72,6 +84,7 @@ 'show_source': 'F2', 'suspend': 'C-z', 'undo': 'C-r', + 'search': 'C-o', 'up_one_line': 'C-p', 'yank_from_buffer': 'C-y'}, 'cli': { @@ -99,9 +112,11 @@ 'highlight_show_source') struct.hist_file = config.get('general', 'hist_file') struct.hist_length = config.getint('general', 'hist_length') + struct.hist_duplicates = config.getboolean('general', 'hist_duplicates') struct.flush_output = config.getboolean('general', 'flush_output') struct.pastebin_key = config.get('keyboard', 'pastebin') struct.save_key = config.get('keyboard', 'save') + struct.search_key = config.get('keyboard', 'search') struct.show_source_key = config.get('keyboard', 'show_source') struct.suspend_key = config.get('keyboard', 'suspend') struct.undo_key = config.get('keyboard', 'undo') @@ -124,6 +139,11 @@ struct.cli_suggestion_width = config.getfloat('cli', 'suggestion_width') + struct.complete_magic_methods = config.getboolean('general', + 'complete_magic_methods') + methods = config.get('general', 'magic_methods') + struct.magic_methods = [meth.strip() for meth in methods.split(",")] + struct.gtk_font = config.get('gtk', 'font') color_scheme_name = config.get('general', 'color_scheme') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/importcompletion.py new/bpython-0.11/bpython/importcompletion.py --- old/bpython-0.10.1/bpython/importcompletion.py 2011-08-09 11:54:31.000000000 +0200 +++ new/bpython-0.11/bpython/importcompletion.py 2012-04-04 23:06:42.000000000 +0200 @@ -123,7 +123,10 @@ # Unfortunately, CPython just crashes if there is a directory # which ends with a python extension, so work around. continue - name = os.path.splitext(name)[0] + for suffix in imp.get_suffixes(): + if name.endswith(suffix[0]): + name = name[:-len(suffix[0])] + break if py3 and name == "badsyntax_pep3120": # Workaround for issue #166 continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/inspection.py new/bpython-0.11/bpython/inspection.py --- old/bpython-0.10.1/bpython/inspection.py 2011-08-09 11:54:31.000000000 +0200 +++ new/bpython-0.11/bpython/inspection.py 2012-04-04 23:06:42.000000000 +0200 @@ -228,9 +228,14 @@ # not take 'self', the latter would: func_name = getattr(f, '__name__', None) - is_bound_method = ((inspect.ismethod(f) and f.im_self is not None) + try: + is_bound_method = ((inspect.ismethod(f) and f.im_self is not None) or (func_name == '__init__' and not func.endswith('.__init__'))) + except: + # if f is a method from a xmlrpclib.Server instance, func_name == + # '__init__' throws xmlrpclib.Fault (see #202) + return None try: if py3: argspec = inspect.getfullargspec(f) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/keys.py new/bpython-0.11/bpython/keys.py --- old/bpython-0.10.1/bpython/keys.py 2011-08-09 11:44:51.000000000 +0200 +++ new/bpython-0.11/bpython/keys.py 2012-04-04 23:06:42.000000000 +0200 @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # The MIT License # # Copyright (c) 2008 Simon de Vlieger @@ -56,7 +54,8 @@ '^%s' % c.upper()) for c in string.ascii_lowercase: - urwid_key_dispatch['C-%s' % c] = 'ctrl %s' % c + urwid_key_dispatch['C-%s' % c] = 'ctrl %s' % c + urwid_key_dispatch['M-%s' % c] = 'meta %s' % c # fill dispatch with cool characters cli_key_dispatch['C-['] = (chr(27), '^[') @@ -70,4 +69,4 @@ cli_key_dispatch['F%s' % str(x)] = ('KEY_F(%s)' % str(x),) for x in xrange(1, 13): - urwid_key_dispatch['F%s' % str(x)] = 'F%s' % str(x) + urwid_key_dispatch['F%s' % str(x)] = 'f%s' % str(x) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/repl.py new/bpython-0.11/bpython/repl.py --- old/bpython-0.10.1/bpython/repl.py 2011-08-09 11:54:31.000000000 +0200 +++ new/bpython-0.11/bpython/repl.py 2012-04-04 23:06:42.000000000 +0200 @@ -146,17 +146,25 @@ class History(object): - def __init__(self, entries=None): + def __init__(self, entries=None, duplicates=False): if entries is None: self.entries = [''] else: self.entries = list(entries) self.index = 0 self.saved_line = '' + self.duplicates = duplicates def append(self, line): line = line.rstrip('\n') if line: + if not self.duplicates: + # remove duplicates + try: + while True: + self.entries.remove(line) + except ValueError: + pass self.entries.append(line) def first(self): @@ -165,21 +173,62 @@ self.index = len(self.entries) return self.entries[-self.index] - def back(self): + def back(self, start=True, search=False): """Move one step back in the history.""" if not self.is_at_end: - self.index += 1 - return self.entries[-self.index] + if search: + self.index += self.find_partial_match_backward(self.saved_line) + elif start: + self.index += self.find_match_backward(self.saved_line) + else: + self.index += 1 + return self.entries[-self.index] if self.index else self.saved_line + + def find_match_backward(self, search_term): + filtered_list_len = len(self.entries) - self.index + for idx, val in enumerate(reversed(self.entries[:filtered_list_len])): + if val.startswith(search_term): + return idx + 1 + return 0 + + def find_partial_match_backward(self, search_term): + filtered_list_len = len(self.entries) - self.index + for idx, val in enumerate(reversed(self.entries[:filtered_list_len])): + if search_term in val: + return idx + 1 + return 0 + - def forward(self): + def forward(self, start=True, search=False): """Move one step forward in the history.""" if self.index > 1: - self.index -= 1 - return self.entries[-self.index] + if search: + self.index -= self.find_partial_match_forward(self.saved_line) + elif start: + self.index -= self.find_match_forward(self.saved_line) + else: + self.index -= 1 + return self.entries[-self.index] if self.index else self.saved_line else: self.index = 0 return self.saved_line + def find_match_forward(self, search_term): + filtered_list_len = len(self.entries) - self.index + 1 + for idx, val in enumerate(self.entries[filtered_list_len:]): + if val.startswith(search_term): + return idx + 1 + return self.index + + def find_partial_match_forward(self, search_term): + filtered_list_len = len(self.entries) - self.index + 1 + for idx, val in enumerate(self.entries[filtered_list_len:]): + if search_term in val: + return idx + 1 + return self.index + + + def last(self): """Move forward to the end of the history.""" if not self.is_at_start: @@ -320,7 +369,7 @@ self.interp = interp self.interp.syntaxerror_callback = self.clear_current_line self.match = False - self.rl_history = History() + self.rl_history = History(duplicates=config.hist_duplicates) self.s_hist = [] self.history = [] self.evaluating = False @@ -352,7 +401,8 @@ pythonhist = os.path.expanduser(self.config.hist_file) if os.path.exists(pythonhist): - self.rl_history.load(pythonhist, getpreferredencoding()) + self.rl_history.load(pythonhist, + getpreferredencoding() or "ascii") def startup(self): """ @@ -597,6 +647,11 @@ e = True else: matches = self.completer.matches + if (self.config.complete_magic_methods and self.buffer and + self.buffer[0].startswith("class ") and + self.current_line().lstrip().startswith("def ")): + matches.extend(name for name in self.config.magic_methods + if name.startswith(cw)) if not e and self.argspec: matches.extend(name + '=' for name in self.argspec[1][0] @@ -715,8 +770,16 @@ not self.interact.confirm("Pastebin buffer? (y/N) ")): self.interact.notify("Pastebin aborted") return + return self.do_pastebin(s) - pasteservice = ServerProxy(self.config.pastebin_url) + def do_pastebin(self, s): + """Actually perform the upload.""" + try: + pasteservice = ServerProxy(self.config.pastebin_url) + except IOError, e: + self.interact.notify("Pastebin error for URL '%s': %s" % + (self.config.pastebin_url, str(e))) + return if s == self.prev_pastebin_content: self.interact.notify('Duplicate pastebin. Previous URL: ' + Files old/bpython-0.10.1/bpython/translations/es_ES/LC_MESSAGES/bpython.mo and new/bpython-0.11/bpython/translations/es_ES/LC_MESSAGES/bpython.mo differ Files old/bpython-0.10.1/bpython/translations/it_IT/LC_MESSAGES/bpython.mo and new/bpython-0.11/bpython/translations/it_IT/LC_MESSAGES/bpython.mo differ Files old/bpython-0.10.1/bpython/translations/nl_NL/LC_MESSAGES/bpython.mo and new/bpython-0.11/bpython/translations/nl_NL/LC_MESSAGES/bpython.mo differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython/urwid.py new/bpython-0.11/bpython/urwid.py --- old/bpython-0.10.1/bpython/urwid.py 2011-08-09 11:54:31.000000000 +0200 +++ new/bpython-0.11/bpython/urwid.py 2012-04-04 23:06:42.000000000 +0200 @@ -37,6 +37,7 @@ import sys import os +import time import locale import signal from types import ModuleType @@ -112,36 +113,61 @@ return EvalProtocol(self.repl) -class TwistedEventLoop(urwid.TwistedEventLoop): +if urwid.VERSION < (1, 0, 0): + class TwistedEventLoop(urwid.TwistedEventLoop): - """TwistedEventLoop modified to properly stop the reactor. + """TwistedEventLoop modified to properly stop the reactor. + + urwid 0.9.9 and 0.9.9.1 crash the reactor on ExitMainLoop instead + of stopping it. One obvious way this breaks is if anything used + the reactor's thread pool: that thread pool is not shut down if + the reactor is not stopped, which means python hangs on exit + (joining the non-daemon threadpool threads that never exit). And + the default resolver is the ThreadedResolver, so if we looked up + any names we hang on exit. That is bad enough that we hack up + urwid a bit here to exit properly. + """ + + def handle_exit(self, f): + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except urwid.ExitMainLoop: + # This is our change. + self.reactor.stop() + except: + # This is the same as in urwid. + # We are obviously not supposed to ever hit this. + import sys + print sys.exc_info() + self._exc_info = sys.exc_info() + self.reactor.crash() + return wrapper +else: + TwistedEventLoop = urwid.TwistedEventLoop - urwid 0.9.9 and 0.9.9.1 crash the reactor on ExitMainLoop instead - of stopping it. One obvious way this breaks is if anything used - the reactor's thread pool: that thread pool is not shut down if - the reactor is not stopped, which means python hangs on exit - (joining the non-daemon threadpool threads that never exit). And - the default resolver is the ThreadedResolver, so if we looked up - any names we hang on exit. That is bad enough that we hack up - urwid a bit here to exit properly. - """ - def handle_exit(self, f): - def wrapper(*args, **kwargs): - try: - return f(*args, **kwargs) - except urwid.ExitMainLoop: - # This is our change. - self.reactor.stop() - except: - # This is the same as in urwid. - # We are obviously not supposed to ever hit this. - import sys - print sys.exc_info() - self._exc_info = sys.exc_info() - self.reactor.crash() - return wrapper +class StatusbarEdit(urwid.Edit): + """Wrapper around urwid.Edit used for the prompt in Statusbar. + This class only adds a single signal that is emitted if the user presses + Enter.""" + + signals = urwid.Edit.signals + ['prompt_enter'] + + def __init__(self, *args, **kwargs): + self.single = False + urwid.Edit.__init__(self, *args, **kwargs) + + def keypress(self, size, key): + if self.single: + urwid.emit_signal(self, 'prompt_enter', self, key) + elif key == 'enter': + urwid.emit_signal(self, 'prompt_enter', self, self.get_edit_text()) + else: + return urwid.Edit.keypress(self, size, key) + +urwid.register_signal(StatusbarEdit, 'prompt_enter') class Statusbar(object): @@ -163,12 +189,88 @@ The "widget" attribute is an urwid widget. """ - def __init__(self, config, s=None): + signals = ['prompt_result'] + + def __init__(self, config, s=None, main_loop=None): self.config = config + self.timer = None + self.main_loop = main_loop self.s = s or '' - # XXX wrap in AttrMap for wrapping? - self.widget = urwid.Text(('main', self.s)) + self.text = urwid.Text(('main', self.s)) + # use wrap mode 'clip' to just cut off at the end of line + self.text.set_wrap_mode('clip') + + self.edit = StatusbarEdit(('main', '')) + urwid.connect_signal(self.edit, 'prompt_enter', self._on_prompt_enter) + + self.widget = urwid.Columns([self.text, self.edit]) + + def _check(self, callback, userdata=None): + """This is the method is called from the timer to reset the status bar.""" + self.timer = None + self.settext(self.s) + + def message(self, s, n=3): + """Display a message for a short n seconds on the statusbar and return + it to its original state.""" + + self.settext(s) + self.timer = self.main_loop.set_alarm_in(n, self._check) + + def _reset_timer(self): + """Reset the timer from message.""" + if self.timer is not None: + self.main_loop.remove_alarm(self.timer) + self.timer = None + + def prompt(self, s=None, single=False): + """Prompt the user for some input (with the optional prompt 's'). After + the user hit enter the signal 'prompt_result' will be emited and the + status bar will be reset. If single is True, the first keypress will be + returned.""" + + self._reset_timer() + + self.edit.single = single + self.edit.set_caption(('main', s or '?')) + self.edit.set_edit_text('') + # hide the text and display the edit widget + if not self.edit in self.widget.widget_list: + self.widget.widget_list.append(self.edit) + if self.text in self.widget.widget_list: + self.widget.widget_list.remove(self.text) + self.widget.set_focus_column(0) + + def settext(self, s, permanent=False): + """Set the text on the status bar to a new value. If permanent is True, + the new value will be permanent. If that status bar is in prompt mode, + the prompt will be aborted. """ + + self._reset_timer() + + # hide the edit and display the text widget + if self.edit in self.widget.widget_list: + self.widget.widget_list.remove(self.edit) + if not self.text in self.widget.widget_list: + self.widget.widget_list.append(self.text) + + self.text.set_text(('main', s)) + if permanent: + self.s = s + + def clear(self): + """Clear the status bar.""" + self.settext('') + + def _on_prompt_enter(self, edit, new_text): + """Reset the statusbar and pass the input from the prompt to the caller + via 'prompt_result'.""" + self.settext(self.s) + urwid.emit_signal(self, 'prompt_result', new_text) + +urwid.register_signal(Statusbar, 'prompt_result') + def decoding_input_filter(keys, raw): """Input filter for urwid which decodes each key with the locale's @@ -225,7 +327,7 @@ self._bpy_selectable = True self._bpy_may_move_cursor = False self.config = config - self.tab_length = config.tab_length + self.tab_length = config.tab_length urwid.Edit.__init__(self, *args, **kwargs) def set_edit_pos(self, pos): @@ -251,7 +353,11 @@ You should arrange for this to be called from the 'change' signal. """ - self._bpy_text, self._bpy_attr = urwid.decompose_tagmarkup(markup) + if markup: + self._bpy_text, self._bpy_attr = urwid.decompose_tagmarkup(markup) + else: + # decompose_tagmarkup in some urwids fails on the empty list + self._bpy_text, self._bpy_attr = '', [] # This is redundant when we're called off the 'change' signal. # I'm assuming this is cheap, making that ok. self._invalidate() @@ -305,9 +411,6 @@ self.set_edit_text(line[:-self.tab_length]) else: return urwid.Edit.keypress(self, size, key) - elif key == 'pastebin': - # do pastebin - pass else: # TODO: Add in specific keypress fetching code here return urwid.Edit.keypress(self, size, key) @@ -413,44 +516,64 @@ return canvas class URWIDInteraction(repl.Interaction): - def __init__(self, config, statusbar=None): + def __init__(self, config, statusbar, frame): repl.Interaction.__init__(self, config, statusbar) + self.frame = frame + urwid.connect_signal(statusbar, 'prompt_result', self._prompt_result) + self.callback = None - def confirm(self, q): - """Ask for yes or no and return boolean""" - try: - reply = self.statusbar.prompt(q) - except ValueError: - return False + def confirm(self, q, callback): + """Ask for yes or no and call callback to return the result""" - return reply.lower() in (_('y'), _('yes')) + def callback_wrapper(result): + callback(result.lower() in (_('y'), _('yes'))) + + self.prompt(q, callback_wrapper, single=True) def notify(self, s, n=10): return self.statusbar.message(s, n) + def prompt(self, s, callback=None, single=False): + """Prompt the user for input. The result will be returned via calling + callback. Note that there can only be one prompt active. But the + callback can already start a new prompt.""" + + if self.callback is not None: + raise Exception('Prompt already in progress') + + self.callback = callback + self.statusbar.prompt(s, single=single) + self.frame.set_focus('footer') + + def _prompt_result(self, text): + self.frame.set_focus('body') + if self.callback is not None: + # The callback might want to start another prompt, so reset it + # before calling the callback. + callback = self.callback + self.callback = None + callback(text) + class URWIDRepl(repl.Repl): + _time_between_redraws = .05 # seconds + def __init__(self, event_loop, palette, interpreter, config): repl.Repl.__init__(self, interpreter, config) - self.listbox = BPythonListBox(urwid.SimpleListWalker([])) - - # String is straight from bpython.cli - self.statusbar = Statusbar(config, - _(" <%s> Rewind <%s> Save <%s> Pastebin " - " <%s> Pager <%s> Show Source ") % - (config.undo_key, config.save_key, config.pastebin_key, - config.last_output_key, config.show_source_key)) + self._redraw_handle = None + self._redraw_pending = False + self._redraw_time = 0 - self.interact = URWIDInteraction(self.config, self.statusbar) + self.listbox = BPythonListBox(urwid.SimpleListWalker([])) self.tooltip = urwid.ListBox(urwid.SimpleListWalker([])) self.tooltip.grid = None self.overlay = Tooltip(self.listbox, self.tooltip) self.stdout_hist = '' - self.frame = urwid.Frame(self.overlay, footer=self.statusbar.widget) + self.frame = urwid.Frame(self.overlay) if urwid.get_encoding_mode() == 'narrow': input_filter = decoding_input_filter @@ -463,6 +586,15 @@ event_loop=event_loop, unhandled_input=self.handle_input, input_filter=input_filter, handle_mouse=False) + # String is straight from bpython.cli + self.statusbar = Statusbar(config, + _(" <%s> Rewind <%s> Save <%s> Pastebin " + " <%s> Pager <%s> Show Source ") % + (config.undo_key, config.save_key, config.pastebin_key, + config.last_output_key, config.show_source_key), self.main_loop) + self.frame.set_footer(self.statusbar.widget) + self.interact = URWIDInteraction(self.config, self.statusbar, self.frame) + self.edits = [] self.edit = None self.current_output = None @@ -478,6 +610,13 @@ self.current_output = urwid.Text(('output', s)) if self.edit is None: self.listbox.body.append(self.current_output) + # Focus the widget we just added to force the + # listbox to scroll. This causes output to scroll + # if the user runs a blocking call that prints + # more than a screenful, instead of staying + # scrolled to the previous input line and then + # jumping to the bottom when done. + self.listbox.set_focus(len(self.listbox.body) - 1) else: self.listbox.body.insert(-1, self.current_output) # The edit widget should be focused and *stay* focused. @@ -489,9 +628,37 @@ ('output', self.current_output.text + s)) if orig_s.endswith('\n'): self.current_output = None - # TODO: maybe do the redraw after a short delay - # (for performance) - self.main_loop.draw_screen() + + # If we hit this repeatedly in a loop the redraw is rather + # slow (testcase: pprint(__builtins__). So if we have recently + # drawn the screen already schedule a call in the future. + # + # Unfortunately we may hit this function repeatedly through a + # blocking call triggered by the user, in which case our + # timeout will not run timely as we do not return to urwid's + # eventloop. So we manually check if our timeout has long + # since expired, and redraw synchronously if it has. + if self._redraw_handle is None: + self.main_loop.draw_screen() + + def maybe_redraw(loop, self): + if self._redraw_pending: + loop.draw_screen() + self._redraw_pending = False + + self._redraw_handle = None + + self._redraw_handle = self.main_loop.set_alarm_in( + self._time_between_redraws, maybe_redraw, self) + self._redraw_time = time.time() + else: + self._redraw_pending = True + now = time.time() + if now - self._redraw_time > 2 * self._time_between_redraws: + # The timeout is well past expired, assume we're + # blocked and redraw synchronously. + self.main_loop.draw_screen() + self._redraw_time = now def current_line(self): """Return the current line (the one the cursor is in).""" @@ -513,7 +680,7 @@ # Stolen from cli. TODO: clean up and split out. if (not text or (not text[-1].isalnum() and text[-1] not in ('.', '_'))): - return + return # Seek backwards in text for the first non-identifier char: for i, c in enumerate(reversed(text)): @@ -740,8 +907,20 @@ self.prompt(False) def keyboard_interrupt(self): - # Do we need to do more here? Break out of multiline input perhaps? - self.echo('KeyboardInterrupt') + # If the user is currently editing, interrupt him. This + # mirrors what the regular python REPL does. + if self.edit is not None: + # XXX this is a lot of code, and I am not sure it is + # actually enough code. Needs some testing. + self.edit.make_readonly() + self.edit = None + self.buffer = [] + self.echo('KeyboardInterrupt') + self.prompt(False) + else: + # I do not quite remember if this is reachable, but let's + # be safe. + self.echo('KeyboardInterrupt') def prompt(self, more): # Clear current output here, or output resulting from the @@ -751,14 +930,20 @@ # XXX is this the right place? self.rl_history.reset() # XXX what is s_hist? + + # We need the caption to use unicode as urwid normalizes later + # input to be the same type, using ascii as encoding. If the + # caption is bytes this breaks typing non-ascii into bpython. + # Currently this decodes using ascii as I do not know where + # ps1 is getting loaded from. If anyone wants to make + # non-ascii prompts work feel free to fix this. if not more: - self.edit = BPythonEdit(self.config, - caption=('prompt', self.ps1)) + caption = ('prompt', self.ps1.decode('ascii')) self.stdout_hist += self.ps1 else: - self.edit = BPythonEdit(self.config, - caption=('prompt_more', self.ps2)) + caption = ('prompt_more', self.ps2.decode('ascii')) self.stdout_hist += self.ps2 + self.edit = BPythonEdit(self.config, caption=caption) urwid.connect_signal(self.edit, 'change', self.on_input_change) urwid.connect_signal(self.edit, 'edit-pos-changed', @@ -791,6 +976,11 @@ edit.set_edit_markup(list(format_tokens(tokens))) def handle_input(self, event): + # Since most of the input handling here should be handled in the edit + # instead, we return here early if the edit doesn't have the focus. + if self.frame.get_focus() != 'body': + return + if event == 'enter': inp = self.edit.get_edit_text() self.history.append(inp) @@ -904,10 +1094,14 @@ ])) if options.help_reactors: - from twisted.application import reactors - # Stolen from twisted.application.app (twistd). - for r in reactors.getReactorTypes(): - print ' %-4s\t%s' % (r.shortName, r.description) + try: + from twisted.application import reactors + # Stolen from twisted.application.app (twistd). + for r in reactors.getReactorTypes(): + print ' %-4s\t%s' % (r.shortName, r.description) + except ImportError: + sys.stderr.write('No reactors are available. Please install ' + 'twisted for reactor support.\n') return palette = [ @@ -922,7 +1116,12 @@ options.reactor = 'select' if options.reactor: - from twisted.application import reactors + try: + from twisted.application import reactors + except ImportError: + sys.stderr.write('No reactors are available. Please install ' + 'twisted for reactor support.\n') + return try: # XXX why does this not just return the reactor it installed? reactor = reactors.installReactor(options.reactor) @@ -945,8 +1144,14 @@ locals_ = main_mod.__dict__ if options.plugin: - from twisted import plugin - from twisted.application import service + try: + from twisted import plugin + from twisted.application import service + except ImportError: + sys.stderr.write('No twisted plugins are available. Please install ' + 'twisted for twisted plugin support.\n') + return + for plug in plugin.getPlugins(service.IServiceMaker): if plug.tapname == options.plugin: break @@ -1066,6 +1271,7 @@ urwid.command_map[key_dispatch[config.down_one_line_key]] = 'cursor down' urwid.command_map[key_dispatch['C-a']] = 'cursor max left' urwid.command_map[key_dispatch['C-e']] = 'cursor max right' + urwid.command_map[key_dispatch[config.pastebin_key]] = 'pastebin' """ 'clear_line': 'C-u', 'clear_screen': 'C-l', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython.egg-info/PKG-INFO new/bpython-0.11/bpython.egg-info/PKG-INFO --- old/bpython-0.10.1/bpython.egg-info/PKG-INFO 2011-08-09 12:22:14.000000000 +0200 +++ new/bpython-0.11/bpython.egg-info/PKG-INFO 2012-04-04 23:11:06.000000000 +0200 @@ -1,11 +1,11 @@ Metadata-Version: 1.0 Name: bpython -Version: 0.10.1 +Version: 0.11 Summary: Fancy Interface to the Python Interpreter Home-page: http://www.bpython-interpreter.org/ Author: Bob Farrell, Andreas Stuehrk et al. Author-email: [email protected] License: MIT/X Description: bpython is a fancy interface to the Python - interpreter for Unix-like operating systems. + interpreter for Unix-like operating systems. Platform: UNKNOWN diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/bpython.egg-info/SOURCES.txt new/bpython-0.11/bpython.egg-info/SOURCES.txt --- old/bpython-0.10.1/bpython.egg-info/SOURCES.txt 2011-08-09 12:22:14.000000000 +0200 +++ new/bpython-0.11/bpython.egg-info/SOURCES.txt 2012-04-04 23:11:06.000000000 +0200 @@ -10,6 +10,7 @@ sample-config sample.theme setup.py +windows.theme bpdb/__init__.py bpdb/debugger.py bpython/__init__.py @@ -47,11 +48,8 @@ bpython/test/test_repl.py bpython/test/test_wizard.py bpython/translations/__init__.py -bpython/translations/es_ES/LC_MESSAGES/bpython.mo bpython/translations/es_ES/LC_MESSAGES/bpython.po -bpython/translations/it_IT/LC_MESSAGES/bpython.mo bpython/translations/it_IT/LC_MESSAGES/bpython.po -bpython/translations/nl_NL/LC_MESSAGES/bpython.mo bpython/translations/nl_NL/LC_MESSAGES/bpython.po data/bpython data/bpython-gtk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/doc/bpython-config.5 new/bpython-0.11/doc/bpython-config.5 --- old/bpython-0.10.1/doc/bpython-config.5 2011-08-09 11:44:51.000000000 +0200 +++ new/bpython-0.11/doc/bpython-config.5 2012-04-04 23:06:42.000000000 +0200 @@ -18,6 +18,7 @@ .SH NAME config \- user configuration file for bpython .SH SYNOPSIS +.B $XDG_CONFIG_HOME/bpython/config .B ~/.bpython/config .SH DESCRIPTION The diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/setup.py new/bpython-0.11/setup.py --- old/bpython-0.10.1/setup.py 2011-08-09 11:44:51.000000000 +0200 +++ new/bpython-0.11/setup.py 2012-04-04 23:06:42.000000000 +0200 @@ -50,7 +50,7 @@ cmdclass['extract_messages'] = extract_messages -if platform.system() == 'FreeBSD': +if platform.system() in ['FreeBSD', 'OpenBSD']: man_dir = 'man' else: man_dir = 'share/man' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bpython-0.10.1/windows.theme new/bpython-0.11/windows.theme --- old/bpython-0.10.1/windows.theme 1970-01-01 01:00:00.000000000 +0100 +++ new/bpython-0.11/windows.theme 2012-04-04 23:06:43.000000000 +0200 @@ -0,0 +1,28 @@ +# Each letter represents a colour marker: +# k, r, g, y, b, m, c, w, d +# which stands for: +# blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default +# +# But in Windows (for now) +# K, R, G, Y, B, M, C, W, k +# blacK, blue, Green, cyan, red, Magenta, yellow, hite, grey +# Capital letters represent bold (brighter) +# Copy to %USERPROFILE%\.bpython\windows.theme and set "color_scheme = windows" in %USERPROFILE%\.bpython\config + +[syntax] +keyword = G +name = Y +comment = c +string = M +error = B +number = C +operator = G +punctuation = C +token = M + +[interface] +background = r +output = w +main = W +prompt = W +prompt_more = K -- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
