From 80765a8017973c643566ab64f76371fa5bc4e126 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 20 Apr 2015 22:08:50 +0200 Subject: [PATCH 0001/1094] Fix bpdb name --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e34a95981..14151fabb 100755 --- a/setup.py +++ b/setup.py @@ -247,7 +247,7 @@ def initialize_options(self): 'bpython = bpython.curtsies:main', 'bpython-curses = bpython.cli:main', 'bpython-urwid = bpython.urwid:main [urwid]', - 'bpbd = bpdb:main' + 'bpdb = bpdb:main' ] } From bb1389adfaefa125f5637320229295a62c3d74f7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 20 Apr 2015 23:37:59 +0200 Subject: [PATCH 0002/1094] Add Keywords --- data/bpython.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/data/bpython.desktop b/data/bpython.desktop index bdb121590..d28912bcc 100644 --- a/data/bpython.desktop +++ b/data/bpython.desktop @@ -7,3 +7,4 @@ Terminal=true Type=Application Categories=Development;Utility;ConsoleOnly; StartupNotify=true +Keywords=Python;REPL;interpreter; From 9fbb325b8cdd1fb3292cf10d60a4ac0fa71849f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 26 Apr 2015 16:48:20 +0200 Subject: [PATCH 0003/1094] Test Python 3.5 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index fa6d1945b..ab0053adb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "2.7" - "3.3" - "3.4" + - "3.5" - "pypy" - "pypy3" From bdc1281b4ddbe15af472c49e935f031d18bf9b94 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 26 Apr 2015 21:25:02 +0200 Subject: [PATCH 0004/1094] Install pyOpenSSL, pyasn1 and ndg-httpsclient (fixes: #523, #524) Depending on requests[security] doesn't seem to work. Signed-off-by: Sebastian Ramacher --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 14151fabb..db50b5f7a 100755 --- a/setup.py +++ b/setup.py @@ -229,7 +229,9 @@ def initialize_options(self): 'python_full_version == "2.7.4" or ' \ 'python_full_version == "2.7.5" or ' \ 'python_full_version == "2.7.6"': [ - 'requests[security]' + 'pyOpenSSL', + 'pyasn1', + 'ndg-httpsclient' ] } From a33048d500bdad5dd7232ac6f4d117fc9df8dd32 Mon Sep 17 00:00:00 2001 From: thekthuser Date: Sun, 26 Apr 2015 16:56:07 -0400 Subject: [PATCH 0005/1094] fixed bug #506 --- bpython/args.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 6fd2b519c..91c8fc7fe 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -3,9 +3,10 @@ """ from __future__ import print_function +import code +import imp import os import sys -import code from optparse import OptionParser, OptionGroup from bpython import __version__ @@ -114,6 +115,9 @@ def exec_code(interpreter, args): source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) + mod = imp.new_module('__console__') + sys.modules['__console__'] = mod + interpreter.locals = mod.__dict__ interpreter.locals['__file__'] = args[0] interpreter.runsource(source, args[0], 'exec') sys.argv = old_argv From fa3ad3a2708bcc0b1c385b8b4328b09c8f4360e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:03:16 +0200 Subject: [PATCH 0006/1094] Display exception from subprocess Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 877bafc53..7c479f605 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1001,9 +1001,7 @@ def open_in_external_editor(self, filename): editor_args = shlex.split(prepare_for_exec(self.config.editor, encoding)) args = editor_args + [prepare_for_exec(filename, encoding)] - if subprocess.call(args) == 0: - return True - return False + return subprocess.call(args) == 0: def edit_config(self): if not (os.path.isfile(self.config.config_path)): @@ -1028,11 +1026,12 @@ def edit_config(self): else: return False - if self.open_in_external_editor(self.config.config_path): - self.interact.notify(_('bpython config file edited. Restart ' - 'bpython for changes to take effect.')) - else: - self.interact.notify(_('Error editing config file.')) + try: + if self.open_in_external_editor(self.config.config_path): + self.interact.notify(_('bpython config file edited. Restart ' + 'bpython for changes to take effect.')) + except OSError as e: + self.interact.notify(_('Error editing config file: %s') % e) def next_indentation(line, tab_length): From fb07d9694d3d224983e2ecabbea036e73087e30e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:13:20 +0200 Subject: [PATCH 0007/1094] Fix SyntaxError Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 7c479f605..184a6665e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1001,7 +1001,7 @@ def open_in_external_editor(self, filename): editor_args = shlex.split(prepare_for_exec(self.config.editor, encoding)) args = editor_args + [prepare_for_exec(filename, encoding)] - return subprocess.call(args) == 0: + return subprocess.call(args) == 0 def edit_config(self): if not (os.path.isfile(self.config.config_path)): From 6e69e89d1e99187b841ed4fa5fd26d39a88b430b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:17:09 +0200 Subject: [PATCH 0008/1094] Fix SyntaxError Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 184a6665e..64673b5d5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1030,8 +1030,8 @@ def edit_config(self): if self.open_in_external_editor(self.config.config_path): self.interact.notify(_('bpython config file edited. Restart ' 'bpython for changes to take effect.')) - except OSError as e: - self.interact.notify(_('Error editing config file: %s') % e) + except OSError as e: + self.interact.notify(_('Error editing config file: %s') % e) def next_indentation(line, tab_length): From c76588768458ca72d52ce34d23b5988d55b3e1cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:35:56 +0200 Subject: [PATCH 0009/1094] Run directories containing __main__.py (fixes #530) Signed-off-by: Sebastian Ramacher --- bpython/args.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 6fd2b519c..95590bdd7 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,6 +4,7 @@ from __future__ import print_function import os +import os.path import sys import code from optparse import OptionParser, OptionGroup @@ -110,10 +111,15 @@ def exec_code(interpreter, args): Helper to execute code in a given interpreter. args should be a [faked] sys.argv """ - with open(args[0], 'r') as sourcefile: + plusmain = os.path.join(args[0], '__main__.py') + if os.path.isdir(args[0]) and os.path.exists(plusmain): + path = plusmain + else: + path = args[0] + with open(path, 'r') as sourcefile: source = sourcefile.read() old_argv, sys.argv = sys.argv, args - sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) - interpreter.locals['__file__'] = args[0] - interpreter.runsource(source, args[0], 'exec') + sys.path.insert(0, os.path.abspath(os.path.dirname(path))) + interpreter.locals['__file__'] = path + interpreter.runsource(source, path, 'exec') sys.argv = old_argv From c83d08dc9c668a54f8fc20624195f6d8519068c3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 30 Apr 2015 16:33:35 +0200 Subject: [PATCH 0010/1094] Revert "Run directories containing __main__.py (fixes #530)" This reverts commit c76588768458ca72d52ce34d23b5988d55b3e1cc. --- bpython/args.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 95590bdd7..6fd2b519c 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,7 +4,6 @@ from __future__ import print_function import os -import os.path import sys import code from optparse import OptionParser, OptionGroup @@ -111,15 +110,10 @@ def exec_code(interpreter, args): Helper to execute code in a given interpreter. args should be a [faked] sys.argv """ - plusmain = os.path.join(args[0], '__main__.py') - if os.path.isdir(args[0]) and os.path.exists(plusmain): - path = plusmain - else: - path = args[0] - with open(path, 'r') as sourcefile: + with open(args[0], 'r') as sourcefile: source = sourcefile.read() old_argv, sys.argv = sys.argv, args - sys.path.insert(0, os.path.abspath(os.path.dirname(path))) - interpreter.locals['__file__'] = path - interpreter.runsource(source, path, 'exec') + sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) + interpreter.locals['__file__'] = args[0] + interpreter.runsource(source, args[0], 'exec') sys.argv = old_argv From 87ce63e248768d7314a0b2eccb51d2691d1a1310 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 30 Apr 2015 18:39:39 +0200 Subject: [PATCH 0011/1094] Remove useless assert Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 64673b5d5..8536d4fab 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -636,7 +636,6 @@ def complete(self, tab=False): return completer.shown_before_tab else: - assert len(matches) > 1 return tab or completer.shown_before_tab def format_docstring(self, docstring, width, height): From 68cae80f47d5eb1d20f65bb5e1b402bee2d3a379 Mon Sep 17 00:00:00 2001 From: Steven Leiva Date: Sun, 26 Apr 2015 15:25:10 -0400 Subject: [PATCH 0012/1094] Update README.rst --- README.rst | 242 +++++++++++++++++++++++++++-------------------------- 1 file changed, 123 insertions(+), 119 deletions(-) diff --git a/README.rst b/README.rst index bf5fb6e71..8a67c21ed 100644 --- a/README.rst +++ b/README.rst @@ -2,12 +2,87 @@ .. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=master .. _ImageLink: https://travis-ci.org/bpython/bpython -bpython - A fancy curses interface to the Python interactive interpreter -======================================================================== +*********************************************************************** +bpython: A fancy curses interface to the Python interactive interpreter +*********************************************************************** + +`bpython`_ is a lightweight Python interpreter that adds several features common +to IDEs. These features include **syntax highlighting**, **expected parameter +list**, **auto-indentation**, and **autocompletion**. (See below for example +usage). + +.. image:: http://i.imgur.com/jf8mCtP.gif + :alt: bpython + :width: 646 + :height: 300 + :align: center + +bpython does **not** aim to be a complete IDE - the focus is on implementing a +few ideas in a practical, useful, and lightweight manner. + +bpython is a great replacement to any occassion where you would normally use the +vanilla Python interpreter - testing out solutions to people's problems on IRC, +quickly testing a method of doing something without creating a temporary file, +etc.. + +You can find more about bpython - including `full documentation`_ - at our +`homepage`_. + +.. contents:: + :local: + :depth: 1 + :backlinks: none + +========================== +Installation & Basic Usage +========================== +If you have `pip`_ installed, you can simply run: + +.. code-block:: bash + + $ pip install bpython + +Start bpython by typing ``bpython`` in your terminal. You can exit bpython by +using the ``exit()`` command. + +=================== +Features & Examples +=================== +* In-line syntax highlighting. This uses Pygments for lexing the code as you + type, and colours appropriately. + +* Readline-like autocomplete. Suggestions displayed as you type. + +* Expected parameter list. As in a lot of modern IDEs, bpython will attempt to + display a list of parameters for any function you call. The inspect module is + tried first, which works with any Python function, and then pydoc if that + fails. + +* Rewind. This isn't called "Undo" because it would be misleading, but "Rewind" + is probably as bad. The idea is that the code entered is kept in memory and + when the Rewind function is called, the last line is popped and the entire + code is re-evaluated. + +* Pastebin code/write to file. Use the key to upload the screen's contents + to pastebin, with a URL returned. + +* Flush curses screen to stdout. When you quit bpython, the screen data will be + flushed to stdout, so it basically looks the same as if you had quit the + vanilla interpreter. + +============= +Configuration +============= +See the sample-config file for a list of available options. 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 + +============ Dependencies ============ - * Pygments * requests * curtsies >= 0.1.18 @@ -18,14 +93,17 @@ Dependencies * watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) -If you are using Python 2 before 2.7.7, the following dependency is also +Python 2 before 2.7.7 +--------------------- +If you are using Python 2 before 2.7.7, the followign dependency is also required: * requests[security] -If you have problems installing cffi which is needed by pyOpenSSL, -please take a look at https://cffi.readthedocs.org/en/release-0.8/#macos-x. - +cffi +---- +If you have problems installing cffi, which is needed by OpenSSL, please take a +look at `cffi docs`_. bpython-urwid ------------- @@ -33,140 +111,66 @@ bpython-urwid * urwid -Introduction -============ -A few people asked for stuff like syntax highlighting and autocomplete for the -Python interactive interpreter. IPython seems to offer this (plus you can get -readline behaviour in the vanilla interpreter) but I tried IPython a couple of -times. Perhaps I didn't really get it, but I get the feeling that the ideas -behind IPython are pretty different to bpython. I didn't want to create a whole -development environment; I simply wanted to provide a couple of neat features -that already exist and turn them into something a little more interactive. - -The idea is to provide the user with all the features in-line, much like modern -IDEs, but in a simple, lightweight package that can be run in a terminal -window, so curses seemed like the best choice. Sorry if you use Windows. - -bpython doesn't attempt to create anything new or groundbreaking, it simply -brings together a few neat ideas and focuses on practicality and usefulness. -For this reason, the "Rewind" function should be taken with a pinch of salt, -but personally I have found it to be very useful. I use bpython now whenever I -would normally use the vanilla interpreter, e.g. for testing out solutions to -people's problems on IRC, quickly testing a method of doing something without -creating a temporary file, etc.. - -I hope you find it useful and please feel free to submit any bugs/patches (yeah -right)/suggestions to: -robertanthonyfarrell@gmail.com -or place them at the github issue page for this project at: -http://github.com/bpython/bpython/issues/ +========== +Known Bugs +========== +For known bugs please see bpython's `known issues and FAQ`_ page. -For any other ways of communicating with bpython users and devs you can find us -at the community page on the projects homepage: -http://bpython-interpreter.org/community +====================== +Contact & Contributing +====================== +I hope you find it useful and please feel free to submit any bugs/patches +suggestions to `Robert`_ or place them on the github +`issues tracker`_. -Or in the documentation at http://docs.bpython-interpreter.org/community.html. +For any other ways of communicating with bpython users and devs you can find us +at the community page on the `project homepage`_, or in the `community`_. Hope to see you there! -Features -======== - -* In-line syntax highlighting. - This uses Pygments for lexing the code as you type, and colours - appropriately. Pygments does a great job of doing all of the tricky stuff - and really leaving me with very little to do except format the tokens in - all my favourite colours. - -* Readline-like autocomplete with suggestions displayed as you type. - Thanks to Python's readline interface to libreadline and a ready-made class - for using a Python interpreter's scope as the dataset, the only work here - was displaying the readline matches as you type in a separate curses window - below/above the cursor. - -* Expected parameter list. - As in a lot of modern IDEs, bpython will attempt to display a list of - parameters for any function you call. The inspect module is tried first, - which works with any Python function, and then pydoc if that fails, which - seems to be pretty adequate, but obviously in some cases it's simply not - possible. I used pyparsing to cure my nested parentheses woes; again, it - was nice and easy. - -* Rewind. - I didn't call this "Undo" because I thought that would be misleading, but - "Rewind" is probably as bad. The idea is that the code entered is kept in - memory and when the Rewind function is called, the last line is popped and - the entire code is re-evaluated. As you can imagine, this has a lot of - potential problems, but for defining classes and functions, I've found it - to be nothing but useful. - -* Pastebin code/write to file. - I don't really use the save thing much, but the pastebin thing's great. Hit - a key and what you see on the screen will be sent to a pastebin and a URL - is returned for you to do what you like with. I've hardcoded - paste.pocoo.org in for now, that needs to be fixed so it's configurable. - Next release, I promise. - -* Flush curses screen to stdout. - A featurette, perhaps, but I thought it was worth noting. I can't - personally recall a curses app that does this, perhaps it's often not - useful, but when you quit bpython, the screen data will be flushed to - stdout, so it basically looks the same as if you had quit the vanilla - interpreter. - -Configuration -============= -See the sample-config file for a list of available options. 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 - -Known Bugs -========== -For known bugs please see bpython's issue tracker at github: - -http://github.com/bpython/bpython/issues/ - +=================== CLI Windows Support =================== Dependencies ------------ -Curses - Use the appropriate version compiled by Christoph Gohlke - http://www.lfd.uci.edu/~gohlke/pythonlibs/ +`Curses`_ Use the appropriate version compiled by Christoph Gohlke. -pyreadline - Use the version in the cheeseshop - http://pypi.python.org/pypi/pyreadline/ +`pyreadline`_ Use the version in the cheeseshop. +=========== 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 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 +* red is swapped with blue +* cyan is swapped with yellow -To correct this I have provided my windows.theme file. +To correct this I have provided a windows.theme file. This curses implementation has 16 colors (dark and light versions of the colours) -See also -======== - -Documentation - http://docs.bpython-interpreter.org/ - -Developer documentation - http://docs.bpython-interpreter.org/contributing.html +.. _homepage: http://www.bpython-interpreter.org +.. _full documentation: http://docs.bpython-interpreter.org/ +.. _cffi docs: https://cffi.readthedocs.org/en/release-0.8/#macos-x +.. _issues tracker: http://github.com/bpython/bpython/issues/ +.. _pip: https://pip.pypa.io/en/latest/index.html +.. _project homepage: http://bpython-interpreter.org/community +.. _community: http://docs.bpython-interpreter.org/community.html +.. _Robert: robertanthonyfarrell@gmail.com +.. _bpython: http://www.bpython-interpreter.org/ +.. _Curses: http://www.lfd.uci.edu/~gohlke/pythonlibs/ +.. _pyreadline: http://pypi.python.org/pypi/pyreadline/ +.. _known issues and FAQ: http://bpython-interpreter.org/known-issues-and-faq.html From e898411c03315c67030364283ac75ed0da180b8b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 May 2015 00:35:14 +0200 Subject: [PATCH 0013/1094] Add missing new line --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 8a67c21ed..aae559515 100644 --- a/README.rst +++ b/README.rst @@ -154,6 +154,7 @@ Current version is tested with Curses Notes ============ The curses used has a bug where the colours are displayed incorrectly: + * red is swapped with blue * cyan is swapped with yellow From 24e5c69497a06bb983589919d410fec74b7cf04a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 May 2015 20:16:41 +0200 Subject: [PATCH 0014/1094] Failing test for #536 --- bpython/test/test_autocomplete.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index a6f2f153a..7d86569b8 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -251,6 +251,16 @@ def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) + @skip_old_style + @unittest.expectedFailure + def test_issue536(self): + class OldStyleWithBrokenGetAttr: + def __getattr__(self, attr): + raise Exception() + + locals_ = {'a': OldStyleWithBrokenGetAttr()} + self.com.matches(2, 'a.', locals_=locals_) + class TestMagicMethodCompletion(unittest.TestCase): From e74bbd20644df265a85549f046e8fa11271faba2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 May 2015 20:18:02 +0200 Subject: [PATCH 0015/1094] Move if py3 to module level Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index b188dc6c7..e0cc140df 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -286,8 +286,11 @@ def get_encoding_file(fname): return 'ascii' -def get_source_unicode(obj): - """Returns a decoded source of object""" - if py3: +if py3: + def get_source_unicode(obj): + """Returns a decoded source of object""" return inspect.getsource(obj) - return inspect.getsource(obj).decode(get_encoding(obj)) +else: + def get_source_unicode(obj): + """Returns a decoded source of object""" + return inspect.getsource(obj).decode(get_encoding(obj)) From 674c3bfbd35853001850b5a1c610b4e3c3a71bba Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 May 2015 20:57:47 +0200 Subject: [PATCH 0016/1094] Add workaround for old-style class with broken __getattr__ (fixes #536) Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 19 ++++++++++++++++++- bpython/test/test_autocomplete.py | 4 ++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a65ed913c..3a009c38a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -296,7 +296,7 @@ def attr_lookup(self, obj, expr, attr): """Second half of original attr_matches method factored out so it can be wrapped in a safe try/finally block in case anything bad happens to restore the original __getattribute__ method.""" - words = dir(obj) + words = self.list_attributes(obj) if hasattr(obj, '__class__'): words.append('__class__') words = words + rlcompleter.get_class_members(obj.__class__) @@ -317,6 +317,23 @@ def attr_lookup(self, obj, expr, attr): matches.append("%s.%s" % (expr, word)) return matches + if py3: + def list_attributes(self, obj): + return dir(obj) + else: + def list_attributes(self, obj): + if isinstance(obj, InstanceType): + try: + return dir(obj) + except Exception: + # This is a case where we can not prevent user code from + # running. We return a default list attributes on error + # instead. (#536) + return ['__doc__', '__module__'] + else: + return dir(obj) + + class DictKeyCompletion(BaseCompletionType): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 7d86569b8..bfe1b0b66 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -252,14 +252,14 @@ def test_att_matches_found_on_old_style_class_object(self): self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) @skip_old_style - @unittest.expectedFailure def test_issue536(self): class OldStyleWithBrokenGetAttr: def __getattr__(self, attr): raise Exception() locals_ = {'a': OldStyleWithBrokenGetAttr()} - self.com.matches(2, 'a.', locals_=locals_) + self.assertIn(u'a.__module__', + self.com.matches(3, 'a._', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 206b0c5601577bf459b4a55ae45f78a5634ca147 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:13:34 +0200 Subject: [PATCH 0017/1094] Remove unused method Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 3a009c38a..8510abba5 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -136,9 +136,6 @@ def shown_before_tab(self): once that has happened.""" return self._shown_before_tab - def method_match(self, word, size, text): - return word[:size] == text - class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" From 360224229eea6cd3cf2de3c64cd2ae13367e5578 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:13:46 +0200 Subject: [PATCH 0018/1094] Move regex definition --- bpython/autocomplete.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 8510abba5..1d0a00060 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -223,11 +223,10 @@ def format(self, filename): return filename -attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") - - class AttrCompletion(BaseCompletionType): + attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") + def matches(self, cursor_offset, line, **kwargs): if 'locals_' not in kwargs: return None @@ -272,7 +271,7 @@ def attr_matches(self, text, namespace): # Gna, Py 2.6's rlcompleter searches for __call__ inside the # instance instead of the type, so we monkeypatch to prevent # side-effects (__getattr__/__getattribute__) - m = attr_matches_re.match(text) + m = self.attr_matches_re.match(text) if not m: return [] From a8787b350a9f647317150d2b5b2877c8429b8c77 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:14:08 +0200 Subject: [PATCH 0019/1094] Decode before comparing Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1d0a00060..d690e3e4e 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -402,11 +402,11 @@ def matches(self, cursor_offset, line, **kwargs): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): + word = try_decode(word, 'ascii') + # if identifier isn't ascii, don't complete (syntax error) + if word is None: + continue if self.method_match(word, n, text) and word != "__builtins__": - word = try_decode(word, 'ascii') - # if identifier isn't ascii, don't complete (syntax error) - if word is None: - continue matches.add(_callable_postfix(val, word)) return matches From dbd8066697d41ebb82c604e2c165f902bc68f42c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:38:21 +0200 Subject: [PATCH 0020/1094] Clean up unittest imports Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 +- bpython/test/test_config.py | 6 +----- bpython/test/test_crashers.py | 5 +---- bpython/test/test_curtsies_coderunner.py | 8 +------- bpython/test/test_curtsies_parser.py | 6 +----- bpython/test/test_curtsies_repl.py | 7 +------ bpython/test/test_filewatch.py | 7 +------ bpython/test/test_history.py | 6 +----- bpython/test/test_importcompletion.py | 6 +----- bpython/test/test_inspection.py | 7 ++----- bpython/test/test_interpreter.py | 9 +++------ bpython/test/test_keys.py | 8 ++------ bpython/test/test_line_properties.py | 6 +----- bpython/test/test_manual_readline.py | 6 +----- bpython/test/test_preprocess.py | 10 +++------- bpython/test/test_repl.py | 8 +++----- 16 files changed, 24 insertions(+), 83 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index d690e3e4e..7e89f4a66 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -488,7 +488,7 @@ def matches(self, cursor_offset, line, **kwargs): self._orig_start = None return None except IndexError: - # for https://github.com/bpython/bpython/issues/483 + # for #483 self._orig_start = None return None diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 16def998f..6b8c5dea4 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -2,11 +2,7 @@ import tempfile import textwrap -try: - import unittest2 as unittest -except ImportError: - import unittest - +from bpython.test import unittest from bpython import config TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index d07195929..5db661bbf 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -6,10 +6,7 @@ import termios import textwrap -try: - import unittest2 as unittest -except ImportError: - import unittest +from bpython.test import unittest try: from twisted.internet import reactor diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 2446ecc65..80af72760 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,12 +1,6 @@ import sys -from bpython.test import mock - -try: - import unittest2 as unittest -except ImportError: - import unittest - +from bpython.test import mock, unittest from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 4b2c96e00..0d765bab2 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,10 +1,6 @@ from __future__ import unicode_literals -try: - import unittest2 as unittest -except ImportError: - import unittest - +from bpython.test import unittest from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index fe2363914..5f85825e0 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -9,11 +9,6 @@ from contextlib import contextmanager from six.moves import StringIO -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython.curtsiesfrontend import events as bpythonevents @@ -22,7 +17,7 @@ from bpython import args from bpython._py3compat import py3 from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, - builtin_target) + builtin_target, unittest) def setup_config(conf): diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 5f94f40ec..e9e08449e 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -1,10 +1,5 @@ import os -try: - import unittest2 as unittest -except ImportError: - import unittest - try: import watchdog from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler @@ -12,7 +7,7 @@ except ImportError: has_watchdog = False -from bpython.test import mock +from bpython.test import mock, unittest @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index e32431d06..abbe99d1f 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,11 +1,7 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - from six.moves import range from bpython.history import History +from bpython.test import unittest class TestHistory(unittest.TestCase): diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index cd301f504..4df736b54 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,11 +1,7 @@ from __future__ import unicode_literals from bpython import importcompletion - -try: - import unittest2 as unittest -except ImportError: - import unittest +from bpython.test import unittest class TestSimpleComplete(unittest.TestCase): diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 35eea43be..b91f0ffbb 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -2,16 +2,13 @@ import os -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython import inspection +from bpython.test import unittest from bpython.test.fodder import encoding_ascii from bpython.test.fodder import encoding_latin1 from bpython.test.fodder import encoding_utf8 + foo_ascii_only = u'''def foo(): """Test""" pass diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 43118096f..c0b9bc50b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -5,24 +5,21 @@ import linecache import sys -try: - import unittest2 as unittest -except ImportError: - import unittest - from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain from bpython.curtsiesfrontend import interpreter from bpython._py3compat import py3 -from bpython.test import mock +from bpython.test import mock, unittest pypy = 'PyPy' in sys.version + def _last_console_filename(): """Returns the last 'filename' used for console input (as will be displayed in a traceback).""" return '' % (len(linecache.cache.bpython_history) - 1) + class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): i = interpreter.Interp() diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 3fb1f3663..bf15542dd 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -1,9 +1,5 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - -import bpython.keys as keys +from bpython import keys +from bpython.test import unittest class TestCLIKeys(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 2f83d7175..26eee9a07 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,10 +1,6 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - import re +from bpython.test import unittest from bpython.line import current_word, current_dict_key, current_dict, \ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index d82f96d38..6ef610638 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,14 +1,10 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython.curtsiesfrontend.manual_readline import \ left_arrow, right_arrow, beginning_of_line, forward_word, back_word, \ end_of_line, delete, last_word_pos, backspace, delete_from_cursor_back, \ delete_from_cursor_forward, delete_rest_of_word, delete_word_to_cursor, \ transpose_character_before_cursor, UnconfiguredEdits, \ delete_word_from_cursor_back +from bpython.test import unittest class TestManualReadline(unittest.TestCase): diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 526f91c72..f94366565 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -4,17 +4,13 @@ import inspect import re -try: - import unittest2 as unittest -except ImportError: - import unittest -skip = unittest.skip - from bpython.curtsiesfrontend.interpreter import code_finished_will_parse from bpython.curtsiesfrontend.preprocess import preprocess +from bpython.test import unittest +from bpython.test.fodder import original, processed -from bpython.test.fodder import original as original, processed +skip = unittest.skip preproc = partial(preprocess, compiler=compiler) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 7e0e7aaa8..2299cea42 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -8,17 +8,15 @@ from six.moves import range import sys -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase +from bpython.test import unittest + pypy = 'PyPy' in sys.version + def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, os.devnull) From dd0a489d96a68168012b7186861f063e7114f361 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:20:08 +0200 Subject: [PATCH 0021/1094] Clean up imports Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 4 +--- bpython/curtsiesfrontend/repl.py | 3 +-- bpython/test/test_repl.py | 8 +++----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index fea3fddcc..5f325a84c 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,3 @@ -import code -import traceback import sys from codeop import CommandCompiler from six import iteritems @@ -13,7 +11,7 @@ from bpython.curtsiesfrontend.parse import parse from bpython.repl import Interpreter as ReplInterpreter from bpython.config import getpreferredencoding -from bpython._py3compat import py3 + default_colors = { Generic.Error: 'R', diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f4da90230..b460b70f5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -3,7 +3,6 @@ import contextlib import errno -import functools import greenlet import logging import os @@ -15,7 +14,7 @@ import threading import time import unicodedata -from six.moves import range, builtins +from six.moves import range from pygments import format from bpython._py3compat import PythonLexer diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2299cea42..710d07c15 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,12 +1,12 @@ from itertools import islice +from six.moves import range import collections import inspect import os import shutil import socket -import tempfile -from six.moves import range import sys +import tempfile from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete @@ -65,7 +65,7 @@ def test_next(self): next(self.matches_iterator) self.assertEqual(next(self.matches_iterator), self.matches[0]) - self.assertEqual(next(self.matches_iterator), self. matches[1]) + self.assertEqual(next(self.matches_iterator), self.matches[1]) self.assertNotEqual(next(self.matches_iterator), self.matches[1]) def test_previous(self): @@ -249,8 +249,6 @@ def test_current_function_cpython(self): self.assert_get_source_error_for_current_function( collections.defaultdict, "could not find class definition") - - def test_current_line(self): self.repl.interp.locals['a'] = socket.socket self.set_input_line('a') From 26b8dafcc906da8c82a9092437d4795f64bb39b2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:23:02 +0200 Subject: [PATCH 0022/1094] PEP8 --- bpython/autocomplete.py | 1 - bpython/patch_linecache.py | 6 +++++- bpython/repl.py | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 7e89f4a66..f4a7a0d7d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -330,7 +330,6 @@ def list_attributes(self, obj): return dir(obj) - class DictKeyCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 9bd935b50..7dc321e7a 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,5 +1,6 @@ import linecache + class BPythonLinecache(dict): """Replaces the cache dict in the standard-library linecache module, to also remember (in an unerasable way) bpython console input.""" @@ -29,7 +30,7 @@ def remember_bpython_input(self, source): a fake filename to use to retrieve it later.""" filename = '' % len(self.bpython_history) self.bpython_history.append((len(source), None, - source.splitlines(True), filename)) + source.splitlines(True), filename)) return filename def __getitem__(self, key): @@ -50,6 +51,7 @@ def __delitem__(self, key): if not self.is_bpython_filename(key): return super(BPythonLinecache, self).__delitem__(key) + def _bpython_clear_linecache(): try: bpython_history = linecache.cache.bpython_history @@ -58,11 +60,13 @@ def _bpython_clear_linecache(): linecache.cache = BPythonLinecache() linecache.cache.bpython_history = bpython_history + # Monkey-patch the linecache module so that we're able # to hold our command history there and have it persist linecache.cache = BPythonLinecache(linecache.cache) linecache.clearcache = _bpython_clear_linecache + def filename_for_console_input(code_string): """Remembers a string of source code, and returns a fake filename to use to retrieve it later.""" diff --git a/bpython/repl.py b/bpython/repl.py index 8536d4fab..6a9c0ebfc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -766,7 +766,6 @@ def do_pastebin(self, s): self.prev_removal_url), 10) return self.prev_pastebin_url - self.interact.notify(_('Posting data to pastebin...')) try: paste_url, removal_url = self.paster.paste(s) From e952c73eb972d48d02af7e6274a48ab27ff80b9e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:32:36 +0200 Subject: [PATCH 0023/1094] PEP8 --- bpython/test/test_filewatch.py | 1 + bpython/test/test_repl.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index e9e08449e..061e18b2b 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -9,6 +9,7 @@ from bpython.test import mock, unittest + @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 710d07c15..57d83b147 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -162,7 +162,6 @@ def test_func_name_method_issue_479(self): self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) - def test_syntax_error_parens(self): for line in ["spam(]", "spam([)", "spam())"]: self.set_input_line(line) @@ -329,7 +328,8 @@ def test_fuzzy_global_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['UnboundLocalError(', '__doc__'] if not py3 else - ['ChildProcessError(', 'UnboundLocalError(', '__doc__']) + ['ChildProcessError(', 'UnboundLocalError(', + '__doc__']) # 2. Attribute tests def test_simple_attribute_complete(self): From bd2002a619f62d643f9328456cf45ec7a1011d14 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 May 2015 18:50:55 +0200 Subject: [PATCH 0024/1094] Add 0.14.2 changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0f229b3d6..c8c4bd714 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,17 @@ Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. +0.14.2 +------ + +Fixes: + +* #498: Fixed is_callable +* #509: Fixed fcntl usage. +* #523, #524: Fix conditional dependencies for SNI support again. +* Fix binary name of bpdb. + + 0.14.1 ------ From 66d7b5234fabf224d6f748cd9e3114b996934805 Mon Sep 17 00:00:00 2001 From: noella Date: Thu, 28 May 2015 22:16:45 -0400 Subject: [PATCH 0025/1094] Refactoring with named tuples --- bpython/autocomplete.py | 51 ++++++++++++++++++----------------------- bpython/line.py | 29 ++++++++++++----------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f4a7a0d7d..a172ca4c0 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,7 +2,6 @@ # The MIT License # -# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -39,6 +38,7 @@ from bpython import inspection from bpython import importcompletion from bpython import line as lineparts +from bpython.line import LinePart from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile @@ -126,8 +126,8 @@ def format(self, word): def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - start, end, word = self.locate(cursor_offset, line) - result = start + len(match), line[:start] + match + line[end:] + lpart = self.locate(cursor_offset, line) + result = lpart.start + len(match), line[:lpart.start] + match + line[lpart.end:] return result @property @@ -200,14 +200,13 @@ def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) if cs is None: return None - start, end, text = cs matches = set() - username = text.split(os.path.sep, 1)[0] + username = cs.word.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) - for filename in self.safe_glob(os.path.expanduser(text)): + for filename in self.safe_glob(os.path.expanduser(cs.word)): if os.path.isdir(filename): filename += os.path.sep - if text.startswith('~'): + if cs.word.startswith('~'): filename = username + filename[len(user_dir):] matches.add(filename) return matches @@ -235,25 +234,24 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - text = r[2] if locals_ is None: locals_ = __main__.__dict__ - assert '.' in text + assert '.' in r.word - for i in range(1, len(text) + 1): - if text[-i] == '[': + for i in range(1, len(r.word) + 1): + if r.word[-i] == '[': i -= 1 break - methodtext = text[-i:] - matches = set(''.join([text[:-i], m]) + methodtext = r.word[-i:] + matches = set(''.join([r.word[:-i], m]) for m in self.attr_matches(methodtext, locals_)) # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes # starting with a _ - if not text.split('.')[-1].startswith('_'): + if not r.word.split('.')[-1].startswith('_'): matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) return matches @@ -340,7 +338,6 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, orig = r _, _, dexpr = lineparts.current_dict(cursor_offset, line) try: obj = safe_eval(dexpr, locals_) @@ -348,7 +345,7 @@ def matches(self, cursor_offset, line, **kwargs): return set() if isinstance(obj, dict) and obj.keys(): return set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(orig)) + if repr(k).startswith(r.word)) else: return set() @@ -371,8 +368,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if 'class' not in current_block: return None - start, end, word = r - return set(name for name in MAGIC_METHODS if name.startswith(word)) + return set(name for name in MAGIC_METHODS if name.startswith(r.word)) def locate(self, current_offset, line): return lineparts.current_method_definition_name(current_offset, line) @@ -392,12 +388,11 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, text = r matches = set() - n = len(text) + n = len(r.word) for word in KEYWORDS: - if self.method_match(word, n, text): + if self.method_match(word, n, r.word): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): @@ -405,7 +400,7 @@ def matches(self, cursor_offset, line, **kwargs): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if self.method_match(word, n, text) and word != "__builtins__": + if self.method_match(word, n, r.word) and word != "__builtins__": matches.add(_callable_postfix(val, word)) return matches @@ -425,14 +420,13 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, word = r if argspec: matches = set(name + '=' for name in argspec[1][0] if isinstance(name, string_types) and - name.startswith(word)) + name.startswith(r.word)) if py3: matches.update(name + '=' for name in argspec[1][4] - if name.startswith(word)) + if name.startswith(r.word)) return matches def locate(self, current_offset, line): @@ -446,14 +440,13 @@ def matches(self, cursor_offset, line, **kwargs): if r is None: return None - start, end, word = r attrs = dir('') if not py3: # decode attributes attrs = (att.decode('ascii') for att in attrs) - matches = set(att for att in attrs if att.startswith(word)) - if not word.startswith('_'): + matches = set(att for att in attrs if att.startswith(r.word)) + if not r.word.startswith('_'): return set(match for match in matches if not match.startswith('_')) return matches @@ -513,7 +506,7 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, cursor_offset, line): start = self._orig_start end = cursor_offset - return start, end, line[start:end] + return LinePart(start, end, line[start:end]) class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): diff --git a/bpython/line.py b/bpython/line.py index 12f753142..c1da3943b 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,10 +5,11 @@ word.""" from itertools import chain +from collections import namedtuple from bpython.lazyre import LazyReCompile - current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') +LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) def current_word(cursor_offset, line): @@ -25,7 +26,7 @@ def current_word(cursor_offset, line): word = m.group() if word is None: return None - return (start, end, word) + return LinePart(start, end, word) current_dict_key_re = LazyReCompile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') @@ -36,7 +37,7 @@ def current_dict_key(cursor_offset, line): matches = current_dict_key_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: - return (m.start(1), m.end(1), m.group(1)) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -48,7 +49,7 @@ def current_dict(cursor_offset, line): matches = current_dict_re.finditer(line) for m in matches: if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: - return (m.start(1), m.end(1), m.group(1)) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -66,7 +67,7 @@ def current_string(cursor_offset, line): for m in current_string_re.finditer(line): i = 3 if m.group(3) else 4 if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset: - return m.start(i), m.end(i), m.group(i) + return LinePart(m.start(i), m.end(i), m.group(i)) return None @@ -89,7 +90,7 @@ def current_object(cursor_offset, line): s += m.group(1) if not s: return None - return start, start+len(s), s + return LinePart(start, start+len(s), s) current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') @@ -106,7 +107,7 @@ def current_object_attribute(cursor_offset, line): for m in matches: if (m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset): - return m.start(1) + start, m.end(1) + start, m.group(1) + return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) return None @@ -128,7 +129,7 @@ def current_from_import_from(cursor_offset, line): for m in matches: if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -153,7 +154,7 @@ def current_from_import_import(cursor_offset, line): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: - return start, end, m.group(1) + return LinePart(start, end, m.group(1)) return None @@ -175,7 +176,7 @@ def current_import(cursor_offset, line): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: - return start, end, m.group(1) + return LinePart(start, end, m.group(1)) current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") @@ -186,7 +187,7 @@ def current_method_definition_name(cursor_offset, line): matches = current_method_definition_name_re.finditer(line) for m in matches: if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -198,7 +199,7 @@ def current_single_word(cursor_offset, line): matches = current_single_word_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -209,7 +210,7 @@ def current_dotted_attribute(cursor_offset, line): return None start, end, word = match if '.' in word[1:]: - return start, end, word + return LinePart(start, end, word) current_string_literal_attr_re = LazyReCompile( @@ -223,5 +224,5 @@ def current_string_literal_attr(cursor_offset, line): matches = current_string_literal_attr_re.finditer(line) for m in matches: if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: - return m.start(4), m.end(4), m.group(4) + return LinePart(m.start(4), m.end(4), m.group(4)) return None From ab3ed098216f923436f9f54abed0f0f5cd0ae889 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:03:35 -0400 Subject: [PATCH 0026/1094] argspec replaced with named tuples for better readability --- bpython/inspection.py | 48 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index e0cc140df..2e3958e87 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -28,6 +28,7 @@ import io import keyword import pydoc +from collections import namedtuple from six.moves import range from pygments.token import Token @@ -40,6 +41,11 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') +Argspec = namedtuple('Argspec', ['args', 'varargs', 'varkwargs', 'defaults', + 'kwonly', 'kwonly_defaults', 'annotations']) + +FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) + class AttrCleaner(object): """A context manager that tries to make an object not exhibit side-effects @@ -175,44 +181,39 @@ def fixlongargs(f, argspec): def getpydocspec(f, func): try: - argspec = pydoc.getdoc(f) + docstring = pydoc.getdoc(f) except NameError: return None - - s = getpydocspec_re.search(argspec) + s = getpydocspec_re.search(docstring) if s is None: return None if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - args = list() - defaults = list() - varargs = varkwargs = None - kwonly_args = list() - kwonly_defaults = dict() + argspec = Argspec(list(), None, None, list(), list(), dict(), None) + for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): - varkwargs = arg[2:] + argspec.varkwargs = arg[2:] elif arg.startswith('*'): - varargs = arg[1:] + argspec.varargs = arg[1:] else: arg, _, default = arg.partition('=') - if varargs is not None: - kwonly_args.append(arg) + if argspec.varargs is not None: + argspec.kwonly_args.append(arg) if default: - kwonly_defaults[arg] = default + argspec.kwonly_defaults[arg] = default else: - args.append(arg) + argspec.args.append(arg) if default: - defaults.append(default) + argspec.defaults.append(default) - return [func, (args, varargs, varkwargs, defaults, - kwonly_args, kwonly_defaults)] + return argspec -def getargspec(func, f): +def getfuncprops(func, f): # Check if it's a real bound method or if it's implicitly calling __init__ # (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would # not take 'self', the latter would: @@ -238,16 +239,19 @@ def getargspec(func, f): argspec = list(argspec) fixlongargs(f, argspec) - argspec = [func, argspec, is_bound_method] + if len(argspec) == 4: + argspec = argspec + [list(),dict(),None] + argspec = Argspec(*argspec) + fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): with AttrCleaner(f): argspec = getpydocspec(f, func) if argspec is None: return None if inspect.ismethoddescriptor(f): - argspec[1][0].insert(0, 'obj') - argspec.append(is_bound_method) - return argspec + argspec.args.insert(0, 'obj') + fprops = FuncProps(func, argspec, is_bound_method) + return fprops def is_eval_safe_name(string): From 86a9ccefcbe3cdd0a621c504992b907fe05266e1 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:05:25 -0400 Subject: [PATCH 0027/1094] argspec replaced with namedtuple FuncProps, in_arg removed from original argspec and made into class var --- bpython/repl.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 6a9c0ebfc..40a5a4d15 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -359,7 +359,8 @@ def __init__(self, interp, config): self.history = [] self.evaluating = False self.matches_iter = MatchesIterator() - self.argspec = None + self.funcprops = None + self.arg_pos = None self.current_func = None self.highlighted_paren = None self._C = {} @@ -468,8 +469,8 @@ def get_object(self, name): def get_args(self): """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.argspec and return True, - otherwise set self.argspec to None and return False""" + argspec() for it. On success, update self.funcprops,self.arg_pos and return True, + otherwise set self.funcprops to None and return False""" self.current_func = None @@ -532,11 +533,11 @@ def get_args(self): except AttributeError: return None self.current_func = f - - self.argspec = inspection.getargspec(func, f) - if self.argspec: - self.argspec.append(arg_number) + self.funcprops = inspection.getfuncprops(func, f) + if self.funcprops: + self.arg_pos = arg_number return True + self.arg_pos = None return False def get_source_of_current_name(self): @@ -567,7 +568,7 @@ def get_source_of_current_name(self): def set_docstring(self): self.docstring = None if not self.get_args(): - self.argspec = None + self.funcprops = None elif self.current_func is not None: try: self.docstring = pydoc.getdoc(self.current_func) @@ -609,7 +610,7 @@ def complete(self, tab=False): cursor_offset=self.cursor_offset, line=self.current_line, locals_=self.interp.locals, - argspec=self.argspec, + argspec=self.funcprops, current_block='\n'.join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, history=self.history) @@ -618,7 +619,7 @@ def complete(self, tab=False): if len(matches) == 0: self.matches_iter.clear() - return bool(self.argspec) + return bool(self.funcprops) self.matches_iter.update(self.cursor_offset, self.current_line, matches, completer) From 0ace2c47e4cf6cdaf185e07cc00b190296ecf2f0 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:07:28 -0400 Subject: [PATCH 0028/1094] Replaced argspec list with namedtuple FuncProps for better readability --- bpython/cli.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 24a2e97bb..6b26d1343 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -699,16 +699,17 @@ def mkargspec(self, topline, down): sturdy.""" r = 3 - fn = topline[0] - args = topline[1][0] - kwargs = topline[1][3] - _args = topline[1][1] - _kwargs = topline[1][2] - is_bound_method = topline[2] - in_arg = topline[3] + fn = topline.func + args = topline.arginfo.args + kwargs = topline.arginfo.defaults + _args = topline.arginfo.varargs + _kwargs = topline.arginfo.varkwargs + is_bound_method = topline.is_bound_method + in_arg = topline.in_arg + print "\n\nprinting topline",topline if py3: - kwonly = topline[1][4] - kwonly_defaults = topline[1][5] or dict() + kwonly = topline.arginfo.kwonly + kwonly_defaults = topline.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) @@ -1454,7 +1455,7 @@ def tab(self, back=False): current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: - self.show_list(self.matches_iter.matches, topline=self.argspec, + self.show_list(self.matches_iter.matches, topline=self.funcprops, formatter=self.matches_iter.completer.format, current_item=current_match) except curses.error: From bd7e8e4848eff46a10054daefecc7a401a0ce5c4 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:11:14 -0400 Subject: [PATCH 0029/1094] Replacing argspec with namedtuples FuncProps, changed func signature to include arg_pos --- bpython/curtsiesfrontend/replpainter.py | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 2dcbeaef0..3e10816d2 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -72,19 +72,17 @@ def matches_lines(rows, columns, matches, current, config, format): return matches_lines -def formatted_argspec(argspec, columns, config): +def formatted_argspec(funcprops, arg_pos, columns, config): # Pretty directly taken from bpython.cli - is_bound_method = argspec[2] - func = argspec[0] - args = argspec[1][0] - kwargs = argspec[1][3] - _args = argspec[1][1] # *args - _kwargs = argspec[1][2] # **kwargs - is_bound_method = argspec[2] - in_arg = argspec[3] + func = funcprops.func + args = funcprops.argspec.args + kwargs = funcprops.argspec.defaults + _args = funcprops.argspec.varargs + _kwargs = funcprops.argspec.varkwargs + is_bound_method = funcprops.is_bound_method if py3: - kwonly = argspec[1][4] - kwonly_defaults = argspec[1][5] or dict() + kwonly = funcprops.argspec.kwonly + kwonly_defaults = funcprops.argspec.kwonly_defaults or dict() arg_color = func_for_letter(config.color_scheme['name']) func_color = func_for_letter(config.color_scheme['name'].swapcase()) @@ -95,16 +93,16 @@ def formatted_argspec(argspec, columns, config): s = func_color(func) + arg_color(': (') - if is_bound_method and isinstance(in_arg, int): + if is_bound_method and isinstance(arg_pos, int): # TODO what values could this have? - in_arg += 1 + arg_pos += 1 for i, arg in enumerate(args): kw = None if kwargs and i >= len(args) - len(kwargs): kw = str(kwargs[i - (len(args) - len(kwargs))]) - color = token_color if in_arg in (i, arg) else arg_color - if i == in_arg or arg == in_arg: + color = token_color if arg_pos in (i, arg) else arg_color + if i == arg_pos or arg == arg_pos: color = bolds[color] if not py3: @@ -135,7 +133,7 @@ def formatted_argspec(argspec, columns, config): for arg in kwonly: s += punctuation_color(', ') color = token_color - if in_arg: + if arg_pos: color = bolds[color] s += color(arg) default = kwonly_defaults.get(arg, marker) @@ -159,13 +157,13 @@ def formatted_docstring(docstring, columns, config): for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, argspec, match, docstring, config, +def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, format): - """Returns painted completions, argspec, match, docstring etc.""" + """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(argspec, width, config) if argspec else []) + + lines = ((formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) + (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) From d8fdc2fad4209e0220903860f232cd4898e1911e Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:16:03 -0400 Subject: [PATCH 0030/1094] Replaced argspec with namedtuple FuncProps --- bpython/curtsiesfrontend/repl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b460b70f5..d66d3fe06 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -909,7 +909,7 @@ def add_to_incremental_search(self, char=None, backspace=False): def update_completion(self, tab=False): """Update visible docstring and matches, and possibly hide/show completion box""" - # Update autocomplete info; self.matches_iter and self.argspec + # Update autocomplete info; self.matches_iter and self.funcprops # Should be called whenever the completion box might need to appear / dissapear # when current line or cursor offset changes, unless via selecting a match self.current_match = None @@ -1272,7 +1272,8 @@ def move_screen_up(current_line_start_row): infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), self.matches_iter.matches, - self.argspec, + self.funcprops, + self.arg_pos, self.current_match, self.docstring, self.config, From 4d6861cd005d85ad6725dccd0acf8ad82c0cbb9c Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:23:09 -0400 Subject: [PATCH 0031/1094] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_curtsies_painting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 157972108..005431128 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -109,8 +109,8 @@ def test_argspec(self): def foo(x, y, z=10): "docstring!" pass - argspec = inspection.getargspec('foo', foo) + [1] - array = replpainter.formatted_argspec(argspec, 30, setup_config()) + argspec = inspection.getfuncprops('foo', foo) + array = replpainter.formatted_argspec(argspec, 1, 30, setup_config()) screen = [bold(cyan('foo')) + cyan(':') + cyan(' ') + cyan('(') + cyan('x') + yellow(',') + yellow(' ') + bold(cyan('y')) + yellow(',') + yellow(' ') + cyan('z') + yellow('=') + From 3057bbed1c95aefad2d9cc683680f9744cad14ad Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:24:13 -0400 Subject: [PATCH 0032/1094] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_inspection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index b91f0ffbb..cf5a1aad5 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -62,22 +62,22 @@ def fails(spam=['-a', '-b']): self.assertEqual(str(['-a', '-b']), default_arg_repr, 'This test is broken (repr does not match), fix me.') - argspec = inspection.getargspec('fails', fails) - defaults = argspec[1][3] + argspec = inspection.getfuncprops('fails', fails) + defaults = argspec.argspec.defaults self.assertEqual(str(defaults[0]), default_arg_repr) def test_pasekeywordpairs_string(self): def spam(eggs="foo, bar"): pass - defaults = inspection.getargspec("spam", spam)[1][3] + defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "'foo, bar'") def test_parsekeywordpairs_multiple_keywords(self): def spam(eggs=23, foobar="yay"): pass - defaults = inspection.getargspec("spam", spam)[1][3] + defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") From 83abeb60f0406a8b0674fa1f12f155e07b502fe5 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:24:43 -0400 Subject: [PATCH 0033/1094] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_repl.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 57d83b147..8f49d17c0 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -171,22 +171,22 @@ def test_syntax_error_parens(self): def test_kw_arg_position(self): self.set_input_line("spam(a=0") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "a") + self.assertEqual(self.repl.arg_pos, "a") self.set_input_line("spam(1, b=1") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "b") + self.assertEqual(self.repl.arg_pos, "b") self.set_input_line("spam(1, c=2") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "c") + self.assertEqual(self.repl.arg_pos, "c") def test_lambda_position(self): self.set_input_line("spam(lambda a, b: 1, ") self.assertTrue(self.repl.get_args()) - self.assertTrue(self.repl.argspec) + self.assertTrue(self.repl.funcprops) # Argument position - self.assertEqual(self.repl.argspec[3], 1) + self.assertEqual(self.repl.arg_pos, 1) def test_issue127(self): self.set_input_line("x=range(") @@ -428,7 +428,7 @@ def test_simple_tab_complete(self): self.repl.print_line = mock.Mock() self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() - self.repl.argspec = mock.Mock() + self.repl.funcprops = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -471,7 +471,7 @@ def test_back_parameter(self): self.repl.matches_iter.previous.return_value = "previtem" self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() - self.repl.argspec = mock.Mock() + self.repl.funcprops = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") self.repl.print_line = mock.Mock() self.repl.s = "foo" From 6586815f155bf283963af9aa154de2b43991c603 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:55:31 -0400 Subject: [PATCH 0034/1094] Argspec - Named tuple FuncProps related refactoring --- bpython/cli.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6b26d1343..90462f990 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -461,7 +461,9 @@ def complete(self, tab=False): list_win_visible = repl.Repl.complete(self, tab) if list_win_visible: try: - self.show_list(self.matches_iter.matches, topline=self.argspec, formatter=self.matches_iter.completer.format) + self.show_list(self.matches_iter.matches, self.arg_pos, + topline=self.funcprops, + formatter=self.matches_iter.completer.format) except curses.error: # XXX: This is a massive hack, it will go away when I get # cusswords into a good enough state that we can start @@ -691,7 +693,7 @@ def lf(self): self.print_line(self.s, newline=True) self.echo("\n") - def mkargspec(self, topline, down): + def mkargspec(self, topline, in_arg, down): """This figures out what to do with the argspec and puts it nicely into the list window. It returns the number of lines used to display the argspec. It's also kind of messy due to it having to call so many @@ -700,15 +702,13 @@ def mkargspec(self, topline, down): r = 3 fn = topline.func - args = topline.arginfo.args - kwargs = topline.arginfo.defaults - _args = topline.arginfo.varargs - _kwargs = topline.arginfo.varkwargs + args = topline.argspec.args + kwargs = topline.argspec.defaults + _args = topline.argspec.varargs + _kwargs = topline.argspec.varkwargs is_bound_method = topline.is_bound_method - in_arg = topline.in_arg - print "\n\nprinting topline",topline if py3: - kwonly = topline.arginfo.kwonly + kwonly = topline.argspec.kwonly kwonly_defaults = topline.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() @@ -1254,7 +1254,7 @@ def write(self, s): self.s_hist.append(s.rstrip()) - def show_list(self, items, topline=None, formatter=None, current_item=None): + def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None): shared = Struct() shared.cols = 0 @@ -1276,7 +1276,7 @@ def show_list(self, items, topline=None, formatter=None, current_item=None): current_item = formatter(current_item) if topline: - height_offset = self.mkargspec(topline, down) + 1 + height_offset = self.mkargspec(topline, arg_pos, down) + 1 else: height_offset = 0 @@ -1455,7 +1455,8 @@ def tab(self, back=False): current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: - self.show_list(self.matches_iter.matches, topline=self.funcprops, + self.show_list(self.matches_iter.matches, self.arg_pos, + topline=self.funcprops, formatter=self.matches_iter.completer.format, current_item=current_match) except curses.error: From de6e387246fb22a272eb9e2711f6146e40fbd785 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:57:31 -0400 Subject: [PATCH 0035/1094] Refactored argspec with replacement named tuple FuncProps --- bpython/urwid.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index f10ceb60b..d2c392d72 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -745,13 +745,14 @@ def _populate_completion(self): widget_list.pop() # This is just me flailing around wildly. TODO: actually write. if self.complete(): - if self.argspec: + if self.funcprops: # This is mostly just stolen from the cli module. - func_name, args, is_bound, in_arg = self.argspec + func_name, args, is_bound = self.funcprops + in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] if py3: - kwonly = self.argspec[1][4] - kwonly_defaults = self.argspec[1][5] or {} + kwonly = self.funcprops.argspec.kwonly + kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} else: kwonly, kwonly_defaults = [], {} markup = [('bold name', func_name), From e72586a15df2a90ffc297b13f675c742e85d98e2 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:58:11 -0400 Subject: [PATCH 0036/1094] Refactored test file for argspec to FuncProp changes --- bpython/test/test_repl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 8f49d17c0..2bb13f4b8 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -429,6 +429,7 @@ def test_simple_tab_complete(self): self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() self.repl.funcprops = mock.Mock() + self.repl.arg_pos = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -472,6 +473,7 @@ def test_back_parameter(self): self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() self.repl.funcprops = mock.Mock() + self.repl.arg_pos = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") self.repl.print_line = mock.Mock() self.repl.s = "foo" From 27b788cb4e54fdaa703d485ab7dcda565224240a Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:27:19 -0400 Subject: [PATCH 0037/1094] Added accidentally deleted Copyright --- bpython/autocomplete.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a172ca4c0..0fd64a5e6 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,6 +2,7 @@ # The MIT License # +# Copyright (c) 2009-2011 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 85a2a5d02373e43f3ca958ba5b330d1109a0b689 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:31:09 -0400 Subject: [PATCH 0038/1094] Changed case for Argspec named tuple definition to ArgSpec --- bpython/inspection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 2e3958e87..258f18003 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -41,7 +41,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') -Argspec = namedtuple('Argspec', ['args', 'varargs', 'varkwargs', 'defaults', +ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -191,7 +191,7 @@ def getpydocspec(f, func): if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - argspec = Argspec(list(), None, None, list(), list(), dict(), None) + argspec = ArgSpec(list(), None, None, list(), list(), dict(), None) for arg in s.group(2).split(','): arg = arg.strip() @@ -241,7 +241,7 @@ def getfuncprops(func, f): fixlongargs(f, argspec) if len(argspec) == 4: argspec = argspec + [list(),dict(),None] - argspec = Argspec(*argspec) + argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): with AttrCleaner(f): From 2141cf78bcd58ef4978935494a1fc904e7a57380 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:35:45 -0400 Subject: [PATCH 0039/1094] Updated copyright year --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 0fd64a5e6..fcf011434 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,7 +2,7 @@ # The MIT License # -# Copyright (c) 2009-2011 the bpython authors. +# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 6dfbc4de87fd1f7c9d10f4457515f0b07b42d79b Mon Sep 17 00:00:00 2001 From: noella Date: Tue, 9 Jun 2015 13:39:49 -0400 Subject: [PATCH 0040/1094] Fixed a named tuple being modifying causing exceptions --- bpython/inspection.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 258f18003..7a700a700 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -181,36 +181,41 @@ def fixlongargs(f, argspec): def getpydocspec(f, func): try: - docstring = pydoc.getdoc(f) + argspec = pydoc.getdoc(f) except NameError: return None - s = getpydocspec_re.search(docstring) + + s = getpydocspec_re.search(argspec) if s is None: return None if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - argspec = ArgSpec(list(), None, None, list(), list(), dict(), None) - + args = list() + defaults = list() + varargs = varkwargs = None + kwonly_args = list() + kwonly_defaults = dict() for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): - argspec.varkwargs = arg[2:] + varkwargs = arg[2:] elif arg.startswith('*'): - argspec.varargs = arg[1:] + varargs = arg[1:] else: arg, _, default = arg.partition('=') - if argspec.varargs is not None: - argspec.kwonly_args.append(arg) + if varargs is not None: + kwonly_args.append(arg) if default: - argspec.kwonly_defaults[arg] = default + kwonly_defaults[arg] = default else: - argspec.args.append(arg) + args.append(arg) if default: - argspec.defaults.append(default) + defaults.append(default) - return argspec + return ArgSpec(args, varargs, varkwargs, default, kwonly_args, + kwonly_defaults, None) def getfuncprops(func, f): From d172921c95fd8dbed297ba2f9e3a12a4d32f7a8d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Jun 2015 22:43:01 +0200 Subject: [PATCH 0041/1094] Make pep8 happy Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/manual_readline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 11a4efa02..02c7cbf03 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -295,6 +295,7 @@ def transpose_word_before_cursor(cursor_offset, line): # bonus functions (not part of readline) + @edit_keys.on('') def uppercase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented From a7d729e3cbce651f5a8b8be38adf8ae6e9f63c2f Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:56:58 -0400 Subject: [PATCH 0042/1094] PEP8 --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 258f18003..511ef6895 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -42,7 +42,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', - 'kwonly', 'kwonly_defaults', 'annotations']) + 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -240,7 +240,7 @@ def getfuncprops(func, f): argspec = list(argspec) fixlongargs(f, argspec) if len(argspec) == 4: - argspec = argspec + [list(),dict(),None] + argspec = argspec + [list(), dict(), None] argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): From 1424a229d882620748bc1c236b1d5109cdc905ef Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:58:08 -0400 Subject: [PATCH 0043/1094] PEP8 --- bpython/autocomplete.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fcf011434..b2fbc3880 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -127,9 +127,10 @@ def format(self, word): def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - lpart = self.locate(cursor_offset, line) - result = lpart.start + len(match), line[:lpart.start] + match + line[lpart.end:] - return result + lpart = self.locate(cursor_offset, line) + offset = lpart.start + len(match) + changed_line = line[:lpart.start] + match + line[lpart.end:] + return offset, changed_line @property def shown_before_tab(self): @@ -401,7 +402,8 @@ def matches(self, cursor_offset, line, **kwargs): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if self.method_match(word, n, r.word) and word != "__builtins__": + if (self.method_match(word, n, r.word) and + word != "__builtins__"): matches.add(_callable_postfix(val, word)) return matches From b89969642ccc876c23506f807d005792c4dd80dc Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:59:37 -0400 Subject: [PATCH 0044/1094] PEP8 --- bpython/curtsiesfrontend/replpainter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 3e10816d2..94e813ecf 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -157,13 +157,14 @@ def formatted_docstring(docstring, columns, config): for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, - format): +def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, + config, format): """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) + + lines = ((formatted_argspec(funcprops, arg_pos, width, config) + if funcprops else []) + (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) From 191e43486bd6f27743c45716cb81ff11227d3e1a Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 01:00:18 -0400 Subject: [PATCH 0045/1094] PEP8 --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 40a5a4d15..433d2732a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -469,8 +469,8 @@ def get_object(self, name): def get_args(self): """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.funcprops,self.arg_pos and return True, - otherwise set self.funcprops to None and return False""" + argspec() for it. On success, update self.funcprops,self.arg_pos and + return True, otherwise set self.funcprops to None and return False""" self.current_func = None From 72be2cd09c45f19a6b866d6d838d2805f8ba9cf6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Jun 2015 14:56:43 +0200 Subject: [PATCH 0046/1094] Failing test for #544 --- bpython/test/test_autocomplete.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bfe1b0b66..8ec494a09 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -20,7 +20,8 @@ from bpython._py3compat import py3 from bpython.test import mock -if sys.version_info[:2] >= (3, 4): +is_py34 = sys.version_info[:2] >= (3, 4) +if is_py34: glob_function = 'glob.iglob' else: glob_function = 'glob.glob' @@ -308,6 +309,13 @@ def test_completions_starting_with_different_cases(self): [Comp('Abc', 'bc'), Comp('ade', 'de')]) self.assertSetEqual(matches, set(['ade'])) + @unittest.skipUnless(is_py34, 'asyncio required') + def test_issue_544(self): + com = autocomplete.MultilineJediCompletion() + code = '@asyncio.coroutine\ndef' + history = ('import asyncio', '@asyncio.coroutin') + com.matches(3, 'def', current_block=code, history=history) + class TestGlobalCompletion(unittest.TestCase): From 04ca930a703ab70fae18ba08c810a84ba1fb5cab Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Jun 2015 14:57:34 +0200 Subject: [PATCH 0047/1094] Work around jedi bugs (fixes #544) This might break jedi based completion. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b2fbc3880..754b3a394 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -479,11 +479,9 @@ def matches(self, cursor_offset, line, **kwargs): script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') completions = script.completions() - except jedi.NotFoundError: - self._orig_start = None - return None - except IndexError: - # for #483 + except (jedi.NotFoundError, IndexError, KeyError): + # IndexError for #483 + # KeyError for #544 self._orig_start = None return None From e1b0dcd94f005a700431dd217ad617d7b6208692 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Jun 2015 22:00:44 +0200 Subject: [PATCH 0048/1094] Do not print version and help with -q (#fixes #527) Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 88dc55009..693fde8a3 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -75,7 +75,8 @@ def main(args=None, locals_=None, banner=None): # expected for interactive sessions (vanilla python does it) sys.path.insert(0, '') - print(bpargs.version_banner()) + if not options.quiet: + print(bpargs.version_banner()) try: exit_value = mainloop(config, locals_, banner, interp, paste, interactive=(not exec_args)) From ff4ad26ca93cbde3472c29a73b7c2b56b56a11f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Jun 2015 22:08:29 +0200 Subject: [PATCH 0049/1094] Allow multi-line banners (fixes #538) Also distinguish between banners and welcome message. Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 693fde8a3..91843120e 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -28,7 +28,11 @@ # WARNING Will be a problem if more than one repl is ever instantiated this way -def main(args=None, locals_=None, banner=None): +def main(args=None, locals_=None, banner=None, welcome_message=None): + """ + banner is displayed directly after the version information. + welcome_message is passed on to Repl and displayed in the statusbar. + """ translations.init() config, options, exec_args = bpargs.parse(args, ( @@ -77,8 +81,10 @@ def main(args=None, locals_=None, banner=None): if not options.quiet: print(bpargs.version_banner()) + if banner is not None: + print(banner) try: - exit_value = mainloop(config, locals_, banner, interp, paste, + exit_value = mainloop(config, locals_, welcome_message, interp, paste, interactive=(not exec_args)) except (SystemExitFromCodeGreenlet, SystemExit) as e: exit_value = e.args From b71f41138732d862a72342b34407092a3e4eb162 Mon Sep 17 00:00:00 2001 From: sharow Date: Mon, 6 Jul 2015 06:37:28 +0900 Subject: [PATCH 0050/1094] fix: Reload and Auto-reloading doesn't work when using Python3.x --- bpython/curtsiesfrontend/repl.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d66d3fe06..a5f5dfa03 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -450,7 +450,7 @@ def smarter_request_reload(files_modified=()): self.incremental_search_target = '' - self.original_modules = sys.modules.keys() + self.original_modules = set(sys.modules.keys()) self.width = None self.height = None @@ -846,9 +846,8 @@ def clear_modules_and_reevaluate(self): if self.watcher: self.watcher.reset() cursor, line = self.cursor_offset, self.current_line - for modname in sys.modules.keys(): - if modname not in self.original_modules: - del sys.modules[modname] + for modname in (set(sys.modules.keys()) - self.original_modules): + del sys.modules[modname] self.reevaluate(insert_into_history=True) self.cursor_offset, self.current_line = cursor, line self.status_bar.message(_('Reloaded at %s by user.') % From 5eed6afc19c4a57026ef2d230df4573d0638bf8e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 7 Jul 2015 20:01:40 +0200 Subject: [PATCH 0051/1094] Fix cli after named-tuple refactoring Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 90462f990..b04a0636e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -709,7 +709,7 @@ def mkargspec(self, topline, in_arg, down): is_bound_method = topline.is_bound_method if py3: kwonly = topline.argspec.kwonly - kwonly_defaults = topline.kwonly_defaults or dict() + kwonly_defaults = topline.argspec.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) From 2c0958fb689662081d24bcdc2e951f51f7174f23 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 7 Jul 2015 20:03:51 +0200 Subject: [PATCH 0052/1094] Fix Python 3 compat (fixes #550) Signed-off-by: Sebastian Ramacher --- bpython/pager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/pager.py b/bpython/pager.py index 20b1af743..70448672f 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,6 +29,8 @@ import sys import shlex +from bpython._py3compat import py3 + def get_pager_command(default='less -rf'): command = shlex.split(os.environ.get('PAGER', default)) @@ -51,7 +53,7 @@ def page(data, use_internal=False): curses.endwin() try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) - if isinstance(data, unicode): + if py3 or isinstance(data, unicode): data = data.encode(sys.__stdout__.encoding, 'replace') popen.stdin.write(data) popen.stdin.close() From dc0f2945b2176d4741a8b8688105972809c4c7e7 Mon Sep 17 00:00:00 2001 From: sharow Date: Sat, 11 Jul 2015 17:35:32 +0900 Subject: [PATCH 0053/1094] add test --- bpython/test/test_curtsies_repl.py | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 5f85825e0..1e405bffc 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -6,6 +6,7 @@ import sys import tempfile import io +from functools import partial from contextlib import contextmanager from six.moves import StringIO @@ -284,6 +285,68 @@ def test_variable_is_cleared(self): self.assertNotIn('b', self.repl.interp.locals) +class TestCurtsiesReevaluateWithImport(TestCase): + def setUp(self): + self.repl = create_repl() + self.open = partial(io.open, mode='wt', encoding='utf-8') + self.dont_write_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + + def tearDown(self): + sys.dont_write_bytecode = self.dont_write_bytecode + + def push(self, line): + self.repl._current_line = line + self.repl.on_enter() + + def head(self, path): + self.push('import sys') + self.push('sys.path.append("%s")' % (path)) + + @staticmethod + @contextmanager + def tempfile(): + with tempfile.NamedTemporaryFile(suffix='.py') as temp: + path, name = os.path.split(temp.name) + yield temp.name, path, name.replace('.py', '') + + def test_module_content_changed(self): + with self.tempfile() as (fullpath, path, modname): + with self.open(fullpath) as f: + f.write('a = 0\n') + self.head(path) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) + with self.open(fullpath) as f: + f.write('a = 1\n') + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) + + def test_import_module_with_rewind(self): + with self.tempfile() as (fullpath, path, modname): + with self.open(fullpath) as f: + f.write('a = 0\n') + self.head(path) + self.push('import %s' % (modname)) + self.assertIn(modname, self.repl.interp.locals) + self.repl.undo() + self.assertNotIn(modname, self.repl.interp.locals) + self.repl.clear_modules_and_reevaluate() + self.assertNotIn(modname, self.repl.interp.locals) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) + with self.open(fullpath) as f: + f.write('a = 1\n') + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) + + class TestCurtsiesPagerText(TestCase): def setUp(self): From 51ffb81c2c7b49dfaf1e126cfb5836d4e5042b44 Mon Sep 17 00:00:00 2001 From: Pete Anderson Date: Tue, 14 Jul 2015 04:43:37 -0400 Subject: [PATCH 0054/1094] Keep autocomplete errors from crashing bpython Perhaps a popup of some sort informing the user that an error has occurred would be better than just swallowing the error as I've done here, but I feel like a misbehaving completer should crash the application. The completer that prompted this for me is FilenameCompletion. I've got a test file in my directory created with `touch $'with\xFFhigh ascii'. If I type an open quote and a w in bpython, it crashes. It's because From python, if I do: >>> import glob >>> glob.glob(u'w*') # this is what FileCompletion will end up calling [u'without high ascii', u'with\uf0ffhigh ascii'] >>> But if I do it from bpython: >>> import glob >>> glob.glob(u'w*'0 [u'without high ascii', 'with\xffhigh ascii'] >>> For some reason, glob is returning one unicode and one str. Then when get_completer calls sorted(matches), sorted throws up when it tries to decode the str from ASCII. I don't know why glob is behaving this way or what the fix is, but I do know that it's not worth crashing bpython whenever I type 'w --- bpython/autocomplete.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 754b3a394..5ee4f2fa8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -544,10 +544,14 @@ def get_completer(completers, cursor_offset, line, **kwargs): double underscore methods like __len__ in method signatures """ - for completer in completers: - matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return sorted(matches), (completer if matches else None) + try: + for completer in completers: + matches = completer.matches(cursor_offset, line, **kwargs) + if matches is not None: + return sorted(matches), (completer if matches else None) + except: + pass + return [], None From d9aec6769076d521cb186d71f513338415c8d398 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 2 Aug 2015 21:12:46 +0200 Subject: [PATCH 0055/1094] Fix attrib decorator (fixes #553) Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 6 ++++-- bpython/test/test_crashers.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 62b49aab5..ef53cfda9 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -14,8 +14,10 @@ try: from nose.plugins.attrib import attr except ImportError: - def attr(func, *args, **kwargs): - return func + def attr(*args, **kwargs): + def identity(func): + return func + return identity @attr(speed='slow') diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 5db661bbf..1aea547e7 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -27,8 +27,10 @@ class TrialTestCase(object): try: from nose.plugins.attrib import attr except ImportError: - def attr(func, *args, **kwargs): - return func + def attr(*args, **kwargs): + def identity(func): + return func + return identity TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") From a17999d57f5b8e9872dde39894c6764a839bd275 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 Aug 2015 21:03:07 +0200 Subject: [PATCH 0056/1094] Fix a typo Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 14aec69f2..0dfb4feff 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -214,7 +214,7 @@ def getpydocspec(f, func): if default: defaults.append(default) - return ArgSpec(args, varargs, varkwargs, default, kwonly_args, + return ArgSpec(args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None) From c08b42d6dcc31f136e556d9ca2c7549f638ea1bd Mon Sep 17 00:00:00 2001 From: Weston Vial Date: Wed, 26 Aug 2015 15:39:12 -0400 Subject: [PATCH 0057/1094] Fix bug #548 - Transpose when empty line crashes --- bpython/curtsiesfrontend/manual_readline.py | 6 ++++-- bpython/test/test_manual_readline.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 02c7cbf03..24a1b9a27 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -280,11 +280,13 @@ def yank_prev_killed_text(cursor_offset, line, cut_buffer): @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): + if cursor_offset == 0: + return cursor_offset, line return (min(len(line), cursor_offset + 1), - line[:cursor_offset-1] + + line[:cursor_offset - 1] + (line[cursor_offset] if len(line) > cursor_offset else '') + line[cursor_offset - 1] + - line[cursor_offset+1:]) + line[cursor_offset + 1:]) @edit_keys.on('') diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 6ef610638..f1e24b780 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -201,6 +201,16 @@ def test_transpose_character_before_cursor(self): "adf s|asdf", "adf as|sdf"], transpose_character_before_cursor) + def test_transpose_empty_line(self): + self.assertEquals(transpose_character_before_cursor(0, ''), + (0,'')) + + def test_transpose_first_character(self): + self.assertEquals(transpose_character_before_cursor(0, 'a'), + transpose_character_before_cursor(0, 'a')) + self.assertEquals(transpose_character_before_cursor(0, 'as'), + transpose_character_before_cursor(0, 'as')) + def test_transpose_word_before_cursor(self): pass From 3abb483b4a109cbfe61d8ed6ca7e5eba3f9fa38f Mon Sep 17 00:00:00 2001 From: Weston Vial Date: Wed, 26 Aug 2015 16:14:14 -0400 Subject: [PATCH 0058/1094] Transpose characters if cursor is at the end of the line. Mimics emacs behavior. --- bpython/curtsiesfrontend/manual_readline.py | 4 +++- bpython/test/test_manual_readline.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 24a1b9a27..a919df14d 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -280,8 +280,10 @@ def yank_prev_killed_text(cursor_offset, line, cut_buffer): @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): - if cursor_offset == 0: + if cursor_offset < 2: return cursor_offset, line + if cursor_offset == len(line): + return cursor_offset, line[:-2] + line[-1] + line[-2] return (min(len(line), cursor_offset + 1), line[:cursor_offset - 1] + (line[cursor_offset] if len(line) > cursor_offset else '') + diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index f1e24b780..3c25e3bb5 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -207,9 +207,15 @@ def test_transpose_empty_line(self): def test_transpose_first_character(self): self.assertEquals(transpose_character_before_cursor(0, 'a'), - transpose_character_before_cursor(0, 'a')) + (0, 'a')) self.assertEquals(transpose_character_before_cursor(0, 'as'), - transpose_character_before_cursor(0, 'as')) + (0, 'as')) + + def test_transpose_end_of_line(self): + self.assertEquals(transpose_character_before_cursor(1, 'a'), + (1, 'a')) + self.assertEquals(transpose_character_before_cursor(2, 'as'), + (2, 'sa')) def test_transpose_word_before_cursor(self): pass From 563f5cb8a9dfeada479d479433b9a5e78f8fea23 Mon Sep 17 00:00:00 2001 From: Jeppe Toustrup Date: Thu, 1 Oct 2015 22:53:38 +0200 Subject: [PATCH 0059/1094] Filter out two underscore attributes in auto completion This change will require you to write two underscores in order to get autocompletion of attributes starting with two underscores, as requested in #528. Fixes #528 --- bpython/autocomplete.py | 7 ++++++- bpython/test/test_autocomplete.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 754b3a394..9bb86d895 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -253,7 +253,12 @@ def matches(self, cursor_offset, line, **kwargs): # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes # starting with a _ - if not r.word.split('.')[-1].startswith('_'): + if r.word.split('.')[-1].startswith('__'): + pass + elif r.word.split('.')[-1].startswith('_'): + matches = set(match for match in matches + if not match.split('.')[-1].startswith('__')) + else: matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) return matches diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 8ec494a09..3681485db 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -245,12 +245,12 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(3, 'a._', locals_={'a': OldStyleFoo()})) + self.com.matches(3, 'a.__', locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', - self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) + self.com.matches(3, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): @@ -260,7 +260,7 @@ def __getattr__(self, attr): locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', - self.com.matches(3, 'a._', locals_=locals_)) + self.com.matches(3, 'a.__', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 9f3460b6c4d6619c9080faf4b3e2e6755c7f354b Mon Sep 17 00:00:00 2001 From: Jeppe Toustrup Date: Mon, 5 Oct 2015 22:12:00 +0200 Subject: [PATCH 0060/1094] Correct auto complete tests after double underscore change --- bpython/test/test_autocomplete.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3681485db..24018f3d2 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -245,12 +245,12 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(3, 'a.__', locals_={'a': OldStyleFoo()})) + self.com.matches(4, 'a.__', locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', - self.com.matches(3, 'A.__', locals_={'A': OldStyleFoo})) + self.com.matches(4, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): @@ -260,7 +260,7 @@ def __getattr__(self, attr): locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', - self.com.matches(3, 'a.__', locals_=locals_)) + self.com.matches(4, 'a.__', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 4df988b5b7f192eb4ceec8217c7290f6fefe0d90 Mon Sep 17 00:00:00 2001 From: Shibo Yao Date: Wed, 7 Oct 2015 07:31:34 -0500 Subject: [PATCH 0061/1094] Fix python 3 compatibility Python 3 doesn't allow addition of dict_items and list, but union works. --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index b04a0636e..3d68b68ca 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -281,7 +281,7 @@ def make_colors(config): } if platform.system() == 'Windows': - c = dict(c.items() + + c = dict(c.items() | [ ('K', 8), ('R', 9), From 668bf06f8b7ec8c239e43f359591257d7993e6c9 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Wed, 25 Nov 2015 23:58:21 -0500 Subject: [PATCH 0062/1094] Show __new__ docstrings. Fixes #572 --- bpython/inspection.py | 4 +++- bpython/repl.py | 20 +++++++++++++++----- bpython/test/test_repl.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0dfb4feff..c4f05c086 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -231,7 +231,9 @@ def getfuncprops(func, f): try: is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or (func_name == '__init__' and not - func.endswith('.__init__'))) + func.endswith('.__init__')) + or (func_name == '__new__' and not + func.endswith('.__new__'))) except: # if f is a method from a xmlrpclib.Server instance, func_name == # '__init__' throws xmlrpclib.Fault (see #202) diff --git a/bpython/repl.py b/bpython/repl.py index 433d2732a..dc13b2f6e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -527,11 +527,21 @@ def get_args(self): return False if inspect.isclass(f): - try: - if f.__init__ is not object.__init__: - f = f.__init__ - except AttributeError: - return None + class_f = None + + if (hasattr(f, '__init__') and + f.__init__ is not object.__init__): + class_f = f.__init__ + if ((not class_f or + not inspection.getfuncprops(func, class_f)) and + hasattr(f, '__new__') and + f.__new__ is not object.__new__ and + f.__new__.__class__ is not object.__new__.__class__): # py3 + class_f = f.__new__ + + if class_f: + f = class_f + self.current_func = f self.funcprops = inspection.getfuncprops(func, f) if self.funcprops: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2bb13f4b8..08ed7564c 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -138,6 +138,14 @@ def setUp(self): self.repl.push(" def spam(self, a, b, c):\n", False) self.repl.push(" pass\n", False) self.repl.push("\n", False) + self.repl.push("class SpammitySpam(object):\n", False) + self.repl.push(" def __init__(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) + self.repl.push("class WonderfulSpam(object):\n", False) + self.repl.push(" def __new__(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) self.repl.push("o = Spam()\n", False) self.repl.push("\n", False) @@ -207,6 +215,13 @@ def test_nonexistent_name(self): self.set_input_line("spamspamspam(") self.assertFalse(self.repl.get_args()) + def test_issue572(self): + self.set_input_line("SpammitySpam(") + self.assertTrue(self.repl.get_args()) + + self.set_input_line("WonderfulSpam(") + self.assertTrue(self.repl.get_args()) + class TestGetSource(unittest.TestCase): def setUp(self): From f16a248b5e933c22e03401d4f2ef11ab3dc0a3ec Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 02:08:28 +0000 Subject: [PATCH 0063/1094] Array item completion is working with strings. Pressing tab works for autocompletion of array items now --- bpython/autocomplete.py | 39 +++++++++++++++++++++++++++++++++++++++ bpython/line.py | 24 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 9bb86d895..1cca269f4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -335,6 +335,44 @@ def list_attributes(self, obj): return dir(obj) +class ArrayObjectMembersCompletion(BaseCompletionType): + + def __init__(self, shown_before_tab=True, mode=SIMPLE): + self._shown_before_tab = shown_before_tab + self.completer = AttrCompletion(mode=mode) + + def matches(self, cursor_offset, line, **kwargs): + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + + r = self.locate(cursor_offset, line) + if r is None: + return None + member_part = r[2] + _, _, dexpr = lineparts.current_array_with_indexer(cursor_offset, line) + try: + locals_['temp_val_from_array'] = safe_eval(dexpr, locals_) + except (EvaluationError, IndexError): + return set() + + temp_line = line.replace(member_part, 'temp_val_from_array.') + + matches = self.completer.matches(len(temp_line), temp_line, **kwargs) + matches_with_correct_name = \ + set(match.replace('temp_val_from_array.', member_part) for match in matches) + + del locals_['temp_val_from_array'] + + return matches_with_correct_name + + def locate(self, current_offset, line): + return lineparts.current_array_item_member_name(current_offset, line) + + def format(self, match): + return after_last_dot(match) + + class DictKeyCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): @@ -565,6 +603,7 @@ def get_default_completer(mode=SIMPLE): MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), GlobalCompletion(mode=mode), + ArrayObjectMembersCompletion(mode=mode), CumulativeCompleter((AttrCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode) diff --git a/bpython/line.py b/bpython/line.py index c1da3943b..52c7b2355 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -226,3 +226,27 @@ def current_string_literal_attr(cursor_offset, line): if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: return LinePart(m.start(4), m.end(4), m.group(4)) return None + + +current_array_with_indexer_re = LazyReCompile( + r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\])\.(.*)''') + + +def current_array_with_indexer(cursor_offset, line): + """an array and indexer, e.g. foo[1]""" + matches = current_array_with_indexer_re.finditer(line) + for m in matches: + if m.start(1) <= cursor_offset and m.end(1) <= cursor_offset: + return LinePart(m.start(1), m.end(1), m.group(1)) + + +current_array_item_member_name_re = LazyReCompile( + r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\]\.)(.*)''') + + +def current_array_item_member_name(cursor_offset, line): + """the member name after an array indexer, e.g. foo[1].bar""" + matches = current_array_item_member_name_re.finditer(line) + for m in matches: + if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: + return LinePart(m.start(1), m.end(2), m.group(1)) From b2867705927b7dbcd109f9382b5280e8da9a76fa Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 18:04:52 +0000 Subject: [PATCH 0064/1094] Able to handle nested arrays now when autocompleting for array items --- bpython/line.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 52c7b2355..15495d988 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -229,7 +229,7 @@ def current_string_literal_attr(cursor_offset, line): current_array_with_indexer_re = LazyReCompile( - r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\])\.(.*)''') + r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+)\.(.*)''') def current_array_with_indexer(cursor_offset, line): @@ -241,7 +241,7 @@ def current_array_with_indexer(cursor_offset, line): current_array_item_member_name_re = LazyReCompile( - r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\]\.)(.*)''') + r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+\.)(.*)''') def current_array_item_member_name(cursor_offset, line): From ce1eb1aa2eb29c75a7e6a3a21c27e04dba4cc8b9 Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 18:59:28 +0000 Subject: [PATCH 0065/1094] Add tests --- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1cca269f4..58711955a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -335,7 +335,7 @@ def list_attributes(self, obj): return dir(obj) -class ArrayObjectMembersCompletion(BaseCompletionType): +class ArrayItemMembersCompletion(BaseCompletionType): def __init__(self, shown_before_tab=True, mode=SIMPLE): self._shown_before_tab = shown_before_tab @@ -603,7 +603,7 @@ def get_default_completer(mode=SIMPLE): MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), GlobalCompletion(mode=mode), - ArrayObjectMembersCompletion(mode=mode), + ArrayItemMembersCompletion(mode=mode), CumulativeCompleter((AttrCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 24018f3d2..d0e75f878 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -263,6 +263,22 @@ def __getattr__(self, attr): self.com.matches(4, 'a.__', locals_=locals_)) +class TestArrayItemCompletion(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.com = autocomplete.ArrayItemMembersCompletion() + + def test_att_matches_found_on_instance(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [Foo()]}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + @skip_old_style + def test_att_matches_found_on_old_style_instance(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': [OldStyleFoo()]}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + class TestMagicMethodCompletion(unittest.TestCase): def test_magic_methods_complete_after_double_underscores(self): From 58f3edfb26875450bbd71b909cb003f6b942bdab Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 15:21:22 -0500 Subject: [PATCH 0066/1094] Allow global completion in brackets Make DictKeyCompletion return None when no matches found so other completers can take over. Perhaps ideal would be comulative, but this seems like good behavior -- it's clear to the user in most cases that what's now being completed are keys, then other completion. --- bpython/autocomplete.py | 9 +++++---- bpython/test/test_autocomplete.py | 15 ++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 58711955a..1370a7fa4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -387,12 +387,13 @@ def matches(self, cursor_offset, line, **kwargs): try: obj = safe_eval(dexpr, locals_) except EvaluationError: - return set() + return None if isinstance(obj, dict) and obj.keys(): - return set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(r.word)) + matches = set("{0!r}]".format(k) for k in obj.keys() + if repr(k).startswith(r.word)) + return matches if matches else None else: - return set() + return None def locate(self, current_offset, line): return lineparts.current_dict_key(current_offset, line) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index d0e75f878..a8ec672de 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -190,20 +190,25 @@ def test_set_of_keys_returned_when_matches_found(self): self.assertSetEqual(com.matches(2, "d[", locals_=local), set(["'ab']", "'cd']"])) - def test_empty_set_returned_when_eval_error(self): + def test_none_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() local = {'e': {"ab": 1, "cd": 2}} - self.assertSetEqual(com.matches(2, "d[", locals_=local), set()) + self.assertEqual(com.matches(2, "d[", locals_=local), None) - def test_empty_set_returned_when_not_dict_type(self): + def test_none_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local = {'l': ["ab", "cd"]} - self.assertSetEqual(com.matches(2, "l[", locals_=local), set()) + self.assertEqual(com.matches(2, "l[", locals_=local), None) + + def test_none_returned_when_no_matches_left(self): + com = autocomplete.DictKeyCompletion() + local = {'d': {"ab": 1, "cd": 2}} + self.assertEqual(com.matches(3, "d[r", locals_=local), None) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local = {'mNumPy': MockNumPy()} - self.assertSetEqual(com.matches(7, "mNumPy[", locals_=local), set()) + self.assertEqual(com.matches(7, "mNumPy[", locals_=local), None) class Foo(object): From 05c83beb32b1c754e127fe40f8e09c7d2d3abf4d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 18:39:12 -0500 Subject: [PATCH 0067/1094] Avoid unsafe completion on array elements This commit removes functionality it would be nice to reimplement but wasn't safe: * completion on nested arrays ( list[1][2].a ) * completion on subclasses of list, tuple etc. --- bpython/autocomplete.py | 43 +++++++++++++++++++++------- bpython/line.py | 39 ++++++++++++++++--------- bpython/test/test_autocomplete.py | 26 +++++++++++++++++ bpython/test/test_line_properties.py | 40 ++++++++++++++++++++++++-- 4 files changed, 122 insertions(+), 26 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1370a7fa4..e1c34090d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -118,8 +118,10 @@ def matches(self, cursor_offset, line, **kwargs): raise NotImplementedError def locate(self, cursor_offset, line): - """Returns a start, stop, and word given a line and cursor, or None - if no target for this type of completion is found under the cursor""" + """Returns a Linepart namedtuple instance or None given cursor and line + + A Linepart namedtuple contains a start, stop, and word. None is returned + if no target for this type of completion is found under the cursor.""" raise NotImplementedError def format(self, word): @@ -346,28 +348,47 @@ def matches(self, cursor_offset, line, **kwargs): return None locals_ = kwargs['locals_'] - r = self.locate(cursor_offset, line) - if r is None: + full = self.locate(cursor_offset, line) + if full is None: return None - member_part = r[2] - _, _, dexpr = lineparts.current_array_with_indexer(cursor_offset, line) + + arr = lineparts.current_indexed_member_access_identifier( + cursor_offset, line) + index = lineparts.current_indexed_member_access_identifier_with_index( + cursor_offset, line) + member = lineparts.current_indexed_member_access_member( + cursor_offset, line) + try: - locals_['temp_val_from_array'] = safe_eval(dexpr, locals_) + obj = safe_eval(arr.word, locals_) + except EvaluationError: + return None + if type(obj) not in (list, tuple) + string_types: + # then is may be unsafe to do attribute lookup on it + return None + + try: + locals_['temp_val_from_array'] = safe_eval(index.word, locals_) except (EvaluationError, IndexError): - return set() + return None - temp_line = line.replace(member_part, 'temp_val_from_array.') + temp_line = line.replace(index.word, 'temp_val_from_array.') matches = self.completer.matches(len(temp_line), temp_line, **kwargs) + if matches is None: + return None + matches_with_correct_name = \ - set(match.replace('temp_val_from_array.', member_part) for match in matches) + set(match.replace('temp_val_from_array.', index.word+'.') + for match in matches if match[20:].startswith(member.word)) del locals_['temp_val_from_array'] return matches_with_correct_name def locate(self, current_offset, line): - return lineparts.current_array_item_member_name(current_offset, line) + a = lineparts.current_indexed_member_access(current_offset, line) + return a def format(self, match): return after_last_dot(match) diff --git a/bpython/line.py b/bpython/line.py index 15495d988..17599a5e1 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -228,25 +228,38 @@ def current_string_literal_attr(cursor_offset, line): return None -current_array_with_indexer_re = LazyReCompile( - r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+)\.(.*)''') +current_indexed_member_re = LazyReCompile( + r'''([a-zA-Z_][\w.]*)\[([a-zA-Z0-9_"']+)\]\.([\w.]*)''') -def current_array_with_indexer(cursor_offset, line): - """an array and indexer, e.g. foo[1]""" - matches = current_array_with_indexer_re.finditer(line) +def current_indexed_member_access(cursor_offset, line): + """An identifier being indexed and member accessed""" + matches = current_indexed_member_re.finditer(line) for m in matches: - if m.start(1) <= cursor_offset and m.end(1) <= cursor_offset: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(1), m.end(3), m.group()) + + +def current_indexed_member_access_identifier(cursor_offset, line): + """An identifier being indexed, e.g. foo in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) + for m in matches: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) -current_array_item_member_name_re = LazyReCompile( - r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+\.)(.*)''') +def current_indexed_member_access_identifier_with_index(cursor_offset, line): + """An identifier being indexed with the index, e.g. foo[1] in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) + for m in matches: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(1), m.end(2)+1, + "%s[%s]" % (m.group(1), m.group(2))) -def current_array_item_member_name(cursor_offset, line): - """the member name after an array indexer, e.g. foo[1].bar""" - matches = current_array_item_member_name_re.finditer(line) +def current_indexed_member_access_member(cursor_offset, line): + """The member name of an indexed object, e.g. bar in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) for m in matches: - if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: - return LinePart(m.start(1), m.end(2), m.group(1)) + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(3), m.end(3), m.group(3)) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index a8ec672de..37a52002d 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -283,6 +283,32 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': [OldStyleFoo()]}), set(['a[0].method', 'a[0].a', 'a[0].b'])) + def test_other_getitem_methods_not_called(self): + class FakeList(object): + def __getitem__(inner_self, i): + self.fail("possibly side-effecting __getitem_ method called") + + self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + + def test_tuples_complete(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': (Foo(),)}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + @unittest.skip('TODO, subclasses do not complete yet') + def test_list_subclasses_complete(self): + class ListSubclass(list): pass + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': ListSubclass([Foo()])}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + def test_getitem_not_called_in_list_subclasses_overriding_getitem(self): + class FakeList(list): + def __getitem__(inner_self, i): + self.fail("possibly side-effecting __getitem_ method called") + + self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + class TestMagicMethodCompletion(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 26eee9a07..7ea49f5d8 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -5,7 +5,9 @@ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ current_method_definition_name, current_single_word, \ - current_string_literal_attr + current_string_literal_attr, current_indexed_member_access_identifier, \ + current_indexed_member_access_identifier_with_index, \ + current_indexed_member_access_member def cursor(s): @@ -20,7 +22,7 @@ def decode(s): if not s.count('|') == 1: raise ValueError('match helper needs | to occur once') - if s.count('<') != s.count('>') or not s.count('<') in (0, 1): + if s.count('<') != s.count('>') or s.count('<') not in (0, 1): raise ValueError('match helper needs <, and > to occur just once') matches = list(re.finditer(r'[<>|]', s)) assert len(matches) in [1, 3], [m.group() for m in matches] @@ -305,6 +307,40 @@ def test_simple(self): self.assertAccess('"hey".asdf d|') self.assertAccess('"hey".<|>') +class TestCurrentIndexedMemberAccessIdentifier(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_identifier + + def test_simple(self): + self.assertAccess('[def].ghi|') + self.assertAccess('[def].|ghi') + self.assertAccess('[def].gh|i') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') + + +class TestCurrentIndexedMemberAccessIdentifierWithIndex(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_identifier_with_index + + def test_simple(self): + self.assertAccess('.ghi|') + self.assertAccess('.|ghi') + self.assertAccess('.gh|i') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') + + +class TestCurrentIndexedMemberAccessMember(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_member + + def test_simple(self): + self.assertAccess('abc[def].') + self.assertAccess('abc[def].<|ghi>') + self.assertAccess('abc[def].') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') if __name__ == '__main__': unittest.main() From dc5e87fd91f3e4377f7623c2b7ed72e4b7cecc53 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 29 Nov 2015 23:29:53 +0100 Subject: [PATCH 0068/1094] Log exceptions from auto completers Also do not catch KeyboardInterrupt and SystemExit. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 762a946c0..91818d4a5 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -29,6 +29,7 @@ import abc import glob import keyword +import logging import os import re import rlcompleter @@ -609,13 +610,18 @@ def get_completer(completers, cursor_offset, line, **kwargs): double underscore methods like __len__ in method signatures """ - try: - for completer in completers: + for completer in completers: + try: matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return sorted(matches), (completer if matches else None) - except: - pass + except Exception as e: + # Instead of crashing the UI, log exceptions from autocompleters. + logger = logging.getLogger(__name__) + logger.debug( + 'Completer {} failed with unhandled exception: {}'.format( + completer, e)) + continue + if matches is not None: + return sorted(matches), (completer if matches else None) return [], None From 404e5c718ebc3e58a655f65404aef36651da7f26 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 29 Nov 2015 23:39:15 +0100 Subject: [PATCH 0069/1094] No longer install Twisted for 2.6 Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index dadbd2373..f1fd0c7f8 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -20,15 +20,12 @@ if [[ $RUN == nosetests ]]; then # Python 2.6 specific dependencies if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2 + elif [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then + # dependencies for crasher tests + pip install Twisted urwid fi case $TRAVIS_PYTHON_VERSION in - 2*) - # dependencies for crasher tests - pip install Twisted urwid - # test specific dependencies - pip install mock - ;; - pypy) + 2*|pypy) # test specific dependencies pip install mock ;; From 599cfea433f60f750a3c019add211353a7bba4bc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 29 Nov 2015 14:51:44 -0500 Subject: [PATCH 0070/1094] fix parameter name completion --- bpython/autocomplete.py | 14 +++++++------- bpython/test/test_autocomplete.py | 4 ++-- bpython/test/test_repl.py | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 91818d4a5..070aa981b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -160,17 +160,17 @@ def format(self, word): return self._completers[0].format(word) def matches(self, cursor_offset, line, **kwargs): + return_value = None all_matches = set() for completer in self._completers: - # these have to be explicitely listed to deal with the different - # signatures of various matches() methods of completers matches = completer.matches(cursor_offset=cursor_offset, line=line, **kwargs) if matches is not None: all_matches.update(matches) + return_value = all_matches - return all_matches + return return_value class ImportCompletion(BaseCompletionType): @@ -634,11 +634,11 @@ def get_default_completer(mode=SIMPLE): FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), - GlobalCompletion(mode=mode), - ArrayItemMembersCompletion(mode=mode), - CumulativeCompleter((AttrCompletion(mode=mode), + CumulativeCompleter((GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), - mode=mode) + mode=mode), + ArrayItemMembersCompletion(mode=mode), + AttrCompletion(mode=mode), ) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 37a52002d..76c519511 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -107,10 +107,10 @@ def test_one_empty_completer_returns_empty(self): cumulative = autocomplete.CumulativeCompleter([a]) self.assertEqual(cumulative.matches(3, 'abc'), set()) - def test_one_none_completer_returns_empty(self): + def test_one_none_completer_returns_none(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc'), set()) + self.assertEqual(cumulative.matches(3, 'abc'), None) def test_two_completers_get_both(self): a = self.completer(['a']) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 08ed7564c..acd3a6889 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -383,7 +383,7 @@ def test_fuzzy_attribute_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) - # 3. Edge Cases + # 3. Edge cases def test_updating_namespace_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("foo") @@ -400,6 +400,19 @@ def test_file_should_not_appear_in_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertNotIn('__file__', self.repl.matches_iter.matches) + # 4. Parameter names + def test_paremeter_name_completion(self): + self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.set_input_line("foo(ab") + + code = "def foo(abc=1, abd=2, xyz=3):\n\tpass\n" + for line in code.split("\n"): + self.repl.push(line) + + self.assertTrue(self.repl.complete()) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['abc=', 'abd=', 'abs(']) + class TestCliRepl(unittest.TestCase): From 575cafaab9bf7f0339dd1541f6c6ce8b8885d9dd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 1 Dec 2015 10:45:46 +0100 Subject: [PATCH 0071/1094] Update appdata to latest spec Signed-off-by: Sebastian Ramacher --- data/bpython.appdata.xml | 43 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml index b24c09368..e3d5d32c9 100644 --- a/data/bpython.appdata.xml +++ b/data/bpython.appdata.xml @@ -1,8 +1,8 @@ - + - - bpython.desktop + + bpython.desktop CC0-1.0 MIT bpython interpreter @@ -23,15 +23,32 @@