bpython-0.17.1/0000755000175100017510000000000013240411345013240 5ustar useruser00000000000000bpython-0.17.1/windows.theme0000644000175100017510000000122213240407731015757 0ustar useruser00000000000000# Each letter represents a colour marker: # k, r, g, y, b, m, c, w, d # which stands for: # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default # # But in Windows (for now) # K, R, G, Y, B, M, C, W, k # blacK, blue, Green, cyan, red, Magenta, yellow, hite, grey # Capital letters represent bold (brighter) # Copy to %USERPROFILE%\.bpython\windows.theme and set "color_scheme = windows" in %USERPROFILE%\.bpython\config [syntax] keyword = G name = Y comment = c string = M error = B number = C operator = G punctuation = C token = M [interface] background = r output = w main = W prompt = W prompt_more = K bpython-0.17.1/MANIFEST.in0000644000175100017510000000072213240407731015003 0ustar useruser00000000000000include .pycheckrc include AUTHORS include CHANGELOG include LICENSE include data/bpython.png include data/bpython.desktop include data/bpython.appdata.xml include doc/sphinx/source/conf.py include doc/sphinx/source/*.rst include doc/sphinx/source/logo.png include *.theme include bpython/test/*.py include bpython/test/*.theme include bpython/translations/*/LC_MESSAGES/bpython.po include bpython/translations/*/LC_MESSAGES/bpython.mo include bpython/sample-config bpython-0.17.1/README.rst0000644000175100017510000001365213240410473014737 0ustar useruser00000000000000|ImageLink|_ .. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=bugfix-0.17 .. _ImageLink: https://travis-ci.org/bpython/bpython *********************************************************************** 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 occasion 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 or by pressing control-D like regular interactive Python. =================== Features & Examples =================== * Readline-like autocomplete, with suggestions displayed as you type. * In-line syntax highlighting. This uses Pygments for lexing the code as you type, and colours appropriately. * 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 session is re-evaluated. Use to rewind. * Edit the current line or your entire session in an editor. F7 opens the current session in a text editor, and if modifications are made, the session is rerun with these changes. * Pastebin code/write to file. Use the key to upload the screen's contents to pastebin, with a URL returned. * Reload imported Python modules. Use to clear sys.modules and rerun your session to test changes to code in a module you're working on. ============= 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 * greenlet * six >= 1.5 * Sphinx != 1.1.2 (optional, for the documentation) * mock (optional, for the testsuite) * babel (optional, for internationalization) * watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) Python 2 before 2.7.7 --------------------- If you are using Python 2 before 2.7.7, the following dependency is also required: * requests[security] cffi ---- If you have problems installing cffi, which is needed by OpenSSL, please take a look at `cffi docs`_. bpython-urwid ------------- ``bpython-urwid`` requires the following additional packages: * urwid ========== Known Bugs ========== For known bugs please see bpython's `known issues and FAQ`_ page. ====================== 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`_. 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! =================== CLI Windows Support =================== Dependencies ------------ `Curses`_ Use the appropriate version compiled by Christoph Gohlke. `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 Notes ------------ The curses used has a bug where the colours are displayed incorrectly: * red is swapped with blue * cyan is swapped with yellow To correct this I have provided a windows.theme file. This curses implementation has 16 colors (dark and light versions of the colours) ============ Alternatives ============ `ptpython`_ `IPython`_ Feel free to get in touch if you know of any other alternatives that people may be interested to try. .. _ptpython: https://github.com/jonathanslenders/ptpython .. _ipython: https://ipython.org/ .. _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 bpython-0.17.1/setup.cfg0000644000175100017510000000010313240411345015053 0ustar useruser00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 bpython-0.17.1/setup.py0000755000175100017510000002054013240410473014757 0ustar useruser00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import platform import re import subprocess import sys from distutils.command.build import build from setuptools import setup from setuptools.command.install import install as _install try: from babel.messages.frontend import compile_catalog as _compile_catalog from babel.messages.frontend import extract_messages as _extract_messages from babel.messages.frontend import update_catalog as _update_catalog from babel.messages.frontend import init_catalog as _init_catalog using_translations = True except ImportError: using_translations = False try: import sphinx from sphinx.setup_command import BuildDoc # Sphinx 1.1.2 is buggy and building bpython with that version fails. # See #241. using_sphinx = sphinx.__version__ >= '1.1.3' except ImportError: using_sphinx = False # version handling def git_describe_to_python_version(version): """Convert output from git describe to PEP 440 conforming versions.""" version_info = version.split('-') if len(version_info) < 2: return 'unknown' # we always have $version-$release release_type = version_info[1] version_data = { 'version': version_info[0], 'release_type': release_type, } if len(version_info) == 4: version_data['commits'] = version_info[2] else: version_data['commits'] = 0 if release_type == 'release': if len(version_info) == 2: # format: $version-release # This is the case at time of the release. fmt = '{version}' elif len(version_info) == 4: # format: $version-release-$commits-$hash # This is the case after a release. fmt = '{version}-{commits}' elif release_type == 'dev': # format: $version-dev-$commits-$hash or $version-dev fmt = '{version}.dev{commits}' else: match = re.match(r'^(alpha|beta|rc)(\d*)$', release_type) if match is None: return 'unknown' if len(version_info) == 2: fmt = '{version}{release_type}' elif len(version_info) == 4: fmt = '{version}{release_type}-{commits}' return fmt.format(**version_data) version_file = 'bpython/_version.py' version = 'unknown' try: # get version from git describe proc = subprocess.Popen(['git', 'describe', '--tags'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = proc.communicate()[0].strip() if sys.version_info[0] > 2: stdout = stdout.decode('ascii') if proc.returncode == 0: version = git_describe_to_python_version(stdout) except OSError: pass if version == 'unknown': try: # get version from existing version file with open(version_file) as vf: version = vf.read().strip().split('=')[-1].replace('\'', '') version = version.strip() except IOError: pass with open(version_file, 'w') as vf: vf.write('# Auto-generated file, do not edit!\n') vf.write('__version__ = \'%s\'\n' % (version, )) class install(_install): """Force install to run build target.""" def run(self): self.run_command('build') _install.run(self) cmdclass = { 'build': build, 'install': install } from bpython import package_dir translations_dir = os.path.join(package_dir, 'translations') # localization options if using_translations: class compile_catalog(_compile_catalog): def initialize_options(self): """Simply set default domain and directory attributes to the correct path for bpython.""" _compile_catalog.initialize_options(self) self.domain = 'bpython' self.directory = translations_dir self.use_fuzzy = True class update_catalog(_update_catalog): def initialize_options(self): """Simply set default domain and directory attributes to the correct path for bpython.""" _update_catalog.initialize_options(self) self.domain = 'bpython' self.output_dir = translations_dir self.input_file = os.path.join(translations_dir, 'bpython.pot') class extract_messages(_extract_messages): def initialize_options(self): """Simply set default domain and output file attributes to the correct values for bpython.""" _extract_messages.initialize_options(self) self.domain = 'bpython' self.output_file = os.path.join(translations_dir, 'bpython.pot') class init_catalog(_init_catalog): def initialize_options(self): """Simply set default domain, input file and output directory attributes to the correct values for bpython.""" _init_catalog.initialize_options(self) self.domain = 'bpython' self.output_dir = translations_dir self.input_file = os.path.join(translations_dir, 'bpython.pot') build.sub_commands.insert(0, ('compile_catalog', None)) cmdclass['compile_catalog'] = compile_catalog cmdclass['extract_messages'] = extract_messages cmdclass['update_catalog'] = update_catalog cmdclass['init_catalog'] = init_catalog if using_sphinx: class BuildDocMan(BuildDoc): def initialize_options(self): BuildDoc.initialize_options(self) self.builder = 'man' self.source_dir = 'doc/sphinx/source' self.build_dir = 'build' build.sub_commands.insert(0, ('build_sphinx_man', None)) cmdclass['build_sphinx_man'] = BuildDocMan if platform.system() in ['FreeBSD', 'OpenBSD']: man_dir = 'man' else: man_dir = 'share/man' # manual pages man_pages = [ (os.path.join(man_dir, 'man1'), ['build/man/bpython.1']), (os.path.join(man_dir, 'man5'), ['build/man/bpython-config.5']), ] else: man_pages = [] data_files = [ # desktop shortcut (os.path.join('share', 'applications'), ['data/bpython.desktop']), # AppData (os.path.join('share', 'appdata'), ['data/bpython.appdata.xml']), # icon (os.path.join('share', 'pixmaps'), ['data/bpython.png']) ] data_files.extend(man_pages) install_requires = [ 'pygments', 'requests', 'curtsies >=0.1.18', 'greenlet', 'six >=1.5' ] extras_require = { 'urwid': ['urwid'], 'watch': ['watchdog'], 'jedi': ['jedi'], # need requests[security] for SNI support (only before 2.7.7) ':python_full_version == "2.7.0" or ' 'python_full_version == "2.7.1" or ' 'python_full_version == "2.7.2" or ' 'python_full_version == "2.7.3" or ' 'python_full_version == "2.7.4" or ' 'python_full_version == "2.7.5" or ' 'python_full_version == "2.7.6"': [ 'pyOpenSSL', 'pyasn1', 'ndg-httpsclient' ] } packages = [ 'bpython', 'bpython.curtsiesfrontend', 'bpython.test', 'bpython.test.fodder', 'bpython.translations', 'bpdb' ] entry_points = { 'console_scripts': [ 'bpython = bpython.curtsies:main', 'bpython-curses = bpython.cli:main', 'bpython-urwid = bpython.urwid:main [urwid]', 'bpdb = bpdb:main' ] } tests_require = [] if sys.version_info[0] == 2: tests_require.append('mock') # translations mo_files = [] for language in os.listdir(translations_dir): mo_subpath = os.path.join(language, 'LC_MESSAGES', 'bpython.mo') if os.path.exists(os.path.join(translations_dir, mo_subpath)): mo_files.append(mo_subpath) setup( name="bpython", version=version, author="Bob Farrell, Andreas Stuehrk et al.", author_email="robertanthonyfarrell@gmail.com", description="Fancy Interface to the Python Interpreter", license="MIT/X", url="http://www.bpython-interpreter.org/", long_description="""bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", install_requires=install_requires, extras_require=extras_require, tests_require=tests_require, packages=packages, data_files=data_files, package_data={ 'bpython': ['sample-config'], 'bpython.translations': mo_files, 'bpython.test': ['test.config', 'test.theme'] }, entry_points=entry_points, cmdclass=cmdclass, test_suite='bpython.test' ) # vim: fileencoding=utf-8 sw=4 ts=4 sts=4 ai et sta bpython-0.17.1/light.theme0000644000175100017510000000126213240407731015400 0ustar useruser00000000000000# Each letter represents a colour marker: # k, r, g, y, b, m, c, w, d # which stands for: # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default # Capital letters represent bold # Copy to $XDG_CONFIG_HOME/bpython/foo.theme and set "color_scheme = foo" in # $XDG_CONFIG_HOME/bpython/config ($XDG_CONFIG_HOME defaults to ~/.config) [syntax] keyword = M name = r comment = b string = g error = r number = B operator = c paren = b punctuation = b token = g [interface] # XXX: gnome-terminal appears to be braindead. The cursor will disappear unless # you set the background colour to "d". background = w output = b main = b prompt = r prompt_more = g right_arrow_suggestion = K bpython-0.17.1/AUTHORS0000644000175100017510000000217113240407731014315 0ustar useruser00000000000000bpython is written and maintained by Bob Farrell . Other contributors are (in alphabetical order): * Thomas Ballinger * Federico Ceratto * Ingrid Cheung * Maja Frydrychowicz * Martha Girdler * Eike Hein * Allison Kaptur * Jason Laster * Miriam Lauter * Mary Mokuolu * Brandon Navra * Michele Orrù * Pavel Panchekha * Keyan Pishdadian * Sebastian Ramacher * Amjith Ramanujam * Andreas Stührk * Simon de Vlieger * Tarek Ziade * Marien Zwart A big thanks goes out to all the people who help us out by either submitting patches, helping us determine problems, our package maintainers, and of course everybody who creates issues for us to fix. bpython-0.17.1/data/0000755000175100017510000000000013240411345014151 5ustar useruser00000000000000bpython-0.17.1/data/bpython.png0000644000175100017510000001707313240407731016356 0ustar useruser00000000000000PNG  IHDRVVUVatEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp ݘCIDATx] tyKZ=,!KmLlvb0 ) P򀤜@OB$Mm$gO6 iMB$%p81`dY$}132b}|3Pc''h:tCԱ(-G]'z -‡>kDuZ$V#Qb F#D*†\-=52ӣV6S21ZobRǞȏ SW -\uNom\XӖy6zMԃ& 0lj9"p<{(e6=q_K8EH3~ww}m,}/M/|)ٸVj| 督[,qrPqb㽝O lE.{K:]7_m.fy*/;d '<ǥ5> Bab$fϠx}˪=_ 'x\,VN\ ϛi+75hb3 %?.QZ)09~89^߾>q]4iԶ岫G2zZw5(5D,by\c`r`88_բqԽ}S섗^z„;Dw 0FdiU SXd{i#rsg>Wsi A)+P |{o :(d9 ]${%(h2P"&$_Oq l}Fu͍FVW Uԍ0R_b(`9Hֶ(~j*i.]M5 $"5C/V_OƸ+Ō***,v_~D:yXx&܉S,՗\c[F: j:{5i8E"V\Q|WX\AEù̽ )eE$$2嬁pSF2˒Qq,uuWڕmk480L8ҩx:%^QXƞuSSʂsKSI .XSu+:vpcY2#Xy7t>0+`F \.u3&/uÊ**Aٗ*Aa*\mDM'19AH"zT ^T#T} Nn0c@[ f>Gc8aUb 4HK/S9 Zn s~`pLxAcB t%p6"ap?|F,!w Q`pi=FPn{_c+J+SX*UBY7 Yº;K?H$UDž| C [As 7C2]vL \%0S~n;]g6:6G6H(h|M{jMK}IJqiҩ]|S<6S8E gJ@fR7Y]x57'|Ow&r22U>0p#cã˩X E!3cJzn*vDW1,ꆍvlm8{5.0B-6_zuKCs|hcNW:񥘺k=yݖm(gU2yjFa)tJ2"$L~cD9GxJ"1 s]|ݹ˿3 o9+vefqbټ!2>'Ȅk?^i| ez%hItb{iSU٬.hȄL\ jC>Si~̎ullDkNUu0yI3BeyJC\FDU^HYXY,qbĉQB55zw|S~7)6gnB5/ Y_k۷ljGUGfly̬tx:W`eJ{)#hPRr&Z]pםnGv"a\Uu h yFVSL6a99-Eg*|es-#IǞ>?Ћ'ڗqFtB[VĒ(!r>AZٖlU 6VX%ݢޒYiquv=IF5ݫ?Һ-KT';wdcע77n +-)Om}WCt@F'%2&Ff 0el VR@vΞL/i四`Y2i`~HAD vE2{Ձ\]_k"([~/۶n$acq As+"q#*7ֶFbx/T"@xyŖ79v&غa{wskO\0XT3)Cl`)Qx\&^^.pȴ`WlZwǿظa_PX| ,4XISP>r c!XXVLrG<#;GNwI5cj9Ñs dgfK,k5Dn[*!QprDlg9B?T$j:i/HԈFB +Љ38FUd^iD(li(7;@m+v2͗{rYÚ2V(Eb-\;}|]MxJM#yfpG(}8 TZZ?tRcA="x,;(-ɆL0Xp׋`#ʯVb%DWNՊ‚jÚ ,}rﱼW(_?s,%qLuwcjg j#KXjaT=?cfƺ\KY]у`BuF _O#T9.Aa;ؿ%2 yYGV}ތk^l,5HO}U&ΚPaT{TЧSe1=t,vfI|)j?u:mV&QD41.P.{_ygy>OaTbPZE ~=)xeff*6SNJwV&l)K@lSu3ajS!W #C؊klGҖm z^=vГRU4q55M(dICtv+Ljȳ U2b(X%_M#y{ei~VZvs##cvߛ9e&RՌsL-զ?[*E^-qdTu3μĕ ڒee,A(k#Ϸ#O<+nY`8X2ISܱߗk[n\3lpSs,^[`)mѴW&%ž@PYٞ7NUY#pW:׳[dQ WN-Pt?:ѿ*UMs5҂me$GC; iTmQY,LL9;q*P)<^CG)~*2xĐcnssb.n`-!ٹ<*!ţ/nk³Z>Ū q:9t1+*v}{/ޅR0LΫ*PDkۯ.KZw~rW{ݷx͚ 訙Hۣ;5""kS?غj4.PjBv.̃#V=f`t_'p d?whD}':GS3a%z6O8oVG{tuwc*ei]m&O{GWO?0ܰb [rr*,՜dLv:Cc8>!xq_i .}򚎏\qõ+w[25%t|ٔt4XeO?NnːԭGsrgXwjKmZ_eYKr;t]+FS5zmMBKh1˓˼ *5WȧB:kGt=@7]i_됽ـumL:nZo$$jD@k$⺱jY6\qͫ)1ӶivX4inxGs:s>.Fo"js=Or>[Y۷|wFS@| W:6IENDB`bpython-0.17.1/data/bpython.appdata.xml0000644000175100017510000000370013240407731017773 0ustar useruser00000000000000 bpython.desktop CC0-1.0 MIT bpython interpreter Fancy interface for the Python interpreter

bpython is a fancy interface to the Python interpreter. It has the following features:

  • In-line syntax highlighting.
  • Readline-like autocomplete with suggestion displayed as you type.
  • Expected parameter list for any Python function.
  • "Rewind" function to pop the last line of code from memory and re-evaluate.
  • Send the code you've entered off to a pastebin.
  • Save the code you've entered to a file.
  • Auto-indentation.
http://www.bpython-interpreter.org/ https://github.com/bpython/bpython/issues http://bpython-interpreter.org/images/8.png http://bpython-interpreter.org/images/1.png http://bpython-interpreter.org/images/2.png http://bpython-interpreter.org/images/3.png http://bpython-interpreter.org/images/4.png http://bpython-interpreter.org/images/5.png http://bpython-interpreter.org/images/6.png http://bpython-interpreter.org/images/7.png bpython@googlegroups.com
bpython-0.17.1/data/bpython.desktop0000644000175100017510000000036513240407731017237 0ustar useruser00000000000000[Desktop Entry] Icon=bpython Name=bpython Comment=A fancy interface to the python interpreter! Exec=/usr/bin/bpython Terminal=true Type=Application Categories=Development;Utility;ConsoleOnly; StartupNotify=true Keywords=Python;REPL;interpreter; bpython-0.17.1/PKG-INFO0000644000175100017510000000064313240411345014340 0ustar useruser00000000000000Metadata-Version: 1.0 Name: bpython Version: 0.17.1 Summary: Fancy Interface to the Python Interpreter Home-page: http://www.bpython-interpreter.org/ Author: Bob Farrell, Andreas Stuehrk et al. Author-email: robertanthonyfarrell@gmail.com License: MIT/X Description-Content-Type: UNKNOWN Description: bpython is a fancy interface to the Python interpreter for Unix-like operating systems. Platform: UNKNOWN bpython-0.17.1/sample.theme0000644000175100017510000000127413240407731015555 0ustar useruser00000000000000# Each letter represents a colour marker: # k, r, g, y, b, m, c, w, d # which stands for: # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default # Capital letters represent bold # Copy to $XDG_CONFIG_HOME/bpython/foo.theme and set "color_scheme = foo" in # $XDG_CONFIG_HOME/bpython/config ($XDG_CONFIG_HOME defaults to ~/.config) [syntax] keyword = y name = c comment = b string = m error = r number = G operator = Y paren = y punctuation = y token = C paren = R [interface] # XXX: gnome-terminal appears to be braindead. The cursor will disappear unless # you set the background colour to "d". background = d output = w main = c prompt = c prompt_more = g right_arrow_suggestion = K bpython-0.17.1/.pycheckrc0000644000175100017510000000006413240407731015220 0ustar useruser00000000000000blacklist = ['pyparsing', 'code', 'pygments/lexer'] bpython-0.17.1/bpdb/0000755000175100017510000000000013240411345014147 5ustar useruser00000000000000bpython-0.17.1/bpdb/__main__.py0000644000175100017510000000235413240407731016251 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2013 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import sys if __name__ == '__main__': from . import main sys.exit(main()) bpython-0.17.1/bpdb/debugger.py0000644000175100017510000000367113240407731016320 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2008 Bob Farrell # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function import pdb import bpython class BPdb(pdb.Pdb): """ PDB with BPython support. """ def __init__(self, *args, **kwargs): pdb.Pdb.__init__(self, *args, **kwargs) self.prompt = '(BPdb) ' self.intro = 'Use "B" to enter bpython, Ctrl-d to exit it.' def postloop(self): # We only want to show the intro message once. self.intro = None pdb.Pdb.postloop(self) # cmd.Cmd commands def do_Bpython(self, arg): bpython.embed(self.curframe.f_locals, ['-i']) def help_Bpython(self): print("B(python)") print("") print("Invoke the bpython interpreter for this stack frame. To exit " "bpython and return to a standard pdb press Ctrl-d") # shortcuts do_B = do_Bpython help_B = help_Bpython bpython-0.17.1/bpdb/__init__.py0000644000175100017510000000776613240407731016304 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2008 Bob Farrell # Copyright (c) 2013 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, absolute_import import os import sys import traceback import bpython from .debugger import BPdb from optparse import OptionParser from pdb import Restart __version__ = bpython.__version__ def set_trace(): """ Just like pdb.set_trace(), a helper function that creates a debugger instance and sets the trace. """ debugger = BPdb() debugger.set_trace(sys._getframe().f_back) # Adopted verbatim from pdb for completeness: def post_mortem(t=None): # handling the default if t is None: # sys.exc_info() returns (type, value, traceback) if an exception is # being handled, otherwise it returns None t = sys.exc_info()[2] if t is None: raise ValueError("A valid traceback must be passed if no " "exception is being handled") p = BPdb() p.reset() p.interaction(None, t) def pm(): post_mortem(getattr(sys, "last_traceback", None)) def main(): parser = OptionParser( usage='Usage: %prog [options] [file [args]]') parser.add_option('--version', '-V', action='store_true', help='Print version and exit.') options, args = parser.parse_args(sys.argv) if options.version: print('bpdb on top of bpython version', __version__, end="") print('on top of Python', sys.version.split()[0]) print('(C) 2008-2013 Bob Farrell, Andreas Stuehrk et al. ' 'See AUTHORS for detail.') return 0 if len(args) < 2: print('usage: bpdb scriptfile [arg] ...') return 2 # The following code is based on Python's pdb.py. mainpyfile = args[1] if not os.path.exists(mainpyfile): print('Error:', mainpyfile, 'does not exist') return 1 # Hide bpdb from argument list. del sys.argv[0] # Replace bpdb's dir with script's dir in front of module search path. sys.path[0] = os.path.dirname(mainpyfile) pdb = BPdb() while True: try: pdb._runscript(mainpyfile) if pdb._user_requested_quit: break print("The program finished and will be restarted") except Restart: print("Restarting", mainpyfile, "with arguments:") print("\t" + " ".join(sys.argv[1:])) except SystemExit: # In most cases SystemExit does not warrant a post-mortem session. print("The program exited via sys.exit(). Exit status: ",) print(sys.exc_info()[1]) except: traceback.print_exc() print("Uncaught exception. Entering post mortem debugging") print("Running 'cont' or 'step' will restart the program") t = sys.exc_info()[2] pdb.interaction(None, t) print("Post mortem debugger finished. The " + mainpyfile + " will be restarted") bpython-0.17.1/CHANGELOG0000644000175100017510000006640013240410760014460 0ustar useruser00000000000000Changelog ========= 0.17.1 ------ Fixes: * Reverted #670 temporarily due to performance impact on large strings being output. 0.17 ---- New features: * #641: Implement Ctrl+O. * Add default_autoreload config option. Thanks to Alex Frieder. Fixes: * Fix deprecation warnings. * Do not call signal outside of main thread. Thanks to Max Nordlund. * Fix option-backspace behavior. Thanks to Alex Frieder. * #648: Fix paste helper. Thanks to Jakob Bowyer. * #653: Handle docstrings more carefully. * #654: Do not modify history file during tests. * #658: Fix newline handling. Thanks to Attila Szöllősi. * #670: Fix handlign of ANSI escape codes. Thanks to Attila Szöllősi. * #687: Fix encoding of jedi completions. 0.16 ---- New features: * #466: Improve handling of completion box height. Fixes: * Fix various spelling mistakes. Thanks to Josh Soref and Simeon Visser. * #601: Fix Python 2 issues on Windows. Thanks to Aditya Gupta. * #614: Fix issues when view source. Thanks to Daniel Hahler. * #625: Fix issues when runnings scripts with non-ASCII characters. * #639: Fix compatbility issues with pdb++. Thanks to Daniel Hahler. Support for Python 2.6 has been dropped. 0.15 ---- This release contains new features and plenty of bug fixes. New features: * #425: Added curtsies 0.2.x support. * #528: Hide private attribute from initial autocompletion suggestions. Thanks to Jeppe Toustrup. * #538: Multi-line banners are allowed. * #229: inspect.getsource works on interactively defined functions. Thanks to Michael Mulley. * Attribute completion works on literals and some expressions containing builtin objects. * Ctrl-e can be used to autocomplete current fish-style suggestion. Thanks to Amjith Ramanujam. Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. * #548 Fix transpose character bug. Thanks to Wes E. Vial. * #527 -q disables version banner. * #544 Fix Jedi completion error. * #536 Fix completion on old-style classes with custom __getattr__. * #480 Fix old-style class autocompletion. Thanks to Joe Jevnik. * #506 In python -i mod.py sys.modules[__name__] refers to module dict. * #590 Fix "None" not being displayed. * #546 Paste detection uses events instead of bytes returned in a single os.read call. * Exceptions in autocompletion are now logged instead of crashing bpython. * Fix reload in Python 3. Thanks to sharow. * Fix keyword argument parameter name completion. Changes to dependencies: * requests[security] has been changed to pyOpenSSL, pyasn1, and ndg-httpsclient. These dependencies are required before Python 2.7.7. 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 ------ Fixes: * #483: Fixed jedi exceptions handling. * #486: Fixed Python 3.3 compatibility. * #489: Create history file with mode 0600. * #491: Fix issues with file name completion. * #494: Fix six version requirement. * Fix conditional dependencies for SNI support in Python versions before 2.7.7. 0.14 ---- This release contains major changes to the frontends: * curtsies is the new default frontend. * The old curses frontend is available as bpython-curses. * The GTK+ frontend has been removed. New features: * #194: Syntax-highlighted tracebacks. Thanks to Miriam Lauter. * #234: Copy to system clipboard. * #285: Re-evaluate session and reimport modules. * #313: Warn when undo may take cause extended delay, and prompt to undo multiple lines. * #322: Watch imported modules for changes and re-evaluate on changes. * #328: bpython history not re-evaluated to edit a previous line of a multiline statement. * #334: readline command Meta-. for yank last argument. Thanks to Susan Steinman and Steph Samson. * #338: bpython help with F1. * #354: Edit config file from within bpython. * #382: Partial support for pasting in text with blank lines. * #410: Startup banner that shows Python and bpython version * #426: Experimental multiline autocompletion. * fish style last history completion with Arrow Right. Thanks to Nicholas Sweeting. * fish style automatic reverse history search with Arrow Up. Thanks to Nicholas Sweeting. * Incremental forward and reverse search. * All readline keys which kill/cut text correctly copy text for paste with Ctrl-y or Meta-y. * French translation. * Removal links for bpaste pastebins are now displayed. * More informative error messages when source cannot be found for an object. Thanks to Liudmila Nikolaeva and Miriam Lauter. * Message displayed if history in scrollback buffer is inconsistent with output from last re-evaluation of bpython session. Thanks to Susan Steinman. * Adjust logging level with -L or -LL. * String literal attribute completion. Fixes: * #254: Use ASCII characters if Unicode box characters are not supported by the terminal. * #284: __file__ is in scope after module run with bpython -i. Thanks to Lindsey Raymond. * #347: Fixed crash on unsafe autocompletion. * #349: Fixed writing newlines to stderr. * #363: Fixed banner crashing bpython-urwid. Thanks to Luca Barbato. * #366, #367: Fixed help() support in curtsies. * #369: Interactive sessions inherit compiler directives from files run with -i interactive flag. * #370, #401, #440, #448, #468, #472: Fixed various display issues in curtsies. * #391: Fixed crash when using Meta-backspace. Thanks to Tony Wang. * #438, #450: bpython-curtsies startup behavior fixed. Errors during startup are reported instead of crashing. * #447: Fixed behavior of duplicate keybindings. Thanks to Keyan Pishdadian. * #458: Fixed dictionary key completion crash in Python 2.6. Thanks to Mary Mokuolu. * Documentation fixes from Lindsey Raymond. * Fixed filename completion. * Fixed various Unicode issues in curtsies. * Fixed and re-enabled dictionary key completion in curtsies. The commandline option --type / -t has been renamed to --paste / -p. Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. Furthermore, it is no longer necessary to run 2to3 on the source code. This release brings a lot more code coverage, a new contributing guide, and most of the code now conforms to PEP-8. Changes to dependencies: * greenlet and curtsies are no longer optional. * six is a new dependency. * jedi is a new optional dependency required for multiline completion. * watchdog is a new optional dependency required for watching changes in imported modules. 0.13.2 ------- A bugfix release. The fixed bugs are: * #424: Use new JSON API at bpaste.net. * #430: Fixed SNI issues with new pastebin service on Mac OS X. * #432: Fixed crash in bpython-curtsies in special circumstances if history file is empty. Thanks to Lisa van Gelder. Changes to dependencies: * requests is a new dependency. * PyOpenSSL, ndg-httpsclient and pyasn1 are new dependencies on Mac OS X. 0.13.1 ------- A bugfix release. The fixed bugs are: * #287: Turned off dictionary completion in bpython-curtsies * #281: Fixed a crash on error-raising properties * #286: Fixed input in Python 3 * #293: Added encoding attribute to stdin bpython curtsies * #296: Fixed warnings in import completion for Python 3 * #290: Stop using root logger * #301: Specify curtsies version in requirements There's also a necessary regression: #232 (adding fileno() on stdin) is reintroduced because its previous fix was found to be the cause of #286 0.13 ---- There are a few new features, a bunch of bugfixes, and a new frontend for bpython in this release. * Dictionary key completion, thanks to Maja Frydrychowicz (#226). To use normal completion and ignore these key completions, type a space. * Edit current line in external editor: ctrl-x (#161) Fixes: * Python 2.5 compatibility, thanks to Michael Schuller (#279). Python 2.5 is not officially supported, but after few changes Michael introduced, he says it's working fine. * FakeStream has flush(), so works correctly with django.core.email.backends.console thanks to Marc Sibson (#259) * FakeStdin has fileno() (#232) * Changes to sys.ps1 and sys.ps2 are respected thanks to Michael Schulle (#267) * atexit registered functions run on exit (#258) * fixed an error on exit code when running a script with bpython script.py (#260) * setup.py extras are used to define dependencies for urwid and curtsies frontends There's a new frontend for bpython: bpython-curtsies. Curtsies is a terminal wrapper written to making native scrolling work in bpython. (#56, #245) Try bpython-curtsies for the bpython experience with a vanilla python layout. (demo: http://ballingt.com/assets/bpython-curtsies-scroll-demo-large.gif) This curtsies frontend addresses some issues unfixed in bpython-cli, and has a few extra features: * Editing full interpreter history in external editor with F7, which is rerun as in rewind * A new interpreter is used for rewind, unless bpython-curtsies was started with custom locals or in interactive mode (#71) * Ctrl-c behaves more like vanilla python (#177) * Completion still works if cursor at the end of the line (#147) * Movement keys meta-b, meta-f, and meta-backspace, ctrl-left and ctrl-right are all honored (#246, #201) * Non-ascii characters work in the file save prompt (#236) * New --type / -t option to run the contents of a file as though they were typed into the bpython-curtsies prompt A few things about bpython-curtsies are worse than regular bpython: * Bad things can happen when using several threads (#265) * output prints slowly (#262) * bpython-curtsies can't be backgrounded and resumed correctly (via ctrl-z, fg) (#274) There are two new options in the new [curtsies] section of the bpython config * list_above: whether completion window can cover text above the current line; defaults to True * fill_terminal: whether bpython-curtsies should be fullscreen (like bpython); defaults to False 0.12 ---- We want to give special thanks to the Hacker School project- (https://www.hackerschool.com/) for choosing bpython as their pet hacking project. In special we would like to thank the following people for contributing their code to bpython: - Martha Girdler - Allison Kaptur - Ingrid Cheung We'd also like to thank Eike Hein for contributing his pastebin code which now makes it possible to paste using a 3rd party program unlocking a whole slew of pastebins for bpython users. * Added a new pastebin_helper config option to name an executable that should perform pastebin upload on bpython's behalf. If set, this overrides pastebin_url. Data is supplied to the helper via STDIN, and it is expected to return a pastebin URL as the first word of its output. * Fixed a bug causing pastebin upload to fail after a previous attempt was unsuccessful. A duplicate pastebin error would be displayed in this case, despite the original upload having failed. * Added more key shortcuts to bpython.urwid * Smarter dedenting after certain expressions * #74 fixed broken completion when auto_display_list was disabled We also have done numerous cleanup actions including building the man pages from our documentation. Including the documentation in the source directory. Some minor changes to the README to have EOL 79 and changes to urwid to work better without twisted installed. * Fix ungetch issues with Python 3.3. See issues #230, #231. 0.11 ---- A bugfix/cleanup release .The fixed bugs are: * #204: "import math" not autocompleting on python 3.2 Otherwise lots of small additions to the to be replacement for our ncurses frontend, the urwid frontend. I'd like to specifically thank Amjith Ramanujam for his work on history search which was further implemented and is in working order right now. 0.10.1 ------ A bugfix release. The fixed bugs are: * #197: find_modules crashes on non-readable directories * #198: Source tarball lacks .po files 0.10 ---- As a highlight of the release, Michele Orrù added i18n support to bpython. Some issues have been resolved as well: * Config files are now located according to the XDG Base Directory Specification. The support for the old bpythonrc files has been dropped and ~/.bpython.ini as config file location is no longer supported. See issue #91. * Fixed some issues with tuple unpacking in argspec. See issues #133 and #138. * Fixed a crash with non-ascii filenames in import completion. See issue #139. * Fixed a crash caused by inspect.findsource() raising an IndexError which happens in some situations. See issue #94. * Non-ascii input should work now under Python 3. * Issue #165: C-a and C-e do the right thing now in urwid. * The short command-line option "-c config" was dropped as it conflicts with vanilla Python's "-c command" option. See issue #186. 0.9.7.1 ------- A bugfix release. The fixed bugs are: * #128: bpython-gtk is broken * #134: crash when using pastebin and no active internet connection 0.9.7 ----- Well guys. It's been some time since the latest release, six months have passed We have added a whole slew of new features, and closed a number of bugs as well. We also have a new frontend for bpython. Marien Zwart contributed a urwid frontend as an alternative for the curses frontend. Be aware that there still is a lot to fix for this urwid frontend (a lot of the keyboard shortcuts do not yet work for example) but please give it a good spin. Urwid also optionally integrates with a Twisted reactor and through that with things like the GTK event loop. At the same time we have done a lot of work on the GTK frontend. The GTK frontend is now 'usable'. Please give that a spin as well by running bpython-gtk on you system. We also welcome a new contributor in the name of Michele Orrù who we hope will help us fix even more bugs and improve functionality. As always, please submit any bugs you might find to our bugtracker. * Pastebin confirmation added; we were getting a lot of people accidentally pastebinning sensitive information so I think this is a good idea. * Don't read PYTHONSTARTUP when executed with -i. * BPDB was merged in. BPDB is an extension to PDB which allows you to press B in a PDB session which will let you be dropped into a bpython sessions with the current PDB locals(). For usage, see the documentation. * The clear word shortcut (default: C-w) now deletes to the buffer. * More tests have been added to bpython. * The pastebin now checks for a previous paste (during the session) with the exact same content to guard against twitchy fingers pastebinning multiple times. * Let import completion return "import " instead of "import". * GTK now has pastebin, both for full log as well as the current selection. * GTK now has write2file. * GTK now has a menu. * GTK now has a statusbar. * GTK now has show source functionality. * GTK saves the pastebin url to the clipboard. * GTK now has it's own configuration section. * Set focus to the GTK text widget to allow for easier embedding in PIDA and others which fixes issues #121. * #87: Add a closed attribute to Repl to fix mercurial.ui.ui expecting stderr to have this attribute. * #108: Unicode characters in docstring crash bpython * #118: Load_theme is not defined. * #99: Configurable font now documented. * #123: Pastebin can't handle 'ESC' key * #124: Unwanted input when using / keys in the statusbar prompt. 0.9.6.2 ------- Unfortunately another bugfix release as I (Bob) broke py3 support. * #84: bpython doesn't work with Python 3 Thanks very much to Henry Prêcheur for both the bug report and the patch. 0.9.6.1 ------- A quick bugfix release (this should not become a habit). * #82: Crash on saving file. 0.9.6 ------ A bugfix/feature release (and a start at gtk). Happy Christmas everyone! * #67: Make pastebin URL really configurable. * #68: Set a __main__ module and set interpreter's namespace to that module. * #70: Implement backward completion on backward tab. * #62: Hide matches starting with a _ unless explicitly typed. * #72: Auto dedentation * #78: Theme without a certain value raises exception - add the possibility for a banner to be shown on bpython startup (when embedded or in code) written by Caio Romao. - add a hack to add a write() method to our fake stdin object - Don't use curses interface when stdout is not attached to a terminal. - PEP-8 conformance. - Only restore indentation when inside a block. - Do not decrease the lineno in tracebacks for Py3 - Do not add internal code to history. - Make paren highlighting more accurate. - Catch SyntaxError in import completion. - Remove globals for configuration. - rl_history now stays the same, also after undo. 0.9.5.2 ------- A bugfix release. Fixed issues: * #60: Filename expansion: Cycling completions and deleting * #61: Filename expansion: Directory names with '.'s get mangled Other fixes without opened issues: * Encode items in the suggestion list properly * Expand usernames in file completion correctly * future imports in startup scripts can influence interpreter's behaviour now * Show the correct docstring for types without a own __init__ method 0.9.5.1 -------- Added missing data files to the tarball. 0.9.5 ----- Fixed issues: * #25 Problems with DEL, Backspace and C-u over multiple lines * #49 Sending last output to $PAGER * #51 Ability to embed bpython shell into an existing script * #52 FakeStdin.readlines() is broken * #53 Error on printing null character * #54 Parsing/introspection ncurses viewer neglects parenthesis bpython has added a view source shortcut to show the source of the current function. The history file is now really configurable. This issue was reported in Debian's bugtracker. bpython has now some basic support for Python 3 (requires Pygments >=1.1.1). As a result, setuptools is now optional. The pastebin URL is now configurable and the default pastebin is now bpaste.net Argument names are now shown as completion suggestions and one can tab through the completion list. 0.9.4 ----- Bugfix release (mostly) * when typing a float literal bpython autocompletes int methods (#36) * Autocompletion for file names (#40) * Indenting doesn't reset (#27) * bpython configuration has moved from ~/.bpython.ini to ~/.bpython/config (currently still supporting fallback) * leftovers of statusbar when exiting bpython cleaned up * bpython now does not crash when a 'popup' goes out of window bounds * numerous fixes and improvements to parentheses highlighting * made *all* keys configurable (except for arrow keys/pgup/pgdown) 0.9.3 ------ This release was a true whopper! * Full unicode support * Configurable hotkey support * Theming support * Pastemode, disables syntax highlighting during a paste for faster pasting, highlights when done * Parentheses matching * Argument highlighting 0.9.2 ----- * help() now uses an external pager if available. * Fix for highlighting prefixed strings. * Fix to reset string highlighting after a SyntaxError. * bpython now uses optparse for option parsing and it supports --version now. * Configuration files are no longer passed by the first command line argument but by the -c command line switch. * Fix for problem related to editing lines in the history: http://bitbucket.org/bobf/bpython/issue/10/odd-behaviour-when-editing-commands-in-the-history 0.9.1 ----- * Fixed a small but annoying bug with sys.argv ini file passing * Fix for Python 2.6 to monkeypatch they way it detects callables in rlcompleter * Config file conversion fix 0.9.0 ----- * Module import completion added. * Changed to paste.pocoo.org due to rafb.net no longer offering a pastebin service. * Switched to .ini file format for config file. * White background-friendly colour scheme added. * C-l now clears the screen. * SyntaxError now correctly added to history to prevent it garbling up on a redraw. Probably some other things, but I hate changelogs. :) 0.8.0 ------ It's been a long while since the last release and there have been numerous little bugfixes and extras here and there so I'm putting this out as 0.8.0. Check the hg commit history if you want more info: http://bitbucket.org/bobf/bpython/ 0.7.2 ----- Menno sent me some patches to fix some stuff: * Socket error handled when submitting to a pastebin. * Resizing could crash if you resize small enough. Other stuff: * 'self' in arg list is now highlighted a different colour. * flush_output option added to config to control whether output is flushed to stdout or not on exit. * Piping something to bpython made it lock up as stdin was not the keyboard - bpython just executes stdin and exits instead of trying to do something clever. * Mark Florisson (eggy) gave me a patch that stops weird breakage when unicode objects get added into the output buffer - they now get encoded into the output encoding. * Bohdan Vlasyuk sent me a patch that fixes a problem with the above patch from Mark if sys.__stdout__.encoding didn't exist. * Save to file now outputs executable code (i.e. without the >>> and ... and with "# OUT: " prepended to all output lines). I never used this feature much but someone asked for this behaviour. 0.7.1 ----- * Added support for a history file, defaults to ~/.pythonhist and 100 lines but is configurable from the rc file (see sample-rc). * Charles Duffy has added a yank/put thing - C-k and C-y. He also ran the code through some PEP-8 checker thing and fixed up a few old habits I manage to break but didn't manage to fix the code to reflect this - thank you! * Jørgen Tjernø has fixed up the autoindentation issues we encountered when bringing soft tabs in. * SyntaxError, ValueError and OverflowError are now caught properly (code.InteractiveInterpreter treats these as different to other exceptions as it doesn't print the whole traceback, so a different handler is called). This was discovered as I was trying to stop autoindentation from occurring on a SyntaxError, which has also been fixed. * '.' now in sys.path on startup. 0.7.0 ----- C-d behaviour changed so it no longer exits if the current line isn't empty. Extra linebreak added to end of stdout flush. pygments and pyparsing are now dependencies. Jørgen Tjernø has done lots of cool things like write a manpage and .desktop file and improved the way tabbing works and also added home, end and del key handling as well as C-w for deleting words - thanks a lot! raw_input() and all its friends now work fine. PYTHONSTARTUP handled without blowing up on stupid errors (it now parses the file at once instead of feeding it to the repl line-by-line). 0.6.4 ----- KeyboardInterrupt handler clears the list window properly now. 0.6.3 ----- Forgot to switch rpartition to split for 2.4 compat. 0.6.2 ----- The help() now works (as far as I can see) exactly the same as the vanilla help() in the regular interpreter. I copied some code from pydoc.py to make it handle the special cases, e.g. help('keywords') help('modules') etc. 0.6.1 ----- Somehow it escaped my attention that the list window was never fully using the rightmost column, except for the first row. This is because me and numbers don't have the best relationship. I think stability is really improving with the latest spat of bugfixes, keep me informed of any bugs. 0.6.0 ----- No noticeable changes except that bpython should now work with Python 2.4. Personally I think it's silly to make a development tool work with an out of date version of Python but some people seem to disagree. The only real downside is that I had to do a horrible version of all() using reduce(), otherwise there's no real differences in the code. 0.5.3 ----- Now you can configure a ~/.bpythonrc file (or pass a rc file at the command line (bpython /foo/bar). See README for details. 0.5.2 ----- help() actually displays the full help page, and I fixed up the ghetto pager a little. 0.5.1 ----- Now you can hit tab to display the autocomplete list, rather than have it pop up automatically as you type which, apparently, annoys Brendogg. 0.5.0 ----- A few people have commented that the help() built-in function doesn't work so well with bpython, since Python will try to output the help string to PAGER (usually "less") which obviously makes everything go wrong when curses is involved. With a bit of hackery I've written my own ghetto pager and injected my own help function into the interpreter when it initialises in an attempt to rectify this. As such, it's pretty untested but it seems to be working okay for me. Suggestions/bug reports/patches are welcome regarding this. 0.4.2 ----- Well, hopefully we're one step closer to making the list sizing stuff work. I really hate doing code for that kind of thing as I never get it quite right, but with perseverence it should end up being completely stable; it's not the hardest thing in the world. Various cosmetic fixes have been put in at the request of a bunch of people who were kind enough to send me emails regarding their experiences. PYTHONSTARTUP is now dealt with and used properly, as per the vanilla interpreter. 0.4.1 ----- It looks like the last release was actually pretty bug-free, aside from one tiny bug that NEVER ACTUALLY HAPPENS but someone was bugging me about it anyway, oh well. 0.4.0 ----- It's been quite a long time since the last update, due to several uninteresting and invalid excuses, but I finally reworked the list drawing procedures so the crashing seems to have been taken care of to an extent. If it still crashes, the way I've written it will hopefully allow a much more robust way of fixing it, one that might actually work. 0.3.2 ----- Thanks to Aaron Gallagher for pointing out a case where the hugely inefficient list generation routines were actually making a significant issue; they're much more efficient now and should hopefully not cause any more problems. 0.3.1 ----- Thanks to Klaus Alexander Seis for the expanduser() patch. Auto indent works on multiple levels now. 0.3.0 ----- Now with auto-indent. Let me know if it's annoying. 0.2.4 ----- Thanks a lot to Angus Gibson for submitting a patch to fix a problem I was having with initialising the keyboard stuff in curses properly. Also a big thanks to John Beisley for providing the patch that shows a class __init__ method's argspec on class instantiation. I've fixed up the argspec display so it handles really long argspecs (e.g. subprocess.Popen()) and doesn't crash if something horrible happens (rather, it avoids letting something horrible happen). I decided to add a key that will get rid of the autocomplete window, since it can get in the way. C-l seemed like a good choice, since it would work well as a side-effect of redrawing the screen (at least that makes sense to me). In so doing I also cleaned up a lot of the reevaluating and resizing code so that a lot of the strange output seen on Rewind/resize seems to be gone. 0.2.3 ----- The fix for the last bug broke the positioning of the autocomplete box, whoops. 0.2.2 ----- That pesky bug keeps coming up. I think it's finally nailed but it's just a matter of testing and hoping. I hate numbers. 0.2.1 ----- I'm having a bit of trouble with some integer division that's causing trouble when a certain set of circumstances arise, and I think I've taken care of that little bug, since it's a real pain in the ass and only creeps up when I'm actually doing something useful, so I'll test it for a bit and release it as hopefully a bug fixed version. 0.2.0 ----- A little late in the day to start a changelog, but here goes... This version fixed another annoying little bug that was causing crashes given certain exact circumstances. I always find it's the way with curses and sizing of windows and things... I've also got bpython to try looking into pydoc if no matches are found for the argspec, which means the builtins have argspecs too now, hooray. bpython-0.17.1/doc/0000755000175100017510000000000013240411345014005 5ustar useruser00000000000000bpython-0.17.1/doc/sphinx/0000755000175100017510000000000013240411345015316 5ustar useruser00000000000000bpython-0.17.1/doc/sphinx/source/0000755000175100017510000000000013240411345016616 5ustar useruser00000000000000bpython-0.17.1/doc/sphinx/source/releases.rst0000644000175100017510000000301413240407731021155 0ustar useruser00000000000000.. _releases: Releases ======== Release schedule ---------------- bpython does not have a set release cycle. The developers will decide together when the time is ripe to release a version. For information what happens after the decision is made to make a release you should read the 'Release Path' section. Release Path ------------ After it is decided to release a new version of bpython the following checklist is followed: * The repository is frozen, nobody pushes until the version is built. * Bob (:ref:`authors`) makes a tarball of the new version and sends it to Simon (:ref:`authors`) who will host it on the bpython website. * The package is then downloaded by all of the people who like to test it. * Everybody checks if there are no great problems: * Version numbers correct? * CHANGELOG is correct? * AUTHORS? * After everybody says 'yes' the website and PyPI are updated to point to this new version. * Simon (:ref:`authors`) also checks if all numbers on the website have been updated. * 24 hours later package maintainers could update their stuff. Checklist --------- A checklist to perform some manual tests before a release: Check that all of the following work before a release: * Runs under Python 2.7, 3.3, 3.4 and 3.5. * Save * Rewind * Pastebin * Pager * Inspect source * History * Tab completion * Argument inspection * All keybinds * All packaged themes * Command line arguments correctly passed to scripts * Delegate to standard Python appropriately * Update CHANGELOG * Update __version__ bpython-0.17.1/doc/sphinx/source/windows.rst0000644000175100017510000000111613240407731021045 0ustar useruser00000000000000.. _windows: Windows ======= When bpython was developed it only supported Linux and we grew support for other platforms as well. There are no official binaries for bpython on Windows (though this is something we plan on providing in the future). The easiest way to get `bpython.cli` (the curses frontend running) is to install an unofficial windows binary for pdcurses from: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses. After this you can just `pip install bpython` and run bpython curses frontend like you would on a Linux system (e.g. by typing `bpython-curses` on your prompt). bpython-0.17.1/doc/sphinx/source/conf.py0000644000175100017510000001564413240407731020133 0ustar useruser00000000000000# -*- coding: utf-8 -*- # # bpython documentation build configuration file, created by # sphinx-quickstart on Mon Jun 8 11:58:16 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'bpython' copyright = u'2008-2015 Bob Farrell, Andreas Stuehrk et al.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../bpython/_version.py') with open(version_file) as vf: version = vf.read().strip().split('=')[-1].replace('\'', '') # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. unused_docs = ['configuration-options'] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = 'logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'bpythondoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). #latex_documents = [ # ('index', 'bpython.tex', u'bpython Documentation', # u'Robert Farrell', 'manual'), #] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('man-bpython', 'bpython', u'a fancy {curtsies, curses, urwid} interface to the Python interactive interpreter', [], 1), ('man-bpython-config', 'bpython-config', u'user configuration file for bpython', [], 5) ] # If true, show URL addresses after external links. #man_show_urls = False bpython-0.17.1/doc/sphinx/source/bpdb.rst0000644000175100017510000000061613240407731020266 0ustar useruser00000000000000.. _bpdb: bpdb ==== To enable bpython support within pdb, start pdb with the following code: .. code-block:: python import bpdb bpdb.set_trace() This will drop you into bpdb instead of pdb, which works exactly like pdb except that you can additionally start bpython at the current stack frame by issuing the command `Bpython` or `B`. You can exit bpython with `^d` to return to bpdb. bpython-0.17.1/doc/sphinx/source/community.rst0000644000175100017510000000174113240407731021403 0ustar useruser00000000000000.. _community: Community ========= Do you need help with using bpython? Do you want to thank the contributors personally? Or maybe you want to help out, contribute some code or resources or want to help in making bpython known to other persons? These are the places where you can find us. IRC --- You can find us in `#bpython `_ on the `Freenode `_ network. Don't worry when you get no response (this does not usually happen) but we are all from Europe and when you get to the channel during our nighttime you might have to wait a while for a response. Mailing List ------------ We have a mailing list at `google groups `_. You can post questions there and releases are announced on the mailing list. Website ------- Our main website is http://bpython-interpreter.org/, our documentation can be found at http://docs.bpython-interpreter.org/, and our pastebin can be found at http://bpaste.net/. bpython-0.17.1/doc/sphinx/source/configuration-options.rst0000644000175100017510000002271113240407731023717 0ustar useruser00000000000000General ------- This refers to the ``[general]`` section in your `$XDG_CONFIG_HOME/bpython/config` file. arg_spec ^^^^^^^^ Display the arg spec (list of arguments) for callables, when possible (default: True). auto_display_list ^^^^^^^^^^^^^^^^^ Display the autocomplete list as you type (default: True). When this is off, you can hit tab to see the suggestions. autocomplete_mode ^^^^^^^^^^^^^^^^^ There are three modes for autocomplete. simple, substring, and fuzzy. Simple matches methods with a common prefix, substring matches methods with a common subsequence, and fuzzy matches methods with common characters (default: simple). As of version 0.14 this option has no effect, but is reserved for later use. .. versionadded:: 0.12 .. _configuration_color_scheme: color_scheme ^^^^^^^^^^^^ See :ref:`themes` for more information. Color schemes should be put in ``$XDG_CONFIG_HOME/bpython/``. For example, to use the theme ``$XDG_CONFIG_HOME/bpython/foo.theme`` set ``color_scheme = foo`` Leave blank or set to "default" to use the default (builtin) theme. complete_magic_methods ^^^^^^^^^^^^^^^^^^^^^^ Whether magic methods should be auto completed (default: True). dedent_after ^^^^^^^^^^^^ Number of blank lines required before next line will be dedented (default: 1). If set to 0, automatic dedenting never occurs. editor ^^^^^^ Editor for externally editing the current line, session, or config file. .. versionadded:: 0.13 flush_output ^^^^^^^^^^^^ Whether to flush all output to stdout on exit (default: True). Only relevant to bpython-curses and bpython-urwid. highlight_show_source ^^^^^^^^^^^^^^^^^^^^^ Whether the source code of an object should be syntax highlighted (default: True). hist_duplicates ^^^^^^^^^^^^^^^ Whether to store duplicate entries in the history (default: True). hist_file ^^^^^^^^^ History file (default: ``~/.pythonhist``). hist_length ^^^^^^^^^^^ Number of lines to store in history (set to 0 to disable) (default: 100). paste_time ^^^^^^^^^^ The time between keypresses before pastemode is deactivated in bpython-curses (default: 0.02). pastebin_confirm ^^^^^^^^^^^^^^^^ Whether pasting to a pastebin needs to be confirmed before sending the data (default: True). pastebin_expiry ^^^^^^^^^^^^^^^ Time duration after which a paste should expire. Valid values are ``1day``, ``1week`` and ``1month`` (default: ``1week``). .. versionadded:: 0.14 pastebin_helper ^^^^^^^^^^^^^^^ The name of a helper executable that should perform pastebin upload on bpython's behalf. If set, this overrides `pastebin_url`. It also overrides `pastebin_show_url`, as the helper is expected to return the full URL to the pastebin as the first word of its output. The data is supplied to the helper via STDIN. An example helper program is ``pastebinit``, available for most systems. The following helper program can be used to create `gists `_: .. code-block:: python #!/usr/bin/env python import sys import urllib2 import json def do_gist_json(s): """ Use json to post to github. """ gist_public = False gist_url = 'https://api.github.com/gists' data = {'description': None, 'public': None, 'files' : { 'sample': { 'content': None } }} data['description'] = 'Gist from BPython' data['public'] = gist_public data['files']['sample']['content'] = s req = urllib2.Request(gist_url, json.dumps(data), {'Content-Type': 'application/json'}) try: res = urllib2.urlopen(req) except HTTPError, e: return e try: json_res = json.loads(res.read()) return json_res['html_url'] except HTTPError, e: return e if __name__ == "__main__": s = sys.stdin.read() print do_gist_json(s) .. versionadded:: 0.12 pastebin_show_url ^^^^^^^^^^^^^^^^^ The url under which the new paste can be reached. ``$paste_id`` will be replaced by the ID of the new paste (default: https://bpaste.net/show/$paste_id/). pastebin_removal_url ^^^^^^^^^^^^^^^^^^^^ The url under which a paste can be removed. ``$removal_id`` will be replaced by the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). .. versionadded:: 0.14 pastebin_url ^^^^^^^^^^^^ The pastebin url to post to (without a trailing slash). This pastebin has to be a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON interface (default: https://bpaste.net/json/new). save_append_py ^^^^^^^^^^^^^^ Whether to append ``.py`` to the filename while saving the input to a file. .. versionadded:: 0.13 single_undo_time ^^^^^^^^^^^^^^^^ Time duration an undo must be predicted to take before prompting to undo multiple lines at once. Use -1 to never prompt, or 0 to always prompt. (default: 1.0) .. versionadded:: 0.14 syntax ^^^^^^ Syntax highlighting as you type (default: True). tab_length ^^^^^^^^^^ Soft tab size (default 4, see PEP-8). unicode_box ^^^^^^^^^^^ Whether to use Unicode characters to draw boxes. .. versionadded:: 0.14 Keyboard -------- This section refers to the ``[keyboard]`` section in your ``$XDG_CONFIG_HOME/bpython/config``. You can set various keyboard shortcuts to be used by bpython. However, we have yet to map all keys to their respective control codes. If you configure a key combination which is not yet supported by bpython it will raise an exception telling you the key does not exist in bpython.keys. Valid keys are: * Control + any alphanumeric character (C-a through C-z, also a few others). * Any function key ranging from F1 to F12. backspace ^^^^^^^^^ Default: C-h Delete character in front of the cursor. .. versionadded:: 0.14 beginning_of_line ^^^^^^^^^^^^^^^^^ Default: C-a Move to the beginning of the line. .. versionadded:: 0.14 clear_line ^^^^^^^^^^ Default: C-u Clears to the beginning of the line. clear_screen ^^^^^^^^^^^^ Default: C-l Clears the screen to the top. clear_word ^^^^^^^^^^ Default: C-w Clear the word the cursor is currently on. copy_clipboard ^^^^^^^^^^^^^^ Default: F10 Copy the entire session to clipboard. .. versionadded:: 0.14 cut_to_buffer ^^^^^^^^^^^^^ Default: C-k Cuts the current line to the buffer. delete ^^^^^^ Default: C-d Delete character under the cursor. down_one_line ^^^^^^^^^^^^^ Default: C-n Move the cursor down, by one line. edit_config ^^^^^^^^^^^ Default: F3 Edit bpython configuration in external editor. .. versionadded:: 0.14 edit_current_block ^^^^^^^^^^^^^^^^^^ Default: C-x Edit current block in external editor. .. versionadded:: 0.14 end_of_line ^^^^^^^^^^^ Default: C-e Move to the of the line. .. versionadded:: 0.14 exit ^^^^ Default: C-d Exits bpython (use on empty line) external_editor ^^^^^^^^^^^^^^^ Default: F7 Edit the entire session in an external editor. .. versionadded:: 0.13 help ^^^^ Default: F1 Brings up sincerely cheerful description of bpython features and current key bindings. .. versionadded:: 0.14 incremental_search ^^^^^^^^^^^^^^^^^^^^^^^^^^ Default: M-s Perform incremental search on all stored lines in the history. .. versionadded:: 0.15 last_output ^^^^^^^^^^^ Default: F9 Shows the last output in the systems $PAGER. Only works in bpython-curses. left ^^^^ Default: C-b Move a character to the left. .. versionadded:: 0.14 pastebin ^^^^^^^^ Default: F8 reimport ^^^^^^^^ Default: F6 Reruns entire session, reloading all modules by clearing the sys.modules cache. .. versionadded:: 0.14 reverse_incremental_search ^^^^^^^^^^^^^^^^^^^^^^^^^^ Default: M-r Perform reverse incremental search on all stored lines in the history. .. versionadded:: 0.15 right ^^^^^ Default: C-f Move a character to the right. .. versionadded:: 0.14 save ^^^^ Default: C-s Saves the current session to a file (prompts for filename) search ^^^^^^ Default: C-o Search up for any lines containing what is on the current line. show_source ^^^^^^^^^^^ Default: F2 Shows the source of the currently being completed (python) function. toggle_file_watch ^^^^^^^^^^^^^^^^^ Default: F5 Toggles file watching behaviour; re-runs entire bpython session whenever an imported module is modified. .. versionadded:: 0.14 transpose_chars ^^^^^^^^^^^^^^^ Default: C-t Transpose current character with the one left of it. .. versionadded:: 0.14 undo ^^^^ Default: C-r Rewinds the last action. up_one_line ^^^^^^^^^^^ Default: C-p Move the cursor up, by one line. yank_from_buffer ^^^^^^^^^^^^^^^^ Default: C-y Pastes the current line from the buffer (the one you previously cutted) CLI --- This refers to the ``[cli]`` section in your config file. suggestion_width ^^^^^^^^^^^^^^^^ Default: 0.8 The width of the suggestion window in percent of the terminal width. .. versionadded:: 0.9.8 trim_prompts ^^^^^^^^^^^^ Default: False Trims lines starting with '>>> ' when set to True. curtsies -------- This refers to the ``[curtsies]`` section in your config file. .. versionadded:: 0.13 list_above ^^^^^^^^^^ Default: False When there is space above the current line, whether the suggestions list will be displayed there instead of below the current line. right_arrow_completion ^^^^^^^^^^^^^^^^^^^^^^ Default: True Full line suggestion and completion (like fish shell and many web browsers). Full line completions are displayed under the cursor in gray. When the cursor is at the end of a line, pressing right arrow or ctrl-f will complete the full line. This option also turns on substring history search, highlighting the matching section in previous result. Sample config ------------- .. include:: ../../../bpython/sample-config :literal: bpython-0.17.1/doc/sphinx/source/contributing.rst0000644000175100017510000001112313240407731022061 0ustar useruser00000000000000.. _contributing: Contributing to bpython ======================= Thanks for working on bpython! On the `GitHub issue tracker`_ some issues are labeled bite-size_ - these are particularly good ones to start out with. See our section about the :ref:`community` for a list of resources. `#bpython `_ on Freenode is particularly useful, but you might have to wait for a while to get a question answered depending on the time of day. Getting your development environment set up ------------------------------------------- bpython supports Python 2.7, 3.3 and newer. The code is compatible with all supported versions without the need to run post processing like `2to3`. Using a virtual environment is probably a good idea. Create a virtual environment with .. code-block:: bash # determines Python version used $ virtualenv bpython-dev # necessary every time you work on bpython $ source bpython-dev/bin/activate Fork bpython in the GitHub web interface, then clone the repo: .. code-block:: bash $ git clone git@github.com:YOUR_GITHUB_USERNAME/bpython.git # or "git clone https://github.com/YOUR_GITHUB_USERNAME/bpython.git" Next install your development copy of bpython and its dependencies: .. code-block:: bash $ cd bpython # install bpython and required dependencies $ pip install -e . # install optional dependencies $ pip install watchdog urwid # development dependencies $ pip install sphinx mock nose # this runs your modified copy of bpython! $ bpython .. note:: Many requirements are also available from your distribution's package manager. On Debian/Ubuntu based systems, the following packages can be used: .. code-block:: bash $ sudp apt-get install python-greenlet python-pygments python-requests $ sudo apt-get install python-watchdog python-urwid $ sudo apt-get install python-sphinx python-mock python-nose Remember to replace ``python`` with ``python3`` in every package name if you intend to develop with Python 3. You also need to run `virtualenv` with `--system-site-packages` packages, if you want to use the packages provided by your distribution. .. note:: Installation of some dependencies with ``pip`` requires Python headers and a C compiler. These are also available from your package manager. .. code-block:: bash $ sudo apt-get install gcc python-dev As a first dev task, I recommend getting `bpython` to print your name every time you hit a specific key. To run tests from the bpython directory: .. code-block:: bash $ nosetests If you want to skip test cases that are known to be slow, run `nosetests` in the following way: .. code-block:: bash $ nosetests -A "speed != 'slow'" Building the documentation -------------------------- The documentation is included in the bpython repository. After checking out the bpython repository and installing `sphinx` as described in the previous step, you can run the following command in your checkout of the repository to build the documentation: .. code-block:: bash $ make -C doc/sphinx html Afterwards you can point your browser to `doc/sphinx/build/html/index.html`. Don't forget to recreate the HTML after you make changes. Hacking on the site or theme ---------------------------- The site (and its theme as well) is stored in a separate repository and built using pelican. To start hacking on the site you need to start out with a checkout and probably a virtual environment: .. code-block:: bash $ virtualenv bpython-site-dev $ source bpython-site-dev/bin/activate $ pip install pelican Fork bsite and bsite-theme in the GitHub web interface, then clone the repositories: .. code-block:: bash $ git clone git@github.com:YOUR_GITHUB_USERNAME/bsite.git $ git clone git@github.com:YOUR_GITHUB_USERNAME/bsite-theme.git Next you can fiddle around in the source files. If you want to build the site you activate your virtualenv and tell pelican to generate the site with the included configuration file. .. code-block:: bash $ source bpython-site-dev/bin/activate # if you want to fiddle on the text of the site otherwise go into # bsite-theme $ cd bsite # if you checked out the theme in a different place, use that path $ pelican -t ../bsite-theme -s pelicanconf.py After this you can open the `output/index.html` in your favourite browser and see if your changes had an effect. .. _GitHub issue tracker: https://github.com/bpython/bpython/issues .. _bite-size: https://github.com/bpython/bpython/labels/bitesize bpython-0.17.1/doc/sphinx/source/configuration.rst0000644000175100017510000000061213240407731022222 0ustar useruser00000000000000.. _configuration: Configuration ============= You can edit the config file by pressing F3 (default). If a config file does not exist you will asked if you would like to create a file. By default it will be saved to ``$XDG_CONFIG_HOME/.config/bpython/config`` [#f1]_. .. include:: configuration-options.rst .. :: Footnotes .. [#f1] ``$XDG_CONFIG_HOME`` defaults to ``~/.config`` if not set. bpython-0.17.1/doc/sphinx/source/man-bpython-config.rst0000644000175100017510000000126513240407731023057 0ustar useruser00000000000000:orphan: bpython-config manual page ========================== Synopsis -------- **$XDG_CONFIG_HOME/bpython/config** Description ----------- The configuration file contains various options controlling the behaviour of :program:`bpython`. .. include:: configuration-options.rst :end-before: .. _configuration_color_scheme: .. include:: configuration-options.rst :start-after: .. _configuration_color_scheme: Author ------ :program:`bpython` was written by Robert Anthony Farrell and his bunch of loyal followers. This manual page was written by Jørgen Pedersen Tjernø , for the Debian project (but may be used by others). bpython-0.17.1/doc/sphinx/source/index.rst0000644000175100017510000000072713240407731020471 0ustar useruser00000000000000bpython documentation ===================== Welcome to the bpython documentation files. This is where you can find all about using and customizing the bpython interpreter. If you want to find out more about the bpython interpreter you can visit our website: http://bpython-interpreter.org Contents: .. toctree:: :maxdepth: 2 authors contributing configuration themes releases community django windows changelog bpaste tips bpdb bpython-0.17.1/doc/sphinx/source/man-bpython.rst0000644000175100017510000000736113240407731021617 0ustar useruser00000000000000:orphan: bpython manual page =================== Synopsis -------- **bpython** [*options*] [*file* [*args*]] **bpython-curses** [*options*] [*file* [*args*]] **bpython-urwid** [*options*] [*file* [*args*]] Description ----------- 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. In-line syntax highlighting. Hilights commands as you type! Readline-like autocomplete with suggestions displayed as you type. Press tab to complete expressions when there's only one suggestion. Expected parameter list. This displays a list of parameters for any function you call. It uses the inspect module, then tries pydoc. Rewind. This is a bit misleading, but it code that has been entered is remembered, and when you Rewind, it pops the last line and re\-evaluates the entire code. This is error\-prone, and mostly useful for defining classes and functions. Pastebin code/write to file. This posts the current buffer to a pastebin (bpaste.net) or writes it to a file. Flush curses screen to stdout. Unlike other curses apps, bpython dumps the screen data to stdout when you quit, so you see what you've done in the buffer of your terminal. Options ------- The long and short forms of options, shown here as alternatives, are equivalent. If :program:`bpython` sees an argument it does not know, execution falls back to the regular Python interpreter. The following options are supported by all frontends: --config= Use instead of default config file. -h, --help Show the help message and exit. -i, --interactive Drop to bpython shell after running file instead of exiting. The PYTHONSTARTUP file is not read. -q, --quiet Do not flush the output to stdout. -V, --version Print :program:`bpython`'s version and exit. In addition to the above options, :program:`bpython` also supports the following options: -L, --log Write debugging messages to the file bpython.log. Use -LL for more verbose logging. -p file, --paste=file Paste in the contents of a file at startup. In addition to the common options, :program:`bpython-urwid` also supports the following options if Twisted is available: -r , --reactor= Use Twisted's instead of urwid's event loop. --help-reactors Display a list of available Twisted reactors. -p , --plugin= Execute a :program:`twistd` plugin. Use :program:`twistd` to get a list of available plugins. Use -- to pass options to the plugin. -s , --server= Run an eval server on port . This option forces the use of a Twisted reactor. Keys ---- :program:`bpython`'s keys are fully configurable. See http://docs.bpython-interpreter.org/configuration.html#keyboard Files ----- **$XDG_CONFIG_HOME/bpython/config** Your bpython config. See sample-config (in /usr/share/doc/bpython/examples on Debian) for various options you can use, or read :manpage:`bpython-config(5)`. Known bugs ---------- See http://github.com/bpython/bpython/issues/ for a list of known issues. See also -------- :manpage:`bpython-config(5)`, :manpage:`python(1)` Author ------ :program:`bpython` was written by Robert Anthony Farrell and his bunch of loyal followers. This manual page was written by Jørgen Pedersen Tjernø , for the Debian project (but may be used by others). bpython-0.17.1/doc/sphinx/source/tips.rst0000644000175100017510000000147613240407731020343 0ustar useruser00000000000000.. _tips: Tips and tricks =============== There are various tricks and tips to bpython. We currently list one of them on this page. If you know any more, don't hesitate to let us know (:ref:`community`)! bpython and multiple python versions ------------------------------------ To use bpython with multiple version items this trick has been relayed to us by Simon Liedtke. Do a source checkout of bpython and add the following to your `.profile` equivalent file. .. code-block:: bash alias bpython3.5='PYTHONPATH=~/python/bpython python3.5 -m bpython.cli' Where the `~/python/bpython`-path is the path to where your bpython source code resides. You can of course add multiple aliases, so you can run bpython with 2.7 and the 3 series. .. note:: Make sure you have the dependencies installed on all Python versions. bpython-0.17.1/doc/sphinx/source/authors.rst0000644000175100017510000000025213240407731021040 0ustar useruser00000000000000.. _authors: Authors ======= If you contributed to bpython and want to be on this list please find us (:ref:`community`) and let us know! .. include:: ../../../AUTHORS bpython-0.17.1/doc/sphinx/source/bpaste.rst0000644000175100017510000000050113240407731020626 0ustar useruser00000000000000.. _bpaste: bpaste ====== bpaste (http://bpaste.net) is the pastebin which we run for bpython. bpython is configured by default to paste to this pastebin. Removal ------- If you want a paste removed from the pastebin you can use the removal link as shown by bpython. Refer to https://bpaste.net/removal if you lost it. bpython-0.17.1/doc/sphinx/source/changelog.rst0000644000175100017510000006640013240410760021305 0ustar useruser00000000000000Changelog ========= 0.17.1 ------ Fixes: * Reverted #670 temporarily due to performance impact on large strings being output. 0.17 ---- New features: * #641: Implement Ctrl+O. * Add default_autoreload config option. Thanks to Alex Frieder. Fixes: * Fix deprecation warnings. * Do not call signal outside of main thread. Thanks to Max Nordlund. * Fix option-backspace behavior. Thanks to Alex Frieder. * #648: Fix paste helper. Thanks to Jakob Bowyer. * #653: Handle docstrings more carefully. * #654: Do not modify history file during tests. * #658: Fix newline handling. Thanks to Attila Szöllősi. * #670: Fix handlign of ANSI escape codes. Thanks to Attila Szöllősi. * #687: Fix encoding of jedi completions. 0.16 ---- New features: * #466: Improve handling of completion box height. Fixes: * Fix various spelling mistakes. Thanks to Josh Soref and Simeon Visser. * #601: Fix Python 2 issues on Windows. Thanks to Aditya Gupta. * #614: Fix issues when view source. Thanks to Daniel Hahler. * #625: Fix issues when runnings scripts with non-ASCII characters. * #639: Fix compatbility issues with pdb++. Thanks to Daniel Hahler. Support for Python 2.6 has been dropped. 0.15 ---- This release contains new features and plenty of bug fixes. New features: * #425: Added curtsies 0.2.x support. * #528: Hide private attribute from initial autocompletion suggestions. Thanks to Jeppe Toustrup. * #538: Multi-line banners are allowed. * #229: inspect.getsource works on interactively defined functions. Thanks to Michael Mulley. * Attribute completion works on literals and some expressions containing builtin objects. * Ctrl-e can be used to autocomplete current fish-style suggestion. Thanks to Amjith Ramanujam. Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. * #548 Fix transpose character bug. Thanks to Wes E. Vial. * #527 -q disables version banner. * #544 Fix Jedi completion error. * #536 Fix completion on old-style classes with custom __getattr__. * #480 Fix old-style class autocompletion. Thanks to Joe Jevnik. * #506 In python -i mod.py sys.modules[__name__] refers to module dict. * #590 Fix "None" not being displayed. * #546 Paste detection uses events instead of bytes returned in a single os.read call. * Exceptions in autocompletion are now logged instead of crashing bpython. * Fix reload in Python 3. Thanks to sharow. * Fix keyword argument parameter name completion. Changes to dependencies: * requests[security] has been changed to pyOpenSSL, pyasn1, and ndg-httpsclient. These dependencies are required before Python 2.7.7. 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 ------ Fixes: * #483: Fixed jedi exceptions handling. * #486: Fixed Python 3.3 compatibility. * #489: Create history file with mode 0600. * #491: Fix issues with file name completion. * #494: Fix six version requirement. * Fix conditional dependencies for SNI support in Python versions before 2.7.7. 0.14 ---- This release contains major changes to the frontends: * curtsies is the new default frontend. * The old curses frontend is available as bpython-curses. * The GTK+ frontend has been removed. New features: * #194: Syntax-highlighted tracebacks. Thanks to Miriam Lauter. * #234: Copy to system clipboard. * #285: Re-evaluate session and reimport modules. * #313: Warn when undo may take cause extended delay, and prompt to undo multiple lines. * #322: Watch imported modules for changes and re-evaluate on changes. * #328: bpython history not re-evaluated to edit a previous line of a multiline statement. * #334: readline command Meta-. for yank last argument. Thanks to Susan Steinman and Steph Samson. * #338: bpython help with F1. * #354: Edit config file from within bpython. * #382: Partial support for pasting in text with blank lines. * #410: Startup banner that shows Python and bpython version * #426: Experimental multiline autocompletion. * fish style last history completion with Arrow Right. Thanks to Nicholas Sweeting. * fish style automatic reverse history search with Arrow Up. Thanks to Nicholas Sweeting. * Incremental forward and reverse search. * All readline keys which kill/cut text correctly copy text for paste with Ctrl-y or Meta-y. * French translation. * Removal links for bpaste pastebins are now displayed. * More informative error messages when source cannot be found for an object. Thanks to Liudmila Nikolaeva and Miriam Lauter. * Message displayed if history in scrollback buffer is inconsistent with output from last re-evaluation of bpython session. Thanks to Susan Steinman. * Adjust logging level with -L or -LL. * String literal attribute completion. Fixes: * #254: Use ASCII characters if Unicode box characters are not supported by the terminal. * #284: __file__ is in scope after module run with bpython -i. Thanks to Lindsey Raymond. * #347: Fixed crash on unsafe autocompletion. * #349: Fixed writing newlines to stderr. * #363: Fixed banner crashing bpython-urwid. Thanks to Luca Barbato. * #366, #367: Fixed help() support in curtsies. * #369: Interactive sessions inherit compiler directives from files run with -i interactive flag. * #370, #401, #440, #448, #468, #472: Fixed various display issues in curtsies. * #391: Fixed crash when using Meta-backspace. Thanks to Tony Wang. * #438, #450: bpython-curtsies startup behavior fixed. Errors during startup are reported instead of crashing. * #447: Fixed behavior of duplicate keybindings. Thanks to Keyan Pishdadian. * #458: Fixed dictionary key completion crash in Python 2.6. Thanks to Mary Mokuolu. * Documentation fixes from Lindsey Raymond. * Fixed filename completion. * Fixed various Unicode issues in curtsies. * Fixed and re-enabled dictionary key completion in curtsies. The commandline option --type / -t has been renamed to --paste / -p. Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. Furthermore, it is no longer necessary to run 2to3 on the source code. This release brings a lot more code coverage, a new contributing guide, and most of the code now conforms to PEP-8. Changes to dependencies: * greenlet and curtsies are no longer optional. * six is a new dependency. * jedi is a new optional dependency required for multiline completion. * watchdog is a new optional dependency required for watching changes in imported modules. 0.13.2 ------- A bugfix release. The fixed bugs are: * #424: Use new JSON API at bpaste.net. * #430: Fixed SNI issues with new pastebin service on Mac OS X. * #432: Fixed crash in bpython-curtsies in special circumstances if history file is empty. Thanks to Lisa van Gelder. Changes to dependencies: * requests is a new dependency. * PyOpenSSL, ndg-httpsclient and pyasn1 are new dependencies on Mac OS X. 0.13.1 ------- A bugfix release. The fixed bugs are: * #287: Turned off dictionary completion in bpython-curtsies * #281: Fixed a crash on error-raising properties * #286: Fixed input in Python 3 * #293: Added encoding attribute to stdin bpython curtsies * #296: Fixed warnings in import completion for Python 3 * #290: Stop using root logger * #301: Specify curtsies version in requirements There's also a necessary regression: #232 (adding fileno() on stdin) is reintroduced because its previous fix was found to be the cause of #286 0.13 ---- There are a few new features, a bunch of bugfixes, and a new frontend for bpython in this release. * Dictionary key completion, thanks to Maja Frydrychowicz (#226). To use normal completion and ignore these key completions, type a space. * Edit current line in external editor: ctrl-x (#161) Fixes: * Python 2.5 compatibility, thanks to Michael Schuller (#279). Python 2.5 is not officially supported, but after few changes Michael introduced, he says it's working fine. * FakeStream has flush(), so works correctly with django.core.email.backends.console thanks to Marc Sibson (#259) * FakeStdin has fileno() (#232) * Changes to sys.ps1 and sys.ps2 are respected thanks to Michael Schulle (#267) * atexit registered functions run on exit (#258) * fixed an error on exit code when running a script with bpython script.py (#260) * setup.py extras are used to define dependencies for urwid and curtsies frontends There's a new frontend for bpython: bpython-curtsies. Curtsies is a terminal wrapper written to making native scrolling work in bpython. (#56, #245) Try bpython-curtsies for the bpython experience with a vanilla python layout. (demo: http://ballingt.com/assets/bpython-curtsies-scroll-demo-large.gif) This curtsies frontend addresses some issues unfixed in bpython-cli, and has a few extra features: * Editing full interpreter history in external editor with F7, which is rerun as in rewind * A new interpreter is used for rewind, unless bpython-curtsies was started with custom locals or in interactive mode (#71) * Ctrl-c behaves more like vanilla python (#177) * Completion still works if cursor at the end of the line (#147) * Movement keys meta-b, meta-f, and meta-backspace, ctrl-left and ctrl-right are all honored (#246, #201) * Non-ascii characters work in the file save prompt (#236) * New --type / -t option to run the contents of a file as though they were typed into the bpython-curtsies prompt A few things about bpython-curtsies are worse than regular bpython: * Bad things can happen when using several threads (#265) * output prints slowly (#262) * bpython-curtsies can't be backgrounded and resumed correctly (via ctrl-z, fg) (#274) There are two new options in the new [curtsies] section of the bpython config * list_above: whether completion window can cover text above the current line; defaults to True * fill_terminal: whether bpython-curtsies should be fullscreen (like bpython); defaults to False 0.12 ---- We want to give special thanks to the Hacker School project- (https://www.hackerschool.com/) for choosing bpython as their pet hacking project. In special we would like to thank the following people for contributing their code to bpython: - Martha Girdler - Allison Kaptur - Ingrid Cheung We'd also like to thank Eike Hein for contributing his pastebin code which now makes it possible to paste using a 3rd party program unlocking a whole slew of pastebins for bpython users. * Added a new pastebin_helper config option to name an executable that should perform pastebin upload on bpython's behalf. If set, this overrides pastebin_url. Data is supplied to the helper via STDIN, and it is expected to return a pastebin URL as the first word of its output. * Fixed a bug causing pastebin upload to fail after a previous attempt was unsuccessful. A duplicate pastebin error would be displayed in this case, despite the original upload having failed. * Added more key shortcuts to bpython.urwid * Smarter dedenting after certain expressions * #74 fixed broken completion when auto_display_list was disabled We also have done numerous cleanup actions including building the man pages from our documentation. Including the documentation in the source directory. Some minor changes to the README to have EOL 79 and changes to urwid to work better without twisted installed. * Fix ungetch issues with Python 3.3. See issues #230, #231. 0.11 ---- A bugfix/cleanup release .The fixed bugs are: * #204: "import math" not autocompleting on python 3.2 Otherwise lots of small additions to the to be replacement for our ncurses frontend, the urwid frontend. I'd like to specifically thank Amjith Ramanujam for his work on history search which was further implemented and is in working order right now. 0.10.1 ------ A bugfix release. The fixed bugs are: * #197: find_modules crashes on non-readable directories * #198: Source tarball lacks .po files 0.10 ---- As a highlight of the release, Michele Orrù added i18n support to bpython. Some issues have been resolved as well: * Config files are now located according to the XDG Base Directory Specification. The support for the old bpythonrc files has been dropped and ~/.bpython.ini as config file location is no longer supported. See issue #91. * Fixed some issues with tuple unpacking in argspec. See issues #133 and #138. * Fixed a crash with non-ascii filenames in import completion. See issue #139. * Fixed a crash caused by inspect.findsource() raising an IndexError which happens in some situations. See issue #94. * Non-ascii input should work now under Python 3. * Issue #165: C-a and C-e do the right thing now in urwid. * The short command-line option "-c config" was dropped as it conflicts with vanilla Python's "-c command" option. See issue #186. 0.9.7.1 ------- A bugfix release. The fixed bugs are: * #128: bpython-gtk is broken * #134: crash when using pastebin and no active internet connection 0.9.7 ----- Well guys. It's been some time since the latest release, six months have passed We have added a whole slew of new features, and closed a number of bugs as well. We also have a new frontend for bpython. Marien Zwart contributed a urwid frontend as an alternative for the curses frontend. Be aware that there still is a lot to fix for this urwid frontend (a lot of the keyboard shortcuts do not yet work for example) but please give it a good spin. Urwid also optionally integrates with a Twisted reactor and through that with things like the GTK event loop. At the same time we have done a lot of work on the GTK frontend. The GTK frontend is now 'usable'. Please give that a spin as well by running bpython-gtk on you system. We also welcome a new contributor in the name of Michele Orrù who we hope will help us fix even more bugs and improve functionality. As always, please submit any bugs you might find to our bugtracker. * Pastebin confirmation added; we were getting a lot of people accidentally pastebinning sensitive information so I think this is a good idea. * Don't read PYTHONSTARTUP when executed with -i. * BPDB was merged in. BPDB is an extension to PDB which allows you to press B in a PDB session which will let you be dropped into a bpython sessions with the current PDB locals(). For usage, see the documentation. * The clear word shortcut (default: C-w) now deletes to the buffer. * More tests have been added to bpython. * The pastebin now checks for a previous paste (during the session) with the exact same content to guard against twitchy fingers pastebinning multiple times. * Let import completion return "import " instead of "import". * GTK now has pastebin, both for full log as well as the current selection. * GTK now has write2file. * GTK now has a menu. * GTK now has a statusbar. * GTK now has show source functionality. * GTK saves the pastebin url to the clipboard. * GTK now has it's own configuration section. * Set focus to the GTK text widget to allow for easier embedding in PIDA and others which fixes issues #121. * #87: Add a closed attribute to Repl to fix mercurial.ui.ui expecting stderr to have this attribute. * #108: Unicode characters in docstring crash bpython * #118: Load_theme is not defined. * #99: Configurable font now documented. * #123: Pastebin can't handle 'ESC' key * #124: Unwanted input when using / keys in the statusbar prompt. 0.9.6.2 ------- Unfortunately another bugfix release as I (Bob) broke py3 support. * #84: bpython doesn't work with Python 3 Thanks very much to Henry Prêcheur for both the bug report and the patch. 0.9.6.1 ------- A quick bugfix release (this should not become a habit). * #82: Crash on saving file. 0.9.6 ------ A bugfix/feature release (and a start at gtk). Happy Christmas everyone! * #67: Make pastebin URL really configurable. * #68: Set a __main__ module and set interpreter's namespace to that module. * #70: Implement backward completion on backward tab. * #62: Hide matches starting with a _ unless explicitly typed. * #72: Auto dedentation * #78: Theme without a certain value raises exception - add the possibility for a banner to be shown on bpython startup (when embedded or in code) written by Caio Romao. - add a hack to add a write() method to our fake stdin object - Don't use curses interface when stdout is not attached to a terminal. - PEP-8 conformance. - Only restore indentation when inside a block. - Do not decrease the lineno in tracebacks for Py3 - Do not add internal code to history. - Make paren highlighting more accurate. - Catch SyntaxError in import completion. - Remove globals for configuration. - rl_history now stays the same, also after undo. 0.9.5.2 ------- A bugfix release. Fixed issues: * #60: Filename expansion: Cycling completions and deleting * #61: Filename expansion: Directory names with '.'s get mangled Other fixes without opened issues: * Encode items in the suggestion list properly * Expand usernames in file completion correctly * future imports in startup scripts can influence interpreter's behaviour now * Show the correct docstring for types without a own __init__ method 0.9.5.1 -------- Added missing data files to the tarball. 0.9.5 ----- Fixed issues: * #25 Problems with DEL, Backspace and C-u over multiple lines * #49 Sending last output to $PAGER * #51 Ability to embed bpython shell into an existing script * #52 FakeStdin.readlines() is broken * #53 Error on printing null character * #54 Parsing/introspection ncurses viewer neglects parenthesis bpython has added a view source shortcut to show the source of the current function. The history file is now really configurable. This issue was reported in Debian's bugtracker. bpython has now some basic support for Python 3 (requires Pygments >=1.1.1). As a result, setuptools is now optional. The pastebin URL is now configurable and the default pastebin is now bpaste.net Argument names are now shown as completion suggestions and one can tab through the completion list. 0.9.4 ----- Bugfix release (mostly) * when typing a float literal bpython autocompletes int methods (#36) * Autocompletion for file names (#40) * Indenting doesn't reset (#27) * bpython configuration has moved from ~/.bpython.ini to ~/.bpython/config (currently still supporting fallback) * leftovers of statusbar when exiting bpython cleaned up * bpython now does not crash when a 'popup' goes out of window bounds * numerous fixes and improvements to parentheses highlighting * made *all* keys configurable (except for arrow keys/pgup/pgdown) 0.9.3 ------ This release was a true whopper! * Full unicode support * Configurable hotkey support * Theming support * Pastemode, disables syntax highlighting during a paste for faster pasting, highlights when done * Parentheses matching * Argument highlighting 0.9.2 ----- * help() now uses an external pager if available. * Fix for highlighting prefixed strings. * Fix to reset string highlighting after a SyntaxError. * bpython now uses optparse for option parsing and it supports --version now. * Configuration files are no longer passed by the first command line argument but by the -c command line switch. * Fix for problem related to editing lines in the history: http://bitbucket.org/bobf/bpython/issue/10/odd-behaviour-when-editing-commands-in-the-history 0.9.1 ----- * Fixed a small but annoying bug with sys.argv ini file passing * Fix for Python 2.6 to monkeypatch they way it detects callables in rlcompleter * Config file conversion fix 0.9.0 ----- * Module import completion added. * Changed to paste.pocoo.org due to rafb.net no longer offering a pastebin service. * Switched to .ini file format for config file. * White background-friendly colour scheme added. * C-l now clears the screen. * SyntaxError now correctly added to history to prevent it garbling up on a redraw. Probably some other things, but I hate changelogs. :) 0.8.0 ------ It's been a long while since the last release and there have been numerous little bugfixes and extras here and there so I'm putting this out as 0.8.0. Check the hg commit history if you want more info: http://bitbucket.org/bobf/bpython/ 0.7.2 ----- Menno sent me some patches to fix some stuff: * Socket error handled when submitting to a pastebin. * Resizing could crash if you resize small enough. Other stuff: * 'self' in arg list is now highlighted a different colour. * flush_output option added to config to control whether output is flushed to stdout or not on exit. * Piping something to bpython made it lock up as stdin was not the keyboard - bpython just executes stdin and exits instead of trying to do something clever. * Mark Florisson (eggy) gave me a patch that stops weird breakage when unicode objects get added into the output buffer - they now get encoded into the output encoding. * Bohdan Vlasyuk sent me a patch that fixes a problem with the above patch from Mark if sys.__stdout__.encoding didn't exist. * Save to file now outputs executable code (i.e. without the >>> and ... and with "# OUT: " prepended to all output lines). I never used this feature much but someone asked for this behaviour. 0.7.1 ----- * Added support for a history file, defaults to ~/.pythonhist and 100 lines but is configurable from the rc file (see sample-rc). * Charles Duffy has added a yank/put thing - C-k and C-y. He also ran the code through some PEP-8 checker thing and fixed up a few old habits I manage to break but didn't manage to fix the code to reflect this - thank you! * Jørgen Tjernø has fixed up the autoindentation issues we encountered when bringing soft tabs in. * SyntaxError, ValueError and OverflowError are now caught properly (code.InteractiveInterpreter treats these as different to other exceptions as it doesn't print the whole traceback, so a different handler is called). This was discovered as I was trying to stop autoindentation from occurring on a SyntaxError, which has also been fixed. * '.' now in sys.path on startup. 0.7.0 ----- C-d behaviour changed so it no longer exits if the current line isn't empty. Extra linebreak added to end of stdout flush. pygments and pyparsing are now dependencies. Jørgen Tjernø has done lots of cool things like write a manpage and .desktop file and improved the way tabbing works and also added home, end and del key handling as well as C-w for deleting words - thanks a lot! raw_input() and all its friends now work fine. PYTHONSTARTUP handled without blowing up on stupid errors (it now parses the file at once instead of feeding it to the repl line-by-line). 0.6.4 ----- KeyboardInterrupt handler clears the list window properly now. 0.6.3 ----- Forgot to switch rpartition to split for 2.4 compat. 0.6.2 ----- The help() now works (as far as I can see) exactly the same as the vanilla help() in the regular interpreter. I copied some code from pydoc.py to make it handle the special cases, e.g. help('keywords') help('modules') etc. 0.6.1 ----- Somehow it escaped my attention that the list window was never fully using the rightmost column, except for the first row. This is because me and numbers don't have the best relationship. I think stability is really improving with the latest spat of bugfixes, keep me informed of any bugs. 0.6.0 ----- No noticeable changes except that bpython should now work with Python 2.4. Personally I think it's silly to make a development tool work with an out of date version of Python but some people seem to disagree. The only real downside is that I had to do a horrible version of all() using reduce(), otherwise there's no real differences in the code. 0.5.3 ----- Now you can configure a ~/.bpythonrc file (or pass a rc file at the command line (bpython /foo/bar). See README for details. 0.5.2 ----- help() actually displays the full help page, and I fixed up the ghetto pager a little. 0.5.1 ----- Now you can hit tab to display the autocomplete list, rather than have it pop up automatically as you type which, apparently, annoys Brendogg. 0.5.0 ----- A few people have commented that the help() built-in function doesn't work so well with bpython, since Python will try to output the help string to PAGER (usually "less") which obviously makes everything go wrong when curses is involved. With a bit of hackery I've written my own ghetto pager and injected my own help function into the interpreter when it initialises in an attempt to rectify this. As such, it's pretty untested but it seems to be working okay for me. Suggestions/bug reports/patches are welcome regarding this. 0.4.2 ----- Well, hopefully we're one step closer to making the list sizing stuff work. I really hate doing code for that kind of thing as I never get it quite right, but with perseverence it should end up being completely stable; it's not the hardest thing in the world. Various cosmetic fixes have been put in at the request of a bunch of people who were kind enough to send me emails regarding their experiences. PYTHONSTARTUP is now dealt with and used properly, as per the vanilla interpreter. 0.4.1 ----- It looks like the last release was actually pretty bug-free, aside from one tiny bug that NEVER ACTUALLY HAPPENS but someone was bugging me about it anyway, oh well. 0.4.0 ----- It's been quite a long time since the last update, due to several uninteresting and invalid excuses, but I finally reworked the list drawing procedures so the crashing seems to have been taken care of to an extent. If it still crashes, the way I've written it will hopefully allow a much more robust way of fixing it, one that might actually work. 0.3.2 ----- Thanks to Aaron Gallagher for pointing out a case where the hugely inefficient list generation routines were actually making a significant issue; they're much more efficient now and should hopefully not cause any more problems. 0.3.1 ----- Thanks to Klaus Alexander Seis for the expanduser() patch. Auto indent works on multiple levels now. 0.3.0 ----- Now with auto-indent. Let me know if it's annoying. 0.2.4 ----- Thanks a lot to Angus Gibson for submitting a patch to fix a problem I was having with initialising the keyboard stuff in curses properly. Also a big thanks to John Beisley for providing the patch that shows a class __init__ method's argspec on class instantiation. I've fixed up the argspec display so it handles really long argspecs (e.g. subprocess.Popen()) and doesn't crash if something horrible happens (rather, it avoids letting something horrible happen). I decided to add a key that will get rid of the autocomplete window, since it can get in the way. C-l seemed like a good choice, since it would work well as a side-effect of redrawing the screen (at least that makes sense to me). In so doing I also cleaned up a lot of the reevaluating and resizing code so that a lot of the strange output seen on Rewind/resize seems to be gone. 0.2.3 ----- The fix for the last bug broke the positioning of the autocomplete box, whoops. 0.2.2 ----- That pesky bug keeps coming up. I think it's finally nailed but it's just a matter of testing and hoping. I hate numbers. 0.2.1 ----- I'm having a bit of trouble with some integer division that's causing trouble when a certain set of circumstances arise, and I think I've taken care of that little bug, since it's a real pain in the ass and only creeps up when I'm actually doing something useful, so I'll test it for a bit and release it as hopefully a bug fixed version. 0.2.0 ----- A little late in the day to start a changelog, but here goes... This version fixed another annoying little bug that was causing crashes given certain exact circumstances. I always find it's the way with curses and sizing of windows and things... I've also got bpython to try looking into pydoc if no matches are found for the argspec, which means the builtins have argspecs too now, hooray. bpython-0.17.1/doc/sphinx/source/logo.png0000644000175100017510000004534413240407731020302 0ustar useruser00000000000000PNG  IHDRXtEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp GXIDATx} WuU3ZG%Yd[^066^Y[$0’H{!!!>L30`+&/,kK{z{_ZnݺuH=3]vkr"@!}Hp |܅Dl{́% J#$x fmr^^c6zQgcr ĽBĮ$@۰`%XP`o"x=EhH$3Bί"$i9ɀ$eɗ2*gl~x#74[MwR$RjeR/_*O"BtCj5:b]jfg'ۀ`9-Y:j gm0(+,"پ^%; )^IR I{64?|X,Se J5A6>k08OtX/6h`\1Q6U&x.' 0ueH9 1|E湔L*S}Ƿ?*5HJUM:<p'8ܿ05W0{Gmr GD:I]GR҆ϒ}_wښd8z'am6p)O uMҠ3X.^Xa 0TJ#/|qt3/f dp pnϿ_z9Ӭ==!>:4l\7?W2ekG`,' R χre(k5& ĚͰ"m(X➇9s:!m[\ ]o*]^pL.*EjQ՛oժ|Qn6p8+5PkĽG/F0~pe'A$DNr:\5=>Et`@c@Z}Uo/]i1ZHG{ɢ\wd3AvB=OM n3軈yl:ABkS泎 +06c%mdo/>sNT:ߛ,] deAĺ:"=ןZT?|I~HYag_5FXE~1/+PU~n?c4<;cGfl6YvJmy'?@39e}ww@ppN9 %5%sfV(s'޵[^[XtltEo3c;4洓N;BDއo6cd:>bG</:$ݿ`v\$' sZw>\Jp)+Ȅx +TⰬY:_:>ѽ1S6@:|21Na0n/%0a^^t@2X9}K,si#$hVHX3G"  X0Pe/&dΣGlsd2`W+(!)$Rgi5`"s >9StFI27}S=~ rmf?T̡^:Yثn !afಽCЙ>n! Bם`M:s7w~>6h={:<zeؘW͛b*iZ^W+) Kk P.IDhJ 4p" ;bc2MɃPeֹ[szMEIIy'}@_x9?!$87›\! taW9I-xzݜ4PHJ* ~Z2L,:W[+"IvHZqT~`@N0^{x_Z@K_[, l̿yG{3 b ٟK"x4˘I{BoĉFWm^+^g?0tֿߖ?,:$uzIR@LF;cj3eY~BR8A]d v|dθdٙo|lACImf?&$ kw&K+Mۖv)Uȓ^N9\g\w\Ӻaܨ0g $e7~w?d8ryrd_~px(;j`.|DSsd_=eDLMEM>|<(+ח›,+I%"E`&_g[ 23.RMQpjs}x(z֝eiĒe>ek.@,̩$sT=WfvZeV-S{Jҹ\0D#wZԪ :q2},0~o|x :܀QNA=tM5HP`- '()OqYpEd^+q c@ڋO:눃 }le&rpBF^//F@LɌ &EAL-"8a f gA$D[o8;(3 "s{R9@Asr$!5$@j,ID uS]F;e>kx鲗^G꽠*ANjDX%'#T2&F!$|IA:`/N:q4U5BpV.hEm{2j{urv? zICwH{--QHgpi D0 1'I9{SJe,"1bxv[  ے%p:R9dbI9AdygXcD@@V7m<)._с,>~ @ЬT r3HMÞoLa"d14V?B!mX8>7 tSvuwOY s\IpG\VE {u?i ,q6iVN@XʸYA՝e|:A/řZ< /\k%wX[L:eFUaNf;axp(g^BAL ϛ;(G)B5RE$8zu.# 癶V9leZDc4{B;L[05 evbfd!O#@M8t$1/#m1ccC)*5iA0xHظ\ 茶8Yb!_ g1;D# uL>DKϼU\;ZZ: K5$_ IX1a^ ThllzkͮL,^!ybZmתZ,8r^)OcOkyVy0kֱ!@(-AS{P rYw]W 뻖V2NWX]v}m+-5BLGܽ{o/^W*]Չc?m6}tJ]y&} 9Y=&+N=n66q;1r*e5AP$?̋t's X8=A D(Բͧ X~hdynQj }Y𚤅pfPœ^'޹;QH&m@w0YGfu]`N6 #u8޺a4 IR-oZw%#4S?n0lM b VbY(!)8Hi!#{ r jtV.g势Ot A@To׼/->73^ D,,x";p~E{wf>K{U#Q$$8/YĈ/\?NL~+?.?eɩC %sz%:J"z$"Y8f2KԻ^*A6XʅЬ\A`C$$}DdtA! -kU^uy|ioIMrm`Aʂl IX0b {!+E|Cy!ǘK ʘ&fΩ m+,;ݟsB\/ 鱕6frADZ- 1y %b i*IuHC{۹MW~c~7fVtҴS`n wl`͚*Ͷһ#u Њw932ig)2I1%Z[~.1m.H c 3B҃b B $Kc$OťnTھ%vIʜv?8t٧Kގ%l 3u (WQHca 3(݉3݃KOC-^^bn,$0O$Ep_k" # vAi' :$܉v{je_%^p <( iN\ϱ\x+y'$!_BOWL!1 B 7b=~q 35t V]p%Br 9 &+&!k/1QĕMLX[~XSͶ&yt }+75vf NWYHzPLGxBD,kD"|xNeD9$i']75/8l@z޸UN{XQX$H<z>1wUþD;i'aN{x.!4QaFc+`Zĵi1 Q,yRSOlK= Dp3~6#A5-!&!$nIƐt{] nE^f?o&=HĊ @bij  wM ӆ+R'S}6V^dL+s~iYmK5$C[z܊O 1+@ɫ"M K0vI ?AEy)5qRRr~rp_+L$kA8 (ThhUjD%uH2lyDb3WcoL5Hx){.H [he#\]7R:Щ N?jk_]y5 @KzR AF-[Pʅ; u_Lc}<96 <:7 e9َZ;҇ݓh?gL1-_OiLߢS W.޸1?4R DGo#S5dgJ-hȜ"-#o3|%f0,%qc=fNQ)[#Y=(Jm=X ܴJR2U!YlGajĒxd5 :Fig瞅 7x&&I9˺#(0EPUZej#?9z_fwwtBS!5|E2*Ir12̊jṾxһ 9qאKZ $#u=/jQ2 -%O uqkڡ训6_[ᵯ!j?"v7}@2M,V=k5,s+:iB,!Zl ji{3&g)Cdu?p{4M@ĕmyPW3yԩcM7፛$17CB)9ctE6<$IJT$ $t\ŐhkZEհKIv,l sڣ̆:g[z Jy3l8u]dR))EҊd%$SB0CjR$lnֻ8Ґ(d@Cq}J?=ư`?/jՅ$bRBcUMkUM4Tũii~Yä]H$2No` yvM0{p㻖 /Җ/)KԁՕƹBRd>٦!xL&<"$K9\Xv639U+<{ܿߡ:Zky*3aF"86!g6E gk7gA1IH]PðD‹`B`k:T LYb &Ƨ=ѩs!M;__c\'bR!q|![8/뇥 044)H+G$A] a#b|p>8ߢWw➽>ߵF3]  c4ҁP'$A&iLMV4R5wdǖZlNN`h/˰<[d"aU,vB5Nc7Wn+؜L ,+nؖNY攓~dž~|M;ݷ~tQ`( m>&I T%R {$ul)~P{J6*z%Hd$^A!L.KPP&)?Z@!8ݟ10xZ@G0`ڐUZ{}޷?<-V>[<+ ,sOTaѯeN|-v[Hz,RPpLӲ *dqkE֟x`! Al_|imI#bjc]̛rW{>lQ~\QK-6yCB} &@4ok8P P8!U؉Kz'U9u$NTC\7@єkq;7-dk0+[tS|V 5uLBb?&Ry@cϖeLfL-tCOڋ587.;XP-3ZfbY iϜ,adșCd@0jAD0!{˫ՐS$>8DD% V/e.?I/:&LlStߍÎ>۳ 4ʊS# 1IVJa}u`t G H(}I?uҦ 8+9'J!AlIǜ$4%1X%aϐx!ـ@>Oub' \{/\*:lڿ~{ҋlqeZIB%昙UqeO|/II EiD؃"l^K\у^<|wU7gj52׸v1.|@9A!؉HEϐXf,هkE |3NykXotI*%3~ :i6/ E3BtA/%!@RB ϷCxNyvQ1洁C/>aʤv c nsYX%C`-"N01fϗ IB7p58۟No"wج;%<.QO1s Y>Hk!Aѩ(Tpm棈##' O^iQUH!s:SbO[I9mb!bv2ɢr|eXD9q>HlDҐ3tw &C`JT,7v{79ZAin; evJglAq^7?SLWKn8c̸A<,Y3Fl`$lO)ښMOvdmꖿTJEM+3ڏ8#lmWG}h14`zv9tɋO-V *>J0$̵glL3uI{(w~w!#%LR{[==f}5= KLr׿}I#LAL ~-bye/ǞeqϾ'*3iBFsbYn "4+i!ӶAm\_ h@ƞKrK/&O$"c5jȡ-1~]KO>?]w9`QmZ;!vA׆Ldh\oIm׺RXYW-cGa@²aWy *3EзnLITHbV\09,ȒHlr&},s޶>[I%߳lqsHe{rXRH H@О~Iڨ>Du6Yۯd%8#on3!ҹlj)0"F3YXӎ 5MdW5E<3۷dp][SjGH U+(MC;&{/;r} )sNI,LX#@0r0^9WŞD4GaC75% H޵wz&6Q`λvM5P48H 0H[ik3B+/mY><*Ao*7 #ԲҪj8Q 96!鴌BhB"ZBX ym0a7``\G8=ΌdRbF~ PL6+ɋӲ.TcDXsDA R!vt0aYV>,C 2?KP+Wj3E&zCc&`v%B$&NjOpX;Z?f$ț/Ei^ՎxD(g#$%3#{zJ4tRk 8I)HQn. %AD D$)@Ċ r%yٜ>{ojʼnfVY BYRUڍtݳ1Ϊ*Vr '%`^cNqf!|MLeI±~_?e!(37)xùqr"O .UwptAs R!6( I +ΌW?Ze ܂'r21!S4+r$LI$Q0CGD? Q.Z?~6(2&VG%$ڙ;Q*X*Nk>0 rEBģQHLv3j{=/NˁŚWIR߅O[2$SP  xbRl$\aӉI5!#|a p}>UީAuo2=VY8 yլD1Ԗ86 NHdT*>2^+_W4:Ϥn9&(G\%Ӎn#%MIWwfg-8Mlݸ%*98Z*Uժ^rғ6"V򮉽/µʴ-e  cRUZ#0mIݫ>´)'1nYUǕOZZI!& 3t'"ٮ/leD5,)Hbs*aD,Y,I'O: 3LȍqϽ|u.eفJ( ZcL[P=}|D.$I`3ǐۂCMĩ,% IQ6%0h1Zezf\ؕmÛ?|jS&rTq$Dv0MD&Pϼ=ןa(5 {,%+j6(@aದk4pu4lܔu;ԣ|F9~ f Y6OA2PXPg#qݮ d>ghǡ#f>~=]G:h3>qw燣ڭ+##q&f2nGjM}X\oso5otj%PyU4xtJ<EۡWuf 5ɬVJd ܶgfˆSщPz4c!"8>͈mK|C=o *V Qqo[ӱ®rfl^1jKeZ#s)~ Ǯ|y_EmbZSu\ti:/MP6%5,!O!IJyM;؏ Z;[f5-0d$Nqq^"u䳩ܛJe̩P5SSB 4Nt2 q(u", {K-IOGD e%%acK{66!瞟M$Jbjbj Wv1Pę1T$b.=Fc.p\@&1FM5ڡޒ>WC 7s "al^9Tj ,uhi!h "IpIb|$o CCc*9# UDBAag,[whH LXfSNyTO>U#gƏi,dg%pytHk+޽4v[Q1{+zٰDm07x Gٰ Xy>ɽZw4yoiz苕>±mҔbLb6%I3#~p@=/4@ 4H(8(a;iS-s^ s6ܳ*4M,>H#( J|B,A0[f3m?;JCN2OW( ' -6锄B͟fb[Xj$ex#/OڢD8zmqӝ4`EWv!J2{=#>?=M Nt O%O("Sޢ>d`Տ~m}q!vヴ'R#3 ;r[OP@(:M(K_|g"ƞ'k2u$FXߑ$j.(dYc|.IOVMQJhO~;rˮ;mp(X* 1?JN9sS EImaVsARЫ3~|O?.A;;2W$tRl0wxe9U`O{k^<9Zg$x;>~3;l 3(rѫ;ihj-_H&++.1H֊tH\j$ Gǿ;~ym{(p!»{31C  fLiT}<%ٴ$3 FbkFXylutoiwz⯿cpysqyi³AӒ$~?Y7nhd3)90̛4*V*LdEU;=}W6 PD^N~Hp& X1hCLƐ"jr`İU(BPpɒ&%$Կ>\ٶkj{OomO_54hMuX 4:Q৻l639]%Ӫ:Q ,QD2^%ds=@(2=̼yD|oze5EE-9!O xOZ|x^\.j3ӵ xefcc[g4NaۖMgoX! V8 $wt\7eV3 XEyc۔Yod&{aditneI*r- Dp|6mx\i1KJ y 8*=XGg&ƙ ; h=AI~ ‚˘ 0Di_^EIҥwӘmqxw& }|&% ̡"Wή.,Irְݻ ްQհﵺ_͘U֩!-ԁlaœ_MOJM:kL/ Z<$;7Bh.%{ߑ>K -(| PD(\jժ>mx,#]eFT.!ev [ǰ ,D-gXF&3 1Sb:i߿c~8ѬQ vz@gЛJ]O.sye> YЉJxS*6>/$s6f В+>̰ij-=!BH=9k:=vnu>8(hD$InB4pU-E,3agMu6SyEDI$+|h[vPRUq>02nH#' q5Gbqگʎ3AkyH ՀS>[&MCwȼNNQ$|b%j)Ow$ >HgW K k_wˢF\ߋkX7 h ȼ3H;1u< XZRj0@'2h> ȂfI@jH85V,ݐ3c,'_r|!.8 oqrz9&;|/ib%!1CbrOP6+C=uף{ #W}Xo}zhşo/t)f͠k0 &Ai!/|>H-a[=pG~=g&݈UL[/f$3gSҠHP!҂SMdX$0S+7j85=fǸu_o;>pĸ768iv2w1%" LPsཱfr|ػZ5RbO}[Պ+1k|Rl=5䴷.5t 8 V"mV!D0v Ƕ'Kx., jԴVoBb 2"W @+{#RIrpʓblG춵y &Pvy|D)曳}h=~EFAbIsO~qʼְqzrzT?YCIOEB@cЅ$G9$(`@@e.]װ4Pz]|oԼz\o :5NBU?dłluMl' %#8iea%nurUP٬<m T nbҁ;s*)o#T5 =s`E.Zkϴ*Sp(>_:]lʚ%i@yezu;,M/AfSx<^sp۱ۥ\C09.e`,oW]xeo t+LDxuZ%WڤAx%=_9葝@FNP;]f-c6*%˶< .{;C5WKE)#g9`7O9r:8:4$ݗ:hg|vo|^wIEhވIbե]/n3wW*ኊzR,/5*+pz4Sï*xЈqReh\ϣ?{w֬;lF _'d'O;&ju\8{$ k8{n54rx: v3`ljqUvt<󅛟7R:f4tГգܲe`9if4 j8Igode=8vښCԴf^iYG{/nyf*:wdٸ>N5QC5¹H!?X*} gvajڧw߹6IEM8FV8Βk}c[w4a;ayEӵů!Q%N"@᎟q5e yE?;xOwf[2GI ⑟ͣ'⛇OFBFjv_?ķk*+u~HҶ̡a= h;?xW}#QK k)ooB%.* [LhGm1hyLX\a^a^@:LCt2-Rẫ׽3971tz/g[ Q8I {hduϭx2sB gnDf@R= WN:ם2 0 ]K:|sd~Oȷ~1[KL1kC*y: q]>'_$+(w r_˗,/lͼ6]jD'p"Nk@0_iRhmW[Q+N֦tTvP5lb xP8s0H@ad(NYS̫̭ӊl",eq**K4c HRg邱IN iZOk:&5&ĶSvBl-m]9_ct&b@‚_Y(\Č"F@R@$l--j5n[TB2o<:ټ >t꼝~6pF4a6X`Ɓֹz+y1, ߫18A8 q^yEFi=0.1.18 greenlet six>=1.5 [:python_full_version == "2.7.0" or python_full_version == "2.7.1" or python_full_version == "2.7.2" or python_full_version == "2.7.3" or python_full_version == "2.7.4" or python_full_version == "2.7.5" or python_full_version == "2.7.6"] pyOpenSSL pyasn1 ndg-httpsclient [jedi] jedi [urwid] urwid [watch] watchdog bpython-0.17.1/bpython.egg-info/PKG-INFO0000644000175100017510000000064313240411345017515 0ustar useruser00000000000000Metadata-Version: 1.0 Name: bpython Version: 0.17.1 Summary: Fancy Interface to the Python Interpreter Home-page: http://www.bpython-interpreter.org/ Author: Bob Farrell, Andreas Stuehrk et al. Author-email: robertanthonyfarrell@gmail.com License: MIT/X Description-Content-Type: UNKNOWN Description: bpython is a fancy interface to the Python interpreter for Unix-like operating systems. Platform: UNKNOWN bpython-0.17.1/bpython/0000755000175100017510000000000013240411345014723 5ustar useruser00000000000000bpython-0.17.1/bpython/_version.py0000644000175100017510000000007313240411345017121 0ustar useruser00000000000000# Auto-generated file, do not edit! __version__ = '0.17.1' bpython-0.17.1/bpython/repl.py0000644000175100017510000013405213240410473016245 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2009-2011 the bpython authors. # Copyright (c) 2012-2013,2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import code import inspect import io import os import pkgutil import pydoc import re import shlex import subprocess import sys import tempfile import textwrap import time import traceback from itertools import takewhile from six import itervalues from types import ModuleType from pygments.token import Token from . import autocomplete from . import inspection from ._py3compat import PythonLexer, py3, prepare_for_exec from .clipboard import get_clipboard, CopyFailed from .config import getpreferredencoding from .formatter import Parenthesis from .history import History from .lazyre import LazyReCompile from .paste import PasteHelper, PastePinnwand, PasteFailed from .patch_linecache import filename_for_console_input from .translations import _, ngettext from . import simpleeval class RuntimeTimer(object): """Calculate running time""" def __init__(self): self.reset_timer() self.time = time.monotonic if hasattr(time, 'monotonic') else time.time def __enter__(self): self.start = self.time() def __exit__(self, ty, val, tb): self.last_command = self.time() - self.start self.running_time += self.last_command return False def reset_timer(self): self.running_time = 0.0 self.last_command = 0.0 def estimate(self): return self.running_time - self.last_command class Interpreter(code.InteractiveInterpreter): """Source code interpreter for use in bpython.""" bpython_input_re = LazyReCompile(r'') def __init__(self, locals=None, encoding=None): """Constructor. The optional 'locals' argument specifies the dictionary in which code will be executed; it defaults to a newly created dictionary with key "__name__" set to "__main__". The syntaxerror callback can be set at any time and will be called on a caught syntax error. The purpose for this in bpython is so that the repl can be instantiated after the interpreter (which it necessarily must be with the current factoring) and then an exception callback can be added to the Interpreter instance afterwards - more specifically, this is so that autoindentation does not occur after a traceback. encoding is only used in Python 2, where it may be necessary to add an encoding comment to a source bytestring before running it. encoding must be a bytestring in Python 2 because it will be templated into a bytestring source as part of an encoding comment. """ self.encoding = encoding or getpreferredencoding() self.syntaxerror_callback = None if locals is None: # instead of messing with sys.modules, we should modify sys.modules # in the interpreter instance sys.modules['__main__'] = main_mod = ModuleType('__main__') locals = main_mod.__dict__ # Unfortunately code.InteractiveInterpreter is a classic class, so no # super() code.InteractiveInterpreter.__init__(self, locals) self.timer = RuntimeTimer() def reset_running_time(self): self.running_time = 0 def runsource(self, source, filename=None, symbol='single', encode='auto'): """Execute Python code. source, filename and symbol are passed on to code.InteractiveInterpreter.runsource. If encode is True, an encoding comment will be added to the source. On Python 3.X, encode will be ignored. encode should only be used for interactive interpreter input, files should always already have an encoding comment or be ASCII. By default an encoding line will be added if no filename is given. In Python 3, source must be a unicode string In Python 2, source may be latin-1 bytestring or unicode string, following the interface of code.InteractiveInterpreter. Because adding an encoding comment to a unicode string in Python 2 would cause a syntax error to be thrown which would reference code the user did not write, setting encoding to True when source is a unicode string in Python 2 will throw a ValueError.""" # str means bytestring in Py2 if encode and not py3 and isinstance(source, unicode): if encode != 'auto': raise ValueError("can't add encoding line to unicode input") encode = False if encode and filename is not None: # files have encoding comments or implicit encoding of ASCII if encode != 'auto': raise ValueError("shouldn't add encoding line to file contents") encode = False if encode and not py3 and isinstance(source, str): # encoding makes sense for bytestrings, so long as there # isn't already an encoding comment comment = inspection.get_encoding_comment(source) if comment: # keep the existing encoding comment, but add two lines # because this interp always adds 2 to stack trace line # numbers in Python 2 source = source.replace(comment, b'%s\n\n' % comment, 1) else: source = b'# coding: %s\n\n%s' % (self.encoding, source) elif not py3 and filename is None: # 2 blank lines still need to be added # because this interpreter always adds 2 to stack trace line # numbers in Python 2 when the filename is "" newlines = u'\n\n' if isinstance(source, unicode) else b'\n\n' source = newlines + source # we know we're in Python 2 here, so ok to reference unicode if filename is None: filename = filename_for_console_input(source) with self.timer: return code.InteractiveInterpreter.runsource(self, source, filename, symbol) def showsyntaxerror(self, filename=None): """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called and the text in a pretty colour.""" if self.syntaxerror_callback is not None: self.syntaxerror_callback() exc_type, value, sys.last_traceback = sys.exc_info() sys.last_type = exc_type sys.last_value = value if filename and exc_type is SyntaxError: # Work hard to stuff the correct filename in the exception try: msg, (dummy_filename, lineno, offset, line) = value.args except: # Not the format we expect; leave it alone pass else: # Stuff in the right filename and right lineno # strip linecache line number if self.bpython_input_re.match(filename): filename = '' if filename == '' and not py3: lineno -= 2 value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value exc_formatted = traceback.format_exception_only(exc_type, value) self.writetb(exc_formatted) def showtraceback(self): """This needs to override the default traceback thing so it can put it into a pretty colour and maybe other stuff, I don't know""" try: t, v, tb = sys.exc_info() sys.last_type = t sys.last_value = v sys.last_traceback = tb tblist = traceback.extract_tb(tb) del tblist[:1] for i, (fname, lineno, module, something) in enumerate(tblist): # strip linecache line number if self.bpython_input_re.match(fname): fname = '' tblist[i] = (fname, lineno, module, something) # Set the right lineno (encoding header adds an extra line) if fname == '' and not py3: tblist[i] = (fname, lineno - 2, module, something) l = traceback.format_list(tblist) if l: l.insert(0, "Traceback (most recent call last):\n") l[len(l):] = traceback.format_exception_only(t, v) finally: tblist = tb = None self.writetb(l) def writetb(self, lines): """This outputs the traceback and should be overridden for anything fancy.""" for line in lines: self.write(line) class MatchesIterator(object): """Stores a list of matches and which one is currently selected if any. Also responsible for doing the actual replacement of the original line with the selected match. A MatchesIterator can be `clear`ed to reset match iteration, and `update`ed to set what matches will be iterated over.""" def __init__(self): # word being replaced in the original line of text self.current_word = '' # possible replacements for current_word self.matches = None # which word is currently replacing the current word self.index = -1 # cursor position in the original line self.orig_cursor_offset = None # original line (before match replacements) self.orig_line = None # class describing the current type of completion self.completer = None def __nonzero__(self): """MatchesIterator is False when word hasn't been replaced yet""" return self.index != -1 def __bool__(self): return self.index != -1 @property def candidate_selected(self): """True when word selected/replaced, False when word hasn't been replaced yet""" return bool(self) def __iter__(self): return self def current(self): if self.index == -1: raise ValueError('No current match.') return self.matches[self.index] def next(self): return self.__next__() def __next__(self): self.index = (self.index + 1) % len(self.matches) return self.matches[self.index] def previous(self): if self.index <= 0: self.index = len(self.matches) self.index -= 1 return self.matches[self.index] def cur_line(self): """Returns a cursor offset and line with the current substitution made""" return self.substitute(self.current()) def substitute(self, match): """Returns a cursor offset and line with match substituted in""" start, end, word = self.completer.locate(self.orig_cursor_offset, self.orig_line) return (start + len(match), self.orig_line[:start] + match + self.orig_line[end:]) def is_cseq(self): return bool( os.path.commonprefix(self.matches)[len(self.current_word):]) def substitute_cseq(self): """Returns a new line by substituting a common sequence in, and update matches""" cseq = os.path.commonprefix(self.matches) new_cursor_offset, new_line = self.substitute(cseq) if len(self.matches) == 1: self.clear() else: self.update(new_cursor_offset, new_line, self.matches, self.completer) if len(self.matches) == 1: self.clear() return new_cursor_offset, new_line def update(self, cursor_offset, current_line, matches, completer): """Called to reset the match index and update the word being replaced Should only be called if there's a target to update - otherwise, call clear""" if matches is None: raise ValueError("Matches may not be None.") self.orig_cursor_offset = cursor_offset self.orig_line = current_line self.matches = matches self.completer = completer self.index = -1 self.start, self.end, self.current_word = self.completer.locate( self.orig_cursor_offset, self.orig_line) def clear(self): self.matches = [] self.cursor_offset = -1 self.current_line = '' self.current_word = '' self.start = None self.end = None self.index = -1 class Interaction(object): def __init__(self, config, statusbar=None): self.config = config if statusbar: self.statusbar = statusbar def confirm(self, s): raise NotImplementedError def notify(self, s, n=10, wait_for_keypress=False): raise NotImplementedError def file_prompt(self, s): raise NotImplementedError class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" class Repl(object): """Implements the necessary guff for a Python-repl-alike interface The execution of the code entered and all that stuff was taken from the Python code module, I had to copy it instead of inheriting it, I can't remember why. The rest of the stuff is basically what makes it fancy. It reads what you type, passes it to a lexer and highlighter which returns a formatted string. This then gets passed to echo() which parses that string and prints to the curses screen in appropriate colours and/or bold attribute. The Repl class also keeps two stacks of lines that the user has typed in: One to be used for the undo feature. I am not happy with the way this works. The only way I have been able to think of is to keep the code that's been typed in in memory and re-evaluate it in its entirety for each "undo" operation. Obviously this means some operations could be extremely slow. I'm not even by any means certain that this truly represents a genuine "undo" implementation, but it does seem to be generally pretty effective. If anyone has any suggestions for how this could be improved, I'd be happy to hear them and implement it/accept a patch. I researched a bit into the idea of keeping the entire Python state in memory, but this really seems very difficult (I believe it may actually be impossible to work) and has its own problems too. The other stack is for keeping a history for pressing the up/down keys to go back and forth between lines. XXX Subclasses should implement echo, current_line, cw """ def __init__(self, interp, config): """Initialise the repl. interp is a Python code.InteractiveInterpreter instance config is a populated bpython.config.Struct. """ self.config = config self.cut_buffer = '' self.buffer = [] self.interp = interp self.interp.syntaxerror_callback = self.clear_current_line self.match = False self.rl_history = History(duplicates=config.hist_duplicates, hist_size=config.hist_length) self.s_hist = [] self.history = [] self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None self.arg_pos = None self.current_func = None self.highlighted_paren = None self._C = {} self.prev_block_finished = 0 self.interact = Interaction(self.config) # previous pastebin content to prevent duplicate pastes, filled on call # to repl.pastebin self.prev_pastebin_content = '' self.prev_pastebin_url = '' self.prev_removal_url = '' # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False self.clipboard = get_clipboard() pythonhist = os.path.expanduser(self.config.hist_file) if os.path.exists(pythonhist): try: self.rl_history.load(pythonhist, getpreferredencoding() or "ascii") except EnvironmentError: pass self.completers = autocomplete.get_default_completer( config.autocomplete_mode) if self.config.pastebin_helper: self.paster = PasteHelper(self.config.pastebin_helper) else: self.paster = PastePinnwand(self.config.pastebin_url, self.config.pastebin_expiry, self.config.pastebin_show_url, self.config.pastebin_removal_url) @property def ps1(self): try: if not py3: return sys.ps1.decode(getpreferredencoding()) else: return sys.ps1 except AttributeError: return u'>>> ' @property def ps2(self): try: if not py3: return sys.ps2.decode(getpreferredencoding()) else: return sys.ps2 except AttributeError: return u'... ' def startup(self): """ Execute PYTHONSTARTUP file if it exits. Call this after front end-specific initialisation. """ filename = os.environ.get('PYTHONSTARTUP') if filename: encoding = inspection.get_encoding_file(filename) with io.open(filename, 'rt', encoding=encoding) as f: source = f.read() if not py3: # Early Python 2.7.X need bytes. source = source.encode(encoding) self.interp.runsource(source, filename, 'exec', encode=False) def current_string(self, concatenate=False): """If the line ends in a string get it, otherwise return ''""" tokens = self.tokenize(self.current_line) string_tokens = list(takewhile(token_is_any_of([Token.String, Token.Text]), reversed(tokens))) if not string_tokens: return '' opening = string_tokens.pop()[1] string = list() for (token, value) in reversed(string_tokens): if token is Token.Text: continue elif opening is None: opening = value elif token is Token.String.Doc: string.append(value[3:-3]) opening = None elif value == opening: opening = None if not concatenate: string = list() else: string.append(value) if opening is None: return '' return ''.join(string) def get_object(self, name): attributes = name.split('.') obj = eval(attributes.pop(0), self.interp.locals) while attributes: with inspection.AttrCleaner(obj): obj = getattr(obj, attributes.pop(0)) return obj @classmethod def _funcname_and_argnum(cls, line): """Parse out the current function name and arg from a line of code.""" # each list in stack: # [full_expr, function_expr, arg_number, opening] # arg_number may be a string if we've encountered a keyword # argument so we're done counting stack = [['', '', 0, '']] try: for (token, value) in PythonLexer().get_tokens(line): if token is Token.Punctuation: if value in '([{': stack.append(['', '', 0, value]) elif value in ')]}': full, _, _, start = stack.pop() expr = start + full + value stack[-1][1] += expr stack[-1][0] += expr elif value == ',': try: stack[-1][2] += 1 except TypeError: stack[-1][2] = '' stack[-1][1] = '' stack[-1][0] += value elif value == ':' and stack[-1][3] == 'lambda': expr = stack.pop()[0] + ':' stack[-1][1] += expr stack[-1][0] += expr else: stack[-1][1] = '' stack[-1][0] += value elif (token is Token.Number or token in Token.Number.subtypes or token is Token.Name or token in Token.Name.subtypes or token is Token.Operator and value == '.'): stack[-1][1] += value stack[-1][0] += value elif token is Token.Operator and value == '=': stack[-1][2] = stack[-1][1] stack[-1][1] = '' stack[-1][0] += value elif token is Token.Number or token in Token.Number.subtypes: stack[-1][1] = value stack[-1][0] += value elif token is Token.Keyword and value == 'lambda': stack.append([value, '', 0, value]) else: stack[-1][1] = '' stack[-1][0] += value while stack[-1][3] in '[{': stack.pop() _, _, arg_number, _ = stack.pop() _, func, _, _ = stack.pop() return func, arg_number except IndexError: return None, None 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""" self.current_func = None if not self.config.arg_spec: return False func, arg_number = self._funcname_and_argnum(self.current_line) if not func: return False try: if inspection.is_eval_safe_name(func): f = self.get_object(func) else: try: fake_cursor = self.current_line.index(func) + len(func) f = simpleeval.evaluate_current_attribute( fake_cursor, self.current_line, self.interp.locals) except simpleeval.EvaluationError: return False except Exception: # another case of needing to catch every kind of error # since user code is run in the case of descriptors # XXX: Make sure you raise here if you're debugging the completion # stuff ! return False if inspect.isclass(f): 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 # py3 f.__new__.__class__ is not object.__new__.__class__): class_f = f.__new__ if class_f: f = class_f self.current_func = f 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): """Return the unicode source code of the object which is bound to the current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" obj = self.current_func try: if obj is None: line = self.current_line if not line.strip(): raise SourceNotFound(_("Nothing to get source of")) if inspection.is_eval_safe_name(line): obj = self.get_object(line) return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: msg = _(u"Cannot get source: %s") % (e, ) except IOError as e: msg = u"%s" % (e, ) except TypeError as e: if "built-in" in u"%s" % (e, ): msg = _("Cannot access source of %r") % (obj, ) else: msg = _("No source code found for %s") % (self.current_line, ) raise SourceNotFound(msg) def set_docstring(self): self.docstring = None if not self.get_args(): self.funcprops = None if self.current_func is not None: try: self.docstring = pydoc.getdoc(self.current_func) except IndexError: self.docstring = None else: # pydoc.getdoc() returns an empty string if no # docstring was found if not self.docstring: self.docstring = None # What complete() does: # Should we show the completion box? (are there matches, or is there a # docstring to show?) # Some completions should always be shown, other only if tab=True # set the current docstring to the "current function's" docstring # Populate the matches_iter object with new matches from the current state # if none, clear the matches iterator # If exactly one match that is equal to current line, clear matches # If example one match and tab=True, then choose that and clear matches def complete(self, tab=False): """Construct a full list of possible completions and display them in a window. Also check if there's an available argspec (via the inspect module) and bang that on top of the completions too. The return value is whether the list_win is visible or not. If no matches are found, just return whether there's an argspec to show If any matches are found, save them and select the first one. If tab is True exactly one match found, make the replacement and return the result of running complete() again on the new line. """ self.set_docstring() matches, completer = autocomplete.get_completer( self.completers, cursor_offset=self.cursor_offset, line=self.current_line, locals_=self.interp.locals, argspec=self.funcprops, current_block='\n'.join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, history=self.history) if len(matches) == 0: self.matches_iter.clear() return bool(self.funcprops) self.matches_iter.update(self.cursor_offset, self.current_line, matches, completer) if len(matches) == 1: if tab: # if this complete is being run for a tab key press, substitute # common sequence self._cursor_offset, self._current_line = \ self.matches_iter.substitute_cseq() return Repl.complete(self) # again for elif self.matches_iter.current_word == matches[0]: self.matches_iter.clear() return False return completer.shown_before_tab else: return tab or completer.shown_before_tab def format_docstring(self, docstring, width, height): """Take a string and try to format it into a sane list of strings to be put into the suggestion box.""" lines = docstring.split('\n') out = [] i = 0 for line in lines: i += 1 if not line.strip(): out.append('\n') for block in textwrap.wrap(line, width): out.append(' ' + block + '\n') if i >= height: return out i += 1 # Drop the last newline out[-1] = out[-1].rstrip() return out def next_indentation(self): """Return the indentation of the next line based on the current input buffer.""" if self.buffer: indentation = next_indentation(self.buffer[-1], self.config.tab_length) if indentation and self.config.dedent_after > 0: def line_is_empty(line): return not line.strip() empty_lines = takewhile(line_is_empty, reversed(self.buffer)) if sum(1 for _ in empty_lines) >= self.config.dedent_after: indentation -= 1 else: indentation = 0 return indentation def formatforfile(self, session_ouput): """Format the stdout buffer to something suitable for writing to disk, i.e. without >>> and ... at input lines and with "# OUT: " prepended to output lines.""" def process(): for line in session_ouput.split('\n'): if line.startswith(self.ps1): yield line[len(self.ps1):] elif line.startswith(self.ps2): yield line[len(self.ps2):] elif line.rstrip(): yield "# OUT: %s" % (line,) return "\n".join(process()) def write2file(self): """Prompt for a filename and write the current contents of the stdout buffer to disk.""" try: fn = self.interact.file_prompt(_('Save to file (Esc to cancel): ')) if not fn: self.interact.notify(_('Save cancelled.')) return except ValueError: self.interact.notify(_('Save cancelled.')) return if fn.startswith('~'): fn = os.path.expanduser(fn) if not fn.endswith('.py') and self.config.save_append_py: fn = fn + '.py' mode = 'w' if os.path.exists(fn): mode = self.interact.file_prompt(_('%s already exists. Do you ' 'want to (c)ancel, ' ' (o)verwrite or ' '(a)ppend? ') % (fn, )) if mode in ('o', 'overwrite', _('overwrite')): mode = 'w' elif mode in ('a', 'append', _('append')): mode = 'a' else: self.interact.notify(_('Save cancelled.')) return session_test = self.formatforfile(self.getstdout()) try: with open(fn, mode) as f: f.write(stdout_text) except IOError as e: self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) else: self.interact.notify(_('Saved to %s.') % (fn, )) def copy2clipboard(self): """Copy current content to clipboard.""" if self.clipboard is None: self.interact.notify(_('No clipboard available.')) return content = self.formatforfile(self.getstdout()) try: self.clipboard.copy(content) except CopyFailed: self.interact.notify(_('Could not copy to clipboard.')) else: self.interact.notify(_('Copied content to clipboard.')) def pastebin(self, s=None): """Upload to a pastebin and display the URL in the status bar.""" if s is None: s = self.getstdout() if (self.config.pastebin_confirm and not self.interact.confirm(_("Pastebin buffer? (y/N) "))): self.interact.notify(_("Pastebin aborted.")) return return self.do_pastebin(s) def do_pastebin(self, s): """Actually perform the upload.""" if s == self.prev_pastebin_content: self.interact.notify(_('Duplicate pastebin. Previous URL: %s. ' 'Removal URL: %s') % (self.prev_pastebin_url, 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) except PasteFailed as e: self.interact.notify(_('Upload failed: %s') % e) return self.prev_pastebin_content = s self.prev_pastebin_url = paste_url self.prev_removal_url = removal_url if removal_url is not None: self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % (paste_url, removal_url), 10) else: self.interact.notify(_('Pastebin URL: %s') % (paste_url, ), 10) return paste_url def push(self, s, insert_into_history=True): """Push a line of code onto the buffer so it can process it all at once when a code block ends""" s = s.rstrip('\n') self.buffer.append(s) if insert_into_history: self.insert_into_history(s) more = self.interp.runsource('\n'.join(self.buffer)) if not more: self.buffer = [] return more def insert_into_history(self, s): pythonhist = os.path.expanduser(self.config.hist_file) try: self.rl_history.append_reload_and_write(s, pythonhist, getpreferredencoding()) except RuntimeError as e: self.interact.notify(u"%s" % (e, )) def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" if (self.config.single_undo_time < 0 or self.interp.timer.estimate() < self.config.single_undo_time): return 1 est = self.interp.timer.estimate() n = self.interact.file_prompt( _("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]") % (est,)) try: if n == '': n = '1' n = int(n) except ValueError: self.interact.notify(_('Undo canceled'), .1) return 0 else: if n == 0: self.interact.notify(_('Undo canceled'), .1) return 0 else: message = ngettext('Undoing %d line... (est. %.1f seconds)', 'Undoing %d lines... (est. %.1f seconds)', n) self.interact.notify(message % (n, est), .1) return n def undo(self, n=1): """Go back in the undo history n steps and call reevaluate() Note that in the program this is called "Rewind" because I want it to be clear that this is by no means a true undo implementation, it is merely a convenience bonus.""" if not self.history: return None self.interp.timer.reset_timer() if len(self.history) < n: n = len(self.history) entries = list(self.rl_history.entries) self.history = self.history[:-n] self.reevaluate() self.rl_history.entries = entries def flush(self): """Olivier Grisel brought it to my attention that the logging module tries to call this method, since it makes assumptions about stdout that may not necessarily be true. The docs for sys.stdout say: "stdout and stderr needn't be built-in file objects: any object is acceptable as long as it has a write() method that takes a string argument." So I consider this to be a bug in logging, and this is a hack to fix it, unfortunately. I'm sure it's not the only module to do it.""" def close(self): """See the flush() method docstring.""" def tokenize(self, s, newline=False): """Tokenizes a line of code, returning pygments tokens with side effects/impurities: - reads self.cpos to see what parens should be highlighted - reads self.buffer to see what came before the passed in line - sets self.highlighted_paren to (buffer_lineno, tokens_for_that_line) for buffer line that should replace that line to unhighlight it, or None if no paren is currently highlighted - calls reprint_line with a buffer's line's tokens and the buffer lineno that has changed if line other than the current line changes """ highlighted_paren = None source = '\n'.join(self.buffer + [s]) cursor = len(source) - self.cpos if self.cpos: cursor += 1 stack = list() all_tokens = list(PythonLexer().get_tokens(source)) # Unfortunately, Pygments adds a trailing newline and strings with # no size, so strip them while not all_tokens[-1][1]: all_tokens.pop() all_tokens[-1] = (all_tokens[-1][0], all_tokens[-1][1].rstrip('\n')) line = pos = 0 parens = dict(zip('{([', '})]')) line_tokens = list() saved_tokens = list() search_for_paren = True for (token, value) in split_lines(all_tokens): pos += len(value) if token is Token.Text and value == '\n': line += 1 # Remove trailing newline line_tokens = list() saved_tokens = list() continue line_tokens.append((token, value)) saved_tokens.append((token, value)) if not search_for_paren: continue under_cursor = (pos == cursor) if token is Token.Punctuation: if value in parens: if under_cursor: line_tokens[-1] = (Parenthesis.UnderCursor, value) # Push marker on the stack stack.append((Parenthesis, value)) else: stack.append((line, len(line_tokens) - 1, line_tokens, value)) elif value in itervalues(parens): saved_stack = list(stack) try: while True: opening = stack.pop() if parens[opening[-1]] == value: break except IndexError: # SyntaxError.. more closed parentheses than # opened or a wrong closing paren opening = None if not saved_stack: search_for_paren = False else: stack = saved_stack if opening and opening[0] is Parenthesis: # Marker found line_tokens[-1] = (Parenthesis, value) search_for_paren = False elif opening and under_cursor and not newline: if self.cpos: line_tokens[-1] = (Parenthesis.UnderCursor, value) else: # The cursor is at the end of line and next to # the paren, so it doesn't reverse the paren. # Therefore, we insert the Parenthesis token # here instead of the Parenthesis.UnderCursor # token. line_tokens[-1] = (Parenthesis, value) (lineno, i, tokens, opening) = opening if lineno == len(self.buffer): highlighted_paren = (lineno, saved_tokens) line_tokens[i] = (Parenthesis, opening) else: highlighted_paren = (lineno, list(tokens)) # We need to redraw a line tokens[i] = (Parenthesis, opening) self.reprint_line(lineno, tokens) search_for_paren = False elif under_cursor: search_for_paren = False self.highlighted_paren = highlighted_paren if line != len(self.buffer): return list() return line_tokens def clear_current_line(self): """This is used as the exception callback for the Interpreter instance. It prevents autoindentation from occurring after a traceback.""" def send_to_external_editor(self, text): """Returns modified text from an editor, or the original text if editor exited with non-zero""" encoding = getpreferredencoding() editor_args = shlex.split(prepare_for_exec(self.config.editor, encoding)) with tempfile.NamedTemporaryFile(suffix='.py') as temp: temp.write(text.encode(encoding)) temp.flush() args = editor_args + [prepare_for_exec(temp.name, encoding)] if subprocess.call(args) == 0: with open(temp.name) as f: if py3: return f.read() else: return f.read().decode(encoding) else: return text def open_in_external_editor(self, filename): encoding = getpreferredencoding() editor_args = shlex.split(prepare_for_exec(self.config.editor, encoding)) args = editor_args + [prepare_for_exec(filename, encoding)] return subprocess.call(args) == 0 def edit_config(self): if not os.path.isfile(self.config.config_path): if self.interact.confirm(_("Config file does not exist - create " "new from default? (y/N)")): try: default_config = pkgutil.get_data('bpython', 'sample-config') if py3: # py3 files need unicode default_config = default_config.decode('ascii') containing_dir = os.path.dirname( os.path.abspath(self.config.config_path)) if not os.path.exists(containing_dir): os.makedirs(containing_dir) with open(self.config.config_path, 'w') as f: f.write(default_config) except (IOError, OSError) as e: self.interact.notify(_("Error writing file '%s': %s") % (self.config.config.path, e)) return False else: return False 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): """Given a code line, return the indentation of the next line.""" line = line.expandtabs(tab_length) indentation = (len(line) - len(line.lstrip(' '))) // tab_length if line.rstrip().endswith(':'): indentation += 1 elif indentation >= 1: if line.lstrip().startswith(('return', 'pass', 'raise', 'yield')): indentation -= 1 return indentation def next_token_inside_string(code_string, inside_string): """Given a code string s and an initial state inside_string, return whether the next token will be inside a string or not.""" for token, value in PythonLexer().get_tokens(code_string): if token is Token.String: value = value.lstrip('bBrRuU') if value in ['"""', "'''", '"', "'"]: if not inside_string: inside_string = value elif value == inside_string: inside_string = False return inside_string def split_lines(tokens): for (token, value) in tokens: if not value: continue while value: head, newline, value = value.partition('\n') yield (token, head) if newline: yield (Token.Text, newline) def token_is(token_type): """Return a callable object that returns whether a token is of the given type `token_type`.""" def token_is_type(token): """Return whether a token is of a certain type or not.""" token = token[0] while token is not token_type and token.parent: token = token.parent return token is token_type return token_is_type def token_is_any_of(token_types): """Return a callable object that returns whether a token is any of the given types `token_types`.""" is_token_types = tuple(map(token_is, token_types)) def token_is_any_of(token): return any(check(token) for check in is_token_types) return token_is_any_of def extract_exit_value(args): """Given the arguments passed to `SystemExit`, return the value that should be passed to `sys.exit`. """ if len(args) == 0: return None elif len(args) == 1: return args[0] else: return args bpython-0.17.1/bpython/patch_linecache.py0000644000175100017510000000543213240407731020377 0ustar useruser00000000000000# encoding: utf-8 from __future__ import absolute_import 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.""" def __init__(self, *args, **kwargs): super(BPythonLinecache, self).__init__(*args, **kwargs) self.bpython_history = [] def is_bpython_filename(self, fname): try: return fname.startswith('' bpython-0.17.1/bpython/formatter.py0000644000175100017510000000736713240407731017321 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2008 Bob Farrell # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # A simple formatter for bpython to work with Pygments. # Pygments really kicks ass, it made it really easy to # get the exact behaviour I wanted, thanks Pygments.:) from __future__ import absolute_import from pygments.formatter import Formatter from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Token, Whitespace, Literal, Punctuation from six import iteritems """These format strings are pretty ugly. \x01 represents a colour marker, which can be preceded by one or two of the following letters: k, r, g, y, b, m, c, w, d Which represent: blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default e.g. \x01y for yellow, \x01gb for green on blue background \x02 represents the bold attribute \x03 represents the start of the actual text that is output (in this case it's a %s for substitution) \x04 represents the end of the string; this is necessary because the strings are all joined together at the end so the parser needs them as delimiters """ Parenthesis = Token.Punctuation.Parenthesis theme_map = { Keyword: 'keyword', Name: 'name', Comment: 'comment', String: 'string', Literal: 'string', Error: 'error', Number: 'number', Token.Literal.Number.Float: 'number', Operator: 'operator', Punctuation: 'punctuation', Token: 'token', Whitespace: 'background', Parenthesis: 'paren', Parenthesis.UnderCursor: 'operator'} class BPythonFormatter(Formatter): """This is the custom formatter for bpython. Its format() method receives the tokensource and outfile params passed to it from the Pygments highlight() method and slops them into the appropriate format string as defined above, then writes to the outfile object the final formatted string. See the Pygments source for more info; it's pretty straightforward.""" def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in iteritems(theme_map): self.f_strings[k] = '\x01%s' % (color_scheme[v],) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current # background colour self.f_strings[k] += 'I' Formatter.__init__(self, **options) def format(self, tokensource, outfile): o = '' for token, text in tokensource: if text == '\n': continue while token not in self.f_strings: token = token.parent o += "%s\x03%s\x04" % (self.f_strings[token], text) outfile.write(o.rstrip()) # vim: sw=4 ts=4 sts=4 ai et bpython-0.17.1/bpython/simpleeval.py0000644000175100017510000002450213240407731017445 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 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 # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # """simple evaluation of side-effect free code In order to provide fancy completion, some code can be executed safely. """ from __future__ import absolute_import import ast import inspect from six import string_types from six.moves import builtins import sys import types from . import line as line_properties from ._py3compat import py3 from .inspection import is_new_style, AttrCleaner _string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) _numeric_types = (int, float, complex) + (() if py3 else (long,)) # added in Python 3.4 if hasattr(ast, 'NameConstant'): _name_type_nodes = (ast.Name, ast.NameConstant) else: _name_type_nodes = (ast.Name,) class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" def safe_eval(expr, namespace): """Not all that safe, just catches some errors""" try: return eval(expr, namespace) except (NameError, AttributeError, SyntaxError): # If debugging safe_eval, raise this! # raise raise EvaluationError # This function is under the Python License, Version 2 # This license requires modifications to the code be reported. # Based on ast.literal_eval in Python 2 and Python 3 # Modifications: # * Python 2 and Python 3 versions of the function are combined # * checks that objects used as operands of + and - are numbers # instead of checking they are constructed with number literals # * new docstring describing different functionality # * looks up names from namespace # * indexing syntax is allowed def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python expression without triggering any user code. The string or node provided may only consist of: * the following Python literal structures: strings, numbers, tuples, lists, and dicts * variable names causing lookups in the passed in namespace or builtins * getitem calls using the [] syntax on objects of the types above Like the Python 3 (and unlike the Python 2) literal_eval, unary and binary + and - operations are allowed on all builtin numeric types. The optional namespace dict-like ought not to cause side effects on lookup """ if namespace is None: namespace = {} if isinstance(node_or_string, string_types): node_or_string = ast.parse(node_or_string, mode='eval') if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body def _convert(node): if isinstance(node, _string_type_nodes): return node.s elif isinstance(node, ast.Num): return node.n elif isinstance(node, ast.Tuple): return tuple(map(_convert, node.elts)) elif isinstance(node, ast.List): return list(map(_convert, node.elts)) elif isinstance(node, ast.Dict): return dict((_convert(k), _convert(v)) for k, v in zip(node.keys, node.values)) # this is a deviation from literal_eval: we allow non-literals elif isinstance(node, _name_type_nodes): try: return namespace[node.id] except KeyError: try: return getattr(builtins, node.id) except AttributeError: raise EvaluationError("can't lookup %s" % node.id) # unary + and - are allowed on any type elif (isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub))): # ast.literal_eval does ast typechecks here, we use type checks operand = _convert(node.operand) if not type(operand) in _numeric_types: raise ValueError("unary + and - only allowed on builtin nums") if isinstance(node.op, ast.UAdd): return + operand else: return - operand elif (isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Sub))): # ast.literal_eval does ast typechecks here, we use type checks left = _convert(node.left) right = _convert(node.right) if not (type(left) in _numeric_types and type(right) in _numeric_types): raise ValueError("binary + and - only allowed on builtin nums") if isinstance(node.op, ast.Add): return left + right else: return left - right # this is a deviation from literal_eval: we allow indexing elif (isinstance(node, ast.Subscript) and isinstance(node.slice, ast.Index)): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) # this is a deviation from literal_eval: we allow attribute access if isinstance(node, ast.Attribute): obj = _convert(node.value) attr = node.attr return safe_get_attribute(obj, attr) raise ValueError('malformed string') return _convert(node_or_string) def safe_getitem(obj, index): if type(obj) in (list, tuple, dict, bytes) + string_types: try: return obj[index] except (KeyError, IndexError): raise EvaluationError("can't lookup key %r on %r" % (index, obj)) raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) def find_attribute_with_name(node, name): if isinstance(node, ast.Attribute) and node.attr == name: return node for item in ast.iter_child_nodes(node): r = find_attribute_with_name(item, name) if r: return r def evaluate_current_expression(cursor_offset, line, namespace=None): """ Return evaluated expression to the right of the dot of current attribute. Only evaluates builtin objects, and do any attribute lookup. """ # Builds asts from with increasing numbers of characters back from cursor. # Find the biggest valid ast. # Once our attribute access is found, return its .value subtree if namespace is None: namespace = {} # in case attribute is blank, e.g. foo.| -> foo.xxx| temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:] temp_cursor = cursor_offset + 3 temp_attribute = line_properties.current_expression_attribute( temp_cursor, temp_line) if temp_attribute is None: raise EvaluationError("No current attribute") attr_before_cursor = temp_line[temp_attribute.start:temp_cursor] def parse_trees(cursor_offset, line): for i in range(cursor_offset - 1, -1, -1): try: tree = ast.parse(line[i:cursor_offset]) yield tree except SyntaxError: continue largest_ast = None for tree in parse_trees(temp_cursor, temp_line): attribute_access = find_attribute_with_name(tree, attr_before_cursor) if attribute_access: largest_ast = attribute_access.value if largest_ast is None: raise EvaluationError( "Corresponding ASTs to right of cursor are invalid") try: return simple_eval(largest_ast, namespace) except ValueError: raise EvaluationError("Could not safely evaluate") def evaluate_current_attribute(cursor_offset, line, namespace=None): """Safely evaluates the expression having an attributed accessed""" # this function runs user code in case of custom descriptors, # so could fail in any way obj = evaluate_current_expression(cursor_offset, line, namespace) attr = line_properties.current_expression_attribute(cursor_offset, line) if attr is None: raise EvaluationError("No attribute found to look up") try: return getattr(obj, attr.word) except AttributeError: raise EvaluationError( "can't lookup attribute %s on %r" % (attr.word, obj)) def safe_get_attribute(obj, attr): """Gets attributes without triggering descriptors on new-style classes""" if is_new_style(obj): with AttrCleaner(obj): result = safe_get_attribute_new_style(obj, attr) if isinstance(result, member_descriptor): # will either be the same slot descriptor or the value return getattr(obj, attr) return result return getattr(obj, attr) class _ClassWithSlots(object): __slots__ = ['a'] member_descriptor = type(_ClassWithSlots.a) def safe_get_attribute_new_style(obj, attr): """Returns approximately the attribute returned by getattr(obj, attr) The object returned ought to be callable if getattr(obj, attr) was. Fake callable objects may be returned instead, in order to avoid executing arbitrary code in descriptors. If the object is an instance of a class that uses __slots__, will return the member_descriptor object instead of the value. """ if not is_new_style(obj): raise ValueError("%r is not a new-style class or object" % obj) to_look_through = (obj.__mro__ if inspect.isclass(obj) else (obj,) + type(obj).__mro__) for cls in to_look_through: if hasattr(cls, '__dict__') and attr in cls.__dict__: return cls.__dict__[attr] raise AttributeError() bpython-0.17.1/bpython/cli.py0000644000175100017510000017314313240410473016056 0ustar useruser00000000000000# The MIT License # # Copyright (c) 2008 Bob Farrell # Copyright (c) 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 # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # Modified by Brandon Navra # Notes for Windows # Prerequisites # - Curses # - pyreadline # # Added # # - Support for running on windows command prompt # - input from numpad keys # # Issues # # - Suspend doesn't work nor does detection of resizing of screen # - Instead the suspend key exits the program # - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) from __future__ import division, absolute_import import platform import os import sys import curses import math import re import time import functools import struct if platform.system() != 'Windows': import signal #Windows does not have job control import termios #Windows uses curses import fcntl #Windows uses curses import unicodedata import errno from six.moves import range # These are used for syntax highlighting from pygments import format from pygments.formatters import TerminalFormatter from ._py3compat import PythonLexer from pygments.token import Token from .formatter import BPythonFormatter # This for completion from . import importcompletion # This for config from .config import Struct, getpreferredencoding # This for keys from .keys import cli_key_dispatch as key_dispatch # This for i18n from . import translations from .translations import _ from . import repl from ._py3compat import py3 from .pager import page from .args import parse as argsparse if not py3: import inspect # --- module globals --- stdscr = None colors = None DO_RESIZE = False # --- def calculate_screen_lines(tokens, width, cursor=0): """Given a stream of tokens and a screen width plus an optional initial cursor position, return the amount of needed lines on the screen.""" lines = 1 pos = cursor for (token, value) in tokens: if token is Token.Text and value == '\n': lines += 1 else: pos += len(value) lines += pos // width pos %= width return lines def forward_if_not_current(func): @functools.wraps(func) def newfunc(self, *args, **kwargs): dest = self.get_dest() if self is dest: return func(self, *args, **kwargs) else: return getattr(self.get_dest(), newfunc.__name__)(*args, **kwargs) return newfunc class FakeStream(object): """Provide a fake file object which calls functions on the interface provided.""" def __init__(self, interface, get_dest): self.encoding = getpreferredencoding() self.interface = interface self.get_dest = get_dest @forward_if_not_current def write(self, s): self.interface.write(s) @forward_if_not_current def writelines(self, l): for s in l: self.write(s) def isatty(self): # some third party (amongst them mercurial) depend on this return True def flush(self): self.interface.flush() class FakeStdin(object): """Provide a fake stdin type for things like raw_input() etc.""" def __init__(self, interface): """Take the curses Repl on init and assume it provides a get_key method which, fortunately, it does.""" self.encoding = getpreferredencoding() self.interface = interface self.buffer = list() def __iter__(self): return iter(self.readlines()) def flush(self): """Flush the internal buffer. This is a no-op. Flushing stdin doesn't make any sense anyway.""" def write(self, value): # XXX IPython expects sys.stdin.write to exist, there will no doubt be # others, so here's a hack to keep them happy raise IOError(errno.EBADF, "sys.stdin is read-only") def isatty(self): return True def readline(self, size=-1): """I can't think of any reason why anything other than readline would be useful in the context of an interactive interpreter so this is the only one I've done anything with. The others are just there in case someone does something weird to stop it from blowing up.""" if not size: return '' elif self.buffer: buffer = self.buffer.pop(0) else: buffer = '' curses.raw(True) try: while not buffer.endswith(('\n', '\r')): key = self.interface.get_key() if key in [curses.erasechar(), 'KEY_BACKSPACE']: y, x = self.interface.scr.getyx() if buffer: self.interface.scr.delch(y, x - 1) buffer = buffer[:-1] continue elif key == chr(4) and not buffer: # C-d return '' elif (key not in ('\n', '\r') and (len(key) > 1 or unicodedata.category(key) == 'Cc')): continue sys.stdout.write(key) # Include the \n in the buffer - raw_input() seems to deal with trailing # linebreaks and will break if it gets an empty string. buffer += key finally: curses.raw(False) if size > 0: rest = buffer[size:] if rest: self.buffer.append(rest) buffer = buffer[:size] if py3: return buffer else: return buffer.encode(getpreferredencoding()) def read(self, size=None): if size == 0: return '' data = list() while size is None or size > 0: line = self.readline(size or -1) if not line: break if size is not None: size -= len(line) data.append(line) return ''.join(data) def readlines(self, size=-1): return list(iter(self.readline, '')) # TODO: # # Tab completion does not work if not at the end of the line. # # Numerous optimisations can be made but it seems to do all the lookup stuff # fast enough on even my crappy server so I'm not too bothered about that # at the moment. # # The popup window that displays the argspecs and completion suggestions # needs to be an instance of a ListWin class or something so I can wrap # the addstr stuff to a higher level. # def get_color(config, name): global colors return colors[config.color_scheme[name].lower()] def get_colpair(config, name): return curses.color_pair(get_color(config, name) + 1) def make_colors(config): """Init all the colours in curses and bang them into a dictionary""" # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default: c = { 'k': 0, 'r': 1, 'g': 2, 'y': 3, 'b': 4, 'm': 5, 'c': 6, 'w': 7, 'd': -1, } if platform.system() == 'Windows': c = dict(list(c.items()) + [ ('K', 8), ('R', 9), ('G', 10), ('Y', 11), ('B', 12), ('M', 13), ('C', 14), ('W', 15), ] ) for i in range(63): if i > 7: j = i // 8 else: j = c[config.color_scheme['background']] curses.init_pair(i + 1, i % 8, j) return c class CLIInteraction(repl.Interaction): def __init__(self, config, statusbar=None): repl.Interaction.__init__(self, config, statusbar) def confirm(self, q): """Ask for yes or no and return boolean""" try: reply = self.statusbar.prompt(q) except ValueError: return False return reply.lower() in (_('y'), _('yes')) def notify(self, s, n=10, wait_for_keypress=False): return self.statusbar.message(s, n) def file_prompt(self, s): return self.statusbar.prompt(s) class CLIRepl(repl.Repl): def __init__(self, scr, interp, statusbar, config, idle=None): repl.Repl.__init__(self, interp, config) self.interp.writetb = self.writetb self.scr = scr self.stdout_hist = '' self.list_win = newwin(get_colpair(config, 'background'), 1, 1, 1, 1) self.cpos = 0 self.do_exit = False self.exit_value = () self.f_string = '' self.idle = idle self.in_hist = False self.paste_mode = False self.last_key_press = time.time() self.s = '' self.statusbar = statusbar self.formatter = BPythonFormatter(config.color_scheme) self.interact = CLIInteraction(self.config, statusbar=self.statusbar) if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 0.8 def _get_cursor_offset(self): return len(self.s) - self.cpos def _set_cursor_offset(self, offset): self.cpos = len(self.s) - offset cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, "The cursor offset from the beginning of the line") def addstr(self, s): """Add a string to the current input line and figure out where it should go, depending on the cursor position.""" self.rl_history.reset() if not self.cpos: self.s += s else: l = len(self.s) self.s = self.s[:l - self.cpos] + s + self.s[l - self.cpos:] self.complete() def atbol(self): """Return True or False accordingly if the cursor is at the beginning of the line (whitespace is ignored). This exists so that p_key() knows how to handle the tab key being pressed - if there is nothing but white space before the cursor then process it as a normal tab otherwise attempt tab completion.""" return not self.s.lstrip() def bs(self, delete_tabs=True): """Process a backspace""" self.rl_history.reset() y, x = self.scr.getyx() if not self.s: return if x == self.ix and y == self.iy: return n = 1 self.clear_wrapped_lines() if not self.cpos: # I know the nested if blocks look nasty. :( if self.atbol() and delete_tabs: n = len(self.s) % self.config.tab_length if not n: n = self.config.tab_length self.s = self.s[:-n] else: self.s = self.s[:-self.cpos - 1] + self.s[-self.cpos:] self.print_line(self.s, clr=True) return n def bs_word(self): self.rl_history.reset() pos = len(self.s) - self.cpos - 1 deleted = [] # First we delete any space to the left of the cursor. while pos >= 0 and self.s[pos] == ' ': deleted.append(self.s[pos]) pos -= self.bs() # Then we delete a full word. while pos >= 0 and self.s[pos] != ' ': deleted.append(self.s[pos]) pos -= self.bs() return ''.join(reversed(deleted)) def check(self): """Check if paste mode should still be active and, if not, deactivate it and force syntax highlighting.""" if (self.paste_mode and time.time() - self.last_key_press > self.config.paste_time): self.paste_mode = False self.print_line(self.s) def clear_current_line(self): """Called when a SyntaxError occurred in the interpreter. It is used to prevent autoindentation from occurring after a traceback.""" repl.Repl.clear_current_line(self) self.s = '' def clear_wrapped_lines(self): """Clear the wrapped lines of the current input.""" # curses does not handle this on its own. Sad. height, width = self.scr.getmaxyx() max_y = min(self.iy + (self.ix + len(self.s)) // width + 1, height) for y in range(self.iy + 1, max_y): self.scr.move(y, 0) self.scr.clrtoeol() def complete(self, tab=False): """Get Autocomplete list and window. Called whenever these should be updated, and called with tab """ if self.paste_mode: self.scr.touchwin() #TODO necessary? return list_win_visible = repl.Repl.complete(self, tab) if list_win_visible: try: 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 # using it. self.list_win.border() self.list_win.refresh() list_win_visible = False if not list_win_visible: self.scr.redrawwin() self.scr.refresh() def clrtobol(self): """Clear from cursor to beginning of line; usual C-u behaviour""" self.clear_wrapped_lines() if not self.cpos: self.s = '' else: self.s = self.s[-self.cpos:] self.print_line(self.s, clr=True) self.scr.redrawwin() self.scr.refresh() def _get_current_line(self): return self.s def _set_current_line(self, line): self.s = line current_line = property(_get_current_line, _set_current_line, None, "The characters of the current line") def cut_to_buffer(self): """Clear from cursor to end of line, placing into cut buffer""" self.cut_buffer = self.s[-self.cpos:] self.s = self.s[:-self.cpos] self.cpos = 0 self.print_line(self.s, clr=True) self.scr.redrawwin() self.scr.refresh() def delete(self): """Process a del""" if not self.s: return if self.mvc(-1): self.bs(False) def echo(self, s, redraw=True): """Parse and echo a formatted string with appropriate attributes. It uses the formatting method as defined in formatter.py to parse the srings. It won't update the screen if it's reevaluating the code (as it does with undo).""" if not py3 and isinstance(s, unicode): s = s.encode(getpreferredencoding()) a = get_colpair(self.config, 'output') if '\x01' in s: rx = re.search('\x01([A-Za-z])([A-Za-z]?)', s) if rx: fg = rx.groups()[0] bg = rx.groups()[1] col_num = self._C[fg.lower()] if bg and bg != 'I': col_num *= self._C[bg.lower()] a = curses.color_pair(int(col_num) + 1) if bg == 'I': a = a | curses.A_REVERSE s = re.sub('\x01[A-Za-z][A-Za-z]?', '', s) if fg.isupper(): a = a | curses.A_BOLD s = s.replace('\x03', '') s = s.replace('\x01', '') # Replace NUL bytes, as addstr raises an exception otherwise s = s.replace('\0', '') # Replace \r\n bytes, as addstr remove the current line otherwise s = s.replace('\r\n', '\n') self.scr.addstr(s, a) if redraw and not self.evaluating: self.scr.refresh() def end(self, refresh=True): self.cpos = 0 h, w = gethw() y, x = divmod(len(self.s) + self.ix, w) y += self.iy self.scr.move(y, x) if refresh: self.scr.refresh() return True def hbegin(self): """Replace the active line with first line in history and increment the index to keep track""" self.cpos = 0 self.clear_wrapped_lines() self.rl_history.enter(self.s) self.s = self.rl_history.first() self.print_line(self.s, clr=True) def hend(self): """Same as hbegin() but, well, forward""" self.cpos = 0 self.clear_wrapped_lines() self.rl_history.enter(self.s) self.s = self.rl_history.last() self.print_line(self.s, clr=True) def back(self): """Replace the active line with previous line in history and increment the index to keep track""" self.cpos = 0 self.clear_wrapped_lines() self.rl_history.enter(self.s) self.s = self.rl_history.back() self.print_line(self.s, clr=True) def fwd(self): """Same as back() but, well, forward""" self.cpos = 0 self.clear_wrapped_lines() self.rl_history.enter(self.s) self.s = self.rl_history.forward() self.print_line(self.s, clr=True) def search(self): """Search with the partial matches from the history object.""" self.cpo = 0 self.clear_wrapped_lines() self.rl_history.enter(self.s) self.s = self.rl_history.back(start=False, search=True) self.print_line(self.s, clr=True) def get_key(self): key = '' while True: try: key += self.scr.getkey() if py3: # Seems like we get a in the locale's encoding # encoded string in Python 3 as well, but of # type str instead of bytes, hence convert it to # bytes first and decode then key = key.encode('latin-1').decode(getpreferredencoding()) else: key = key.decode(getpreferredencoding()) self.scr.nodelay(False) except UnicodeDecodeError: # Yes, that actually kind of sucks, but I don't see another way to get # input right self.scr.nodelay(True) except curses.error: # I'm quite annoyed with the ambiguity of this exception handler. I previously # caught "curses.error, x" and accessed x.message and checked that it was "no # input", which seemed a crappy way of doing it. But then I ran it on a # different computer and the exception seems to have entirely different # attributes. So let's hope getkey() doesn't raise any other crazy curses # exceptions. :) self.scr.nodelay(False) # XXX What to do here? Raise an exception? if key: return key else: if key != '\x00': t = time.time() self.paste_mode = ( t - self.last_key_press <= self.config.paste_time ) self.last_key_press = t return key else: key = '' finally: if self.idle: self.idle(self) def get_line(self): """Get a line of text and return it This function initialises an empty string and gets the curses cursor position on the screen and stores it for the echo() function to use later (I think). Then it waits for key presses and passes them to p_key(), which returns None if Enter is pressed (that means "Return", idiot).""" self.s = '' self.rl_history.reset() self.iy, self.ix = self.scr.getyx() if not self.paste_mode: for _ in range(self.next_indentation()): self.p_key('\t') self.cpos = 0 while True: key = self.get_key() if self.p_key(key) is None: if self.config.cli_trim_prompts and self.s.startswith(">>> "): self.s = self.s[4:] return self.s def home(self, refresh=True): self.scr.move(self.iy, self.ix) self.cpos = len(self.s) if refresh: self.scr.refresh() return True def lf(self): """Process a linefeed character; it only needs to check the cursor position and move appropriately so it doesn't clear the current line after the cursor.""" if self.cpos: for _ in range(self.cpos): self.mvc(-1) # Reprint the line (as there was maybe a highlighted paren in it) self.print_line(self.s, newline=True) self.echo("\n") 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 addstr() to get the colouring right, but it seems to be pretty sturdy.""" r = 3 fn = topline.func args = topline.argspec.args kwargs = topline.argspec.defaults _args = topline.argspec.varargs _kwargs = topline.argspec.varkwargs is_bound_method = topline.is_bound_method if py3: kwonly = topline.argspec.kwonly 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) h, w = self.list_win.getmaxyx() self.list_win.addstr('\n ') self.list_win.addstr(fn, get_colpair(self.config, 'name') | curses.A_BOLD) self.list_win.addstr(': (', get_colpair(self.config, 'name')) maxh = self.scr.getmaxyx()[0] if is_bound_method and isinstance(in_arg, int): in_arg += 1 punctuation_colpair = get_colpair(self.config, 'punctuation') for k, i in enumerate(args): y, x = self.list_win.getyx() ln = len(str(i)) kw = None if kwargs and k + 1 > len(args) - len(kwargs): kw = repr(kwargs[k - (len(args) - len(kwargs))]) ln += len(kw) + 1 if ln + x >= w: ty = self.list_win.getbegyx()[0] if not down and ty > 0: h += 1 self.list_win.mvwin(ty - 1, 1) self.list_win.resize(h, w) elif down and h + r < maxh - ty: h += 1 self.list_win.resize(h, w) else: break r += 1 self.list_win.addstr('\n\t') if str(i) == 'self' and k == 0: color = get_colpair(self.config, 'name') else: color = get_colpair(self.config, 'token') if k == in_arg or i == in_arg: color |= curses.A_BOLD if not py3: # See issue #138: We need to format tuple unpacking correctly # We use the undocumented function inspection.strseq() for # that. Fortunately, that madness is gone in Python 3. self.list_win.addstr(inspect.strseq(i, str), color) else: self.list_win.addstr(str(i), color) if kw is not None: self.list_win.addstr('=', punctuation_colpair) self.list_win.addstr(kw, get_colpair(self.config, 'token')) if k != len(args) -1: self.list_win.addstr(', ', punctuation_colpair) if _args: if args: self.list_win.addstr(', ', punctuation_colpair) self.list_win.addstr('*%s' % (_args, ), get_colpair(self.config, 'token')) if py3 and kwonly: if not _args: if args: self.list_win.addstr(', ', punctuation_colpair) self.list_win.addstr('*', punctuation_colpair) marker = object() for arg in kwonly: self.list_win.addstr(', ', punctuation_colpair) color = get_colpair(self.config, 'token') if arg == in_arg: color |= curses.A_BOLD self.list_win.addstr(arg, color) default = kwonly_defaults.get(arg, marker) if default is not marker: self.list_win.addstr('=', punctuation_colpair) self.list_win.addstr(repr(default), get_colpair(self.config, 'token')) if _kwargs: if args or _args or (py3 and kwonly): self.list_win.addstr(', ', punctuation_colpair) self.list_win.addstr('**%s' % (_kwargs, ), get_colpair(self.config, 'token')) self.list_win.addstr(')', punctuation_colpair) return r def mvc(self, i, refresh=True): """This method moves the cursor relatively from the current position, where: 0 == (right) end of current line length of current line len(self.s) == beginning of current line and: current cursor position + i for positive values of i the cursor will move towards the beginning of the line, negative values the opposite.""" y, x = self.scr.getyx() if self.cpos == 0 and i < 0: return False if x == self.ix and y == self.iy and i >= 1: return False h, w = gethw() if x - i < 0: y -= 1 x = w if x - i >= w: y += 1 x = 0 + i self.cpos += i self.scr.move(y, x - i) if refresh: self.scr.refresh() return True def p_key(self, key): """Process a keypress""" if key is None: return '' config = self.config if platform.system() == 'Windows': C_BACK = chr(127) BACKSP = chr(8) else: C_BACK = chr(8) BACKSP = chr(127) if key == C_BACK: # C-Backspace (on my computer anyway!) self.clrtobol() key = '\n' # Don't return; let it get handled if key == chr(27): #Escape Key return '' if key in (BACKSP, 'KEY_BACKSPACE'): self.bs() self.complete() return '' elif key in key_dispatch[config.delete_key] and not self.s: # Delete on empty line exits self.do_exit = True return None elif key in ('KEY_DC', ) + key_dispatch[config.delete_key]: self.delete() self.complete() # Redraw (as there might have been highlighted parens) self.print_line(self.s) return '' elif key in key_dispatch[config.undo_key]: # C-r n = self.prompt_undo() if n > 0: self.undo(n=n) return '' elif key in key_dispatch[config.search_key]: self.search() return '' elif key in ('KEY_UP', ) + key_dispatch[config.up_one_line_key]: # Cursor Up/C-p self.back() return '' elif key in ('KEY_DOWN', ) + key_dispatch[config.down_one_line_key]: # Cursor Down/C-n self.fwd() return '' elif key in ("KEY_LEFT",' ^B', chr(2)): # Cursor Left or ^B self.mvc(1) # Redraw (as there might have been highlighted parens) self.print_line(self.s) elif key in ("KEY_RIGHT", '^F', chr(6)): # Cursor Right or ^F self.mvc(-1) # Redraw (as there might have been highlighted parens) self.print_line(self.s) elif key in ("KEY_HOME", '^A', chr(1)): # home or ^A self.home() # Redraw (as there might have been highlighted parens) self.print_line(self.s) elif key in ("KEY_END", '^E', chr(5)): # end or ^E self.end() # Redraw (as there might have been highlighted parens) self.print_line(self.s) elif key in ("KEY_NPAGE", '\T'): # page_down or \T self.hend() self.print_line(self.s) elif key in ("KEY_PPAGE", '\S'): # page_up or \S self.hbegin() self.print_line(self.s) elif key in key_dispatch[config.cut_to_buffer_key]: # cut to buffer self.cut_to_buffer() return '' elif key in key_dispatch[config.yank_from_buffer_key]: # yank from buffer self.yank_from_buffer() return '' elif key in key_dispatch[config.clear_word_key]: self.cut_buffer = self.bs_word() self.complete() return '' elif key in key_dispatch[config.clear_line_key]: self.clrtobol() return '' elif key in key_dispatch[config.clear_screen_key]: self.s_hist = [self.s_hist[-1]] self.highlighted_paren = None self.redraw() return '' elif key in key_dispatch[config.exit_key]: if not self.s: self.do_exit = True return None else: return '' elif key in key_dispatch[config.save_key]: self.write2file() return '' elif key in key_dispatch[config.pastebin_key]: self.pastebin() return '' elif key in key_dispatch[config.copy_clipboard_key]: self.copy2clipboard() return '' elif key in key_dispatch[config.last_output_key]: page(self.stdout_hist[self.prev_block_finished:-4]) return '' elif key in key_dispatch[config.show_source_key]: try: source = self.get_source_of_current_name() except repl.SourceNotFound as e: self.statusbar.message('%s' % (e, )) else: if config.highlight_show_source: source = format(PythonLexer().get_tokens(source), TerminalFormatter()) page(source) return '' elif key in ('\n', '\r', 'PADENTER'): self.lf() return None elif key == '\t': return self.tab() elif key == 'KEY_BTAB': return self.tab(back=True) elif key in key_dispatch[config.suspend_key]: if platform.system() != 'Windows': self.suspend() return '' else: self.do_exit = True return None elif key == '\x18': return self.send_current_line_to_editor() elif key == '\x03': raise KeyboardInterrupt() elif key[0:3] == 'PAD' and not key in ('PAD0', 'PADSTOP'): pad_keys = { 'PADMINUS': '-', 'PADPLUS': '+', 'PADSLASH': '/', 'PADSTAR': '*', } try: self.addstr(pad_keys[key]) self.print_line(self.s) except KeyError: return '' elif len(key) == 1 and not unicodedata.category(key) == 'Cc': self.addstr(key) self.print_line(self.s) else: return '' return True def print_line(self, s, clr=False, newline=False): """Chuck a line of text through the highlighter, move the cursor to the beginning of the line and output it to the screen.""" if not s: clr = True if self.highlighted_paren is not None: # Clear previous highlighted paren self.reprint_line(*self.highlighted_paren) self.highlighted_paren = None if self.config.syntax and (not self.paste_mode or newline): o = format(self.tokenize(s, newline), self.formatter) else: o = s self.f_string = o self.scr.move(self.iy, self.ix) if clr: self.scr.clrtoeol() if clr and not s: self.scr.refresh() if o: for t in o.split('\x04'): self.echo(t.rstrip('\n')) if self.cpos: t = self.cpos for _ in range(self.cpos): self.mvc(1) self.cpos = t def prompt(self, more): """Show the appropriate Python prompt""" if not more: self.echo("\x01%s\x03%s" % (self.config.color_scheme['prompt'], self.ps1)) self.stdout_hist += self.ps1 self.s_hist.append('\x01%s\x03%s\x04' % (self.config.color_scheme['prompt'], self.ps1)) else: prompt_more_color = self.config.color_scheme['prompt_more'] self.echo("\x01%s\x03%s" % (prompt_more_color, self.ps2)) self.stdout_hist += self.ps2 self.s_hist.append('\x01%s\x03%s\x04' % (prompt_more_color, self.ps2)) def push(self, s, insert_into_history=True): # curses.raw(True) prevents C-c from causing a SIGINT curses.raw(False) try: return repl.Repl.push(self, s, insert_into_history) except SystemExit as e: # Avoid a traceback on e.g. quit() self.do_exit = True self.exit_value = e.args return False finally: curses.raw(True) def redraw(self): """Redraw the screen.""" self.scr.erase() for k, s in enumerate(self.s_hist): if not s: continue self.iy, self.ix = self.scr.getyx() for i in s.split('\x04'): self.echo(i, redraw=False) if k < len(self.s_hist) -1: self.scr.addstr('\n') self.iy, self.ix = self.scr.getyx() self.print_line(self.s) self.scr.refresh() self.statusbar.refresh() def repl(self): """Initialise the repl and jump into the loop. This method also has to keep a stack of lines entered for the horrible "undo" feature. It also tracks everything that would normally go to stdout in the normal Python interpreter so it can quickly write it to stdout on exit after curses.endwin(), as well as a history of lines entered for using up/down to go back and forth (which has to be separate to the evaluation history, which will be truncated when undoing.""" # Use our own helper function because Python's will use real stdin and # stdout instead of our wrapped self.push('from bpython._internal import _help as help\n', False) self.iy, self.ix = self.scr.getyx() self.more = False while not self.do_exit: self.f_string = '' self.prompt(self.more) try: inp = self.get_line() except KeyboardInterrupt: self.statusbar.message('KeyboardInterrupt') self.scr.addstr('\n') self.scr.touchwin() self.scr.refresh() continue self.scr.redrawwin() if self.do_exit: return self.exit_value self.history.append(inp) self.s_hist[-1] += self.f_string if py3: self.stdout_hist += inp + '\n' else: self.stdout_hist += inp.encode(getpreferredencoding()) + '\n' stdout_position = len(self.stdout_hist) self.more = self.push(inp) if not self.more: self.prev_block_finished = stdout_position self.s = '' return self.exit_value def reprint_line(self, lineno, tokens): """Helper function for paren highlighting: Reprint line at offset `lineno` in current input buffer.""" if not self.buffer or lineno == len(self.buffer): return real_lineno = self.iy height, width = self.scr.getmaxyx() for i in range(lineno, len(self.buffer)): string = self.buffer[i] # 4 = length of prompt length = len(string.encode(getpreferredencoding())) + 4 real_lineno -= int(math.ceil(length / width)) if real_lineno < 0: return self.scr.move(real_lineno, len(self.ps1) if lineno == 0 else len(self.ps2)) line = format(tokens, BPythonFormatter(self.config.color_scheme)) for string in line.split('\x04'): self.echo(string) def resize(self): """This method exists simply to keep it straight forward when initialising a window and resizing it.""" self.size() self.scr.erase() self.scr.resize(self.h, self.w) self.scr.mvwin(self.y, self.x) self.statusbar.resize(refresh=False) self.redraw() def getstdout(self): """This method returns the 'spoofed' stdout buffer, for writing to a file or sending to a pastebin or whatever.""" return self.stdout_hist + '\n' def reevaluate(self): """Clear the buffer, redraw the screen and re-evaluate the history""" self.evaluating = True self.stdout_hist = '' self.f_string = '' self.buffer = [] self.scr.erase() self.s_hist = [] # Set cursor position to -1 to prevent paren matching self.cpos = -1 self.prompt(False) self.iy, self.ix = self.scr.getyx() for line in self.history: if py3: self.stdout_hist += line + '\n' else: self.stdout_hist += line.encode(getpreferredencoding()) + '\n' self.print_line(line) self.s_hist[-1] += self.f_string # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. self.scr.addstr('\n') self.more = self.push(line) self.prompt(self.more) self.iy, self.ix = self.scr.getyx() self.cpos = 0 indent = repl.next_indentation(self.s, self.config.tab_length) self.s = '' self.scr.refresh() if self.buffer: for _ in range(indent): self.tab() self.evaluating = False #map(self.push, self.history) #^-- That's how simple this method was at first :( def write(self, s): """For overriding stdout defaults""" if '\x04' in s: for block in s.split('\x04'): self.write(block) return if s.rstrip() and '\x03' in s: t = s.split('\x03')[1] else: t = s if not py3 and isinstance(t, unicode): t = t.encode(getpreferredencoding()) if not self.stdout_hist: self.stdout_hist = t else: self.stdout_hist += t self.echo(s) self.s_hist.append(s.rstrip()) def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None): shared = Struct() shared.cols = 0 shared.rows = 0 shared.wl = 0 y, x = self.scr.getyx() h, w = self.scr.getmaxyx() down = (y < h // 2) if down: max_h = h - y else: max_h = y + 1 max_w = int(w * self.config.cli_suggestion_width) self.list_win.erase() if items: items = [formatter(x) for x in items] if current_item: current_item = formatter(current_item) if topline: height_offset = self.mkargspec(topline, arg_pos, down) + 1 else: height_offset = 0 def lsize(): wl = max(len(i) for i in v_items) + 1 if not wl: wl = 1 cols = ((max_w - 2) // wl) or 1 rows = len(v_items) // cols if cols * rows < len(v_items): rows += 1 if rows + 2 >= max_h: rows = max_h - 2 return False shared.rows = rows shared.cols = cols shared.wl = wl return True if items: # visible items (we'll append until we can't fit any more in) v_items = [items[0][:max_w - 3]] lsize() else: v_items = [] for i in items[1:]: v_items.append(i[:max_w - 3]) if not lsize(): del v_items[-1] v_items[-1] = '...' break rows = shared.rows if rows + height_offset < max_h: rows += height_offset display_rows = rows else: display_rows = rows + height_offset cols = shared.cols wl = shared.wl if topline and not v_items: w = max_w elif wl + 3 > max_w: w = max_w else: t = (cols + 1) * wl + 3 if t > max_w: t = max_w w = t if height_offset and display_rows + 5 >= max_h: del v_items[-(cols * (height_offset)):] if self.docstring is None: self.list_win.resize(rows + 2, w) else: docstring = self.format_docstring(self.docstring, max_w - 2, max_h - height_offset) docstring_string = ''.join(docstring) rows += len(docstring) self.list_win.resize(rows, max_w) if down: self.list_win.mvwin(y + 1, 0) else: self.list_win.mvwin(y - rows - 2, 0) if v_items: self.list_win.addstr('\n ') if not py3: encoding = getpreferredencoding() for ix, i in enumerate(v_items): padding = (wl - len(i)) * ' ' if i == current_item: color = get_colpair(self.config, 'operator') else: color = get_colpair(self.config, 'main') if not py3: i = i.encode(encoding) self.list_win.addstr(i + padding, color) if ((cols == 1 or (ix and not (ix + 1) % cols)) and ix + 1 < len(v_items)): self.list_win.addstr('\n ') if self.docstring is not None: if not py3 and isinstance(docstring_string, unicode): docstring_string = docstring_string.encode(encoding, 'ignore') self.list_win.addstr('\n' + docstring_string, get_colpair(self.config, 'comment')) # XXX: After all the trouble I had with sizing the list box (I'm not very good # at that type of thing) I decided to do this bit of tidying up here just to # make sure there's no unnecessary blank lines, it makes things look nicer. y = self.list_win.getyx()[0] self.list_win.resize(y + 2, w) self.statusbar.win.touchwin() self.statusbar.win.noutrefresh() self.list_win.attron(get_colpair(self.config, 'main')) self.list_win.border() self.scr.touchwin() self.scr.cursyncup() self.scr.noutrefresh() # This looks a little odd, but I can't figure a better way to stick the cursor # back where it belongs (refreshing the window hides the list_win) self.scr.move(*self.scr.getyx()) self.list_win.refresh() def size(self): """Set instance attributes for x and y top left corner coordinates and width and height for the window.""" global stdscr h, w = stdscr.getmaxyx() self.y = 0 self.w = w self.h = h - 1 self.x = 0 def suspend(self): """Suspend the current process for shell job control.""" if platform.system() != 'Windows': curses.endwin() os.kill(os.getpid(), signal.SIGSTOP) def tab(self, back=False): """Process the tab key being hit. If there's only whitespace in the line or the line is blank then process a normal tab, otherwise attempt to autocomplete to the best match of possible choices in the match list. If `back` is True, walk backwards through the list of suggestions and don't indent if there are only whitespace in the line. """ # 1. check if we should add a tab character if self.atbol() and not back: x_pos = len(self.s) - self.cpos num_spaces = x_pos % self.config.tab_length if not num_spaces: num_spaces = self.config.tab_length self.addstr(' ' * num_spaces) self.print_line(self.s) return True # 2. run complete() if we aren't already iterating through matches if not self.matches_iter: self.complete(tab=True) self.print_line(self.s) # 3. check to see if we can expand the current word if self.matches_iter.is_cseq(): #TODO resolve this error-prone situation: # can't assign at same time to self.s and self.cursor_offset # because for cursor_offset # property to work correctly, self.s must already be set temp_cursor_offset, self.s = self.matches_iter.substitute_cseq() self.cursor_offset = temp_cursor_offset self.print_line(self.s) if not self.matches_iter: self.complete() # 4. swap current word for a match list item elif self.matches_iter.matches: current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: 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: # XXX: This is a massive hack, it will go away when I get # cusswords into a good enough state that we can start # using it. self.list_win.border() self.list_win.refresh() _, self.s = self.matches_iter.cur_line() self.print_line(self.s, True) return True def undo(self, n=1): repl.Repl.undo(self, n) # This will unhighlight highlighted parens self.print_line(self.s) def writetb(self, lines): for line in lines: self.write('\x01%s\x03%s' % (self.config.color_scheme['error'], line)) def yank_from_buffer(self): """Paste the text from the cut buffer at the current cursor location""" self.addstr(self.cut_buffer) self.print_line(self.s, clr=True) def send_current_line_to_editor(self): lines = self.send_to_external_editor(self.s).split('\n') self.s = '' self.print_line(self.s) while lines and not lines[-1]: lines.pop() if not lines: return '' self.f_string = '' self.cpos = -1 # Set cursor position to -1 to prevent paren matching self.iy, self.ix = self.scr.getyx() self.evaluating = True for line in lines: if py3: self.stdout_hist += line + '\n' else: self.stdout_hist += line.encode(getpreferredencoding()) + '\n' self.history.append(line) self.print_line(line) self.s_hist[-1] += self.f_string self.scr.addstr('\n') self.more = self.push(line) self.prompt(self.more) self.iy, self.ix = self.scr.getyx() self.evaluating = False self.cpos = 0 indent = repl.next_indentation(self.s, self.config.tab_length) self.s = '' self.scr.refresh() if self.buffer: for _ in range(indent): self.tab() self.print_line(self.s) self.scr.redrawwin() return '' class Statusbar(object): """This class provides the status bar at the bottom of the screen. It has message() and prompt() methods for user interactivity, as well as settext() and clear() methods for changing its appearance. The check() method needs to be called repeatedly if the statusbar is going to be aware of when it should update its display after a message() has been called (it'll display for a couple of seconds and then disappear). It should be called as: foo = Statusbar(stdscr, scr, 'Initial text to display') or, for a blank statusbar: foo = Statusbar(stdscr, scr) It can also receive the argument 'c' which will be an integer referring to a curses colour pair, e.g.: foo = Statusbar(stdscr, 'Hello', c=4) stdscr should be a curses window object in which to put the status bar. pwin should be the parent window. To be honest, this is only really here so the cursor can be returned to the window properly. """ def __init__(self, scr, pwin, background, config, s=None, c=None): """Initialise the statusbar and display the initial text (if any)""" self.size() self.win = newwin(background, self.h, self.w, self.y, self.x) self.config = config self.s = s or '' self._s = self.s self.c = c self.timer = 0 self.pwin = pwin self.settext(s, c) def size(self): """Set instance attributes for x and y top left corner coordinates and width and height for the window.""" h, w = gethw() self.y = h - 1 self.w = w self.h = 1 self.x = 0 def resize(self, refresh=True): """This method exists simply to keep it straight forward when initialising a window and resizing it.""" self.size() self.win.mvwin(self.y, self.x) self.win.resize(self.h, self.w) if refresh: self.refresh() def refresh(self): """This is here to make sure the status bar text is redraw properly after a resize.""" self.settext(self._s) def check(self): """This is the method that should be called every half second or so to see if the status bar needs updating.""" if not self.timer: return if time.time() < self.timer: return self.settext(self._s) def message(self, s, n=3): """Display a message for a short n seconds on the statusbar and return it to its original state.""" self.timer = time.time() + n self.settext(s) def prompt(self, s=''): """Prompt the user for some input (with the optional prompt 's') and return the input text, then restore the statusbar to its original value.""" self.settext(s or '? ', p=True) iy, ix = self.win.getyx() def bs(s): y, x = self.win.getyx() if x == ix: return s s = s[:-1] self.win.delch(y, x - 1) self.win.move(y, x - 1) return s o = '' while True: c = self.win.getch() # '\b' if c == 127: o = bs(o) # '\n' elif c == 10: break # ESC elif c == 27: curses.flushinp() raise ValueError # literal elif 0 < c < 127: c = chr(c) self.win.addstr(c, get_colpair(self.config, 'prompt')) o += c self.settext(self._s) return o def settext(self, s, c=None, p=False): """Set the text on the status bar to a new permanent value; this is the value that will be set after a prompt or message. c is the optional curses colour pair to use (if not specified the last specified colour pair will be used). p is True if the cursor is expected to stay in the status window (e.g. when prompting).""" self.win.erase() if len(s) >= self.w: s = s[:self.w - 1] self.s = s if c: self.c = c if s: if not py3 and isinstance(s, unicode): s = s.encode(getpreferredencoding()) if self.c: self.win.addstr(s, self.c) else: self.win.addstr(s) if not p: self.win.noutrefresh() self.pwin.refresh() else: self.win.refresh() def clear(self): """Clear the status bar.""" self.win.clear() def init_wins(scr, config): """Initialise the two windows (the main repl interface and the little status bar at the bottom with some stuff in it)""" #TODO: Document better what stuff is on the status bar. background = get_colpair(config, 'background') h, w = gethw() main_win = newwin(background, h - 1, w, 0, 0) main_win.scrollok(True) main_win.keypad(1) # Thanks to Angus Gibson for pointing out this missing line which was causing # problems that needed dirty hackery to fix. :) commands = ( (_('Rewind'), config.undo_key), (_('Save'), config.save_key), (_('Pastebin'), config.pastebin_key), (_('Pager'), config.last_output_key), (_('Show Source'), config.show_source_key) ) message = ' '.join('<%s> %s' % (key, command) for command, key in commands if key) statusbar = Statusbar(scr, main_win, background, config, message, get_colpair(config, 'main')) return main_win, statusbar def sigwinch(unused_scr): global DO_RESIZE DO_RESIZE = True def sigcont(unused_scr): sigwinch(unused_scr) # Forces the redraw curses.ungetch('\x00') def gethw(): """I found this code on a usenet post, and snipped out the bit I needed, so thanks to whoever wrote that, sorry I forgot your name, I'm sure you're a great guy. It's unfortunately necessary (unless someone has any better ideas) in order to allow curses and readline to work together. I looked at the code for libreadline and noticed this comment: /* This is the stuff that is hard for me. I never seem to write good display routines in C. Let's see how I do this time. */ So I'm not going to ask any questions. """ if platform.system() != 'Windows': h, w = struct.unpack( "hhhh", fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8))[0:2] else: from ctypes import windll, create_string_buffer # stdin handle is -10 # stdout handle is -11 # stderr handle is -12 h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 else: sizex, sizey = stdscr.getmaxyx()# can't determine actual size - return default values h, w = sizey, sizex return h, w def idle(caller): """This is called once every iteration through the getkey() loop (currently in the Repl class, see the get_line() method). The statusbar check needs to go here to take care of timed messages and the resize handlers need to be here to make sure it happens conveniently.""" global DO_RESIZE if importcompletion.find_coroutine() or caller.paste_mode: caller.scr.nodelay(True) key = caller.scr.getch() caller.scr.nodelay(False) if key != -1: curses.ungetch(key) else: curses.ungetch('\x00') caller.statusbar.check() caller.check() if DO_RESIZE: do_resize(caller) def do_resize(caller): """This needs to hack around readline and curses not playing nicely together. See also gethw() above.""" global DO_RESIZE h, w = gethw() if not h: # Hopefully this shouldn't happen. :) return curses.endwin() os.environ["LINES"] = str(h) os.environ["COLUMNS"] = str(w) curses.doupdate() DO_RESIZE = False try: caller.resize() except curses.error: pass # The list win resizes itself every time it appears so no need to do it here. class FakeDict(object): """Very simple dict-alike that returns a constant value for any key - used as a hacky solution to using a colours dict containing colour codes if colour initialisation fails.""" def __init__(self, val): self._val = val def __getitem__(self, k): return self._val def newwin(background, *args): """Wrapper for curses.newwin to automatically set background colour on any newly created window.""" win = curses.newwin(*args) win.bkgd(' ', background) return win def curses_wrapper(func, *args, **kwargs): """Like curses.wrapper(), but reuses stdscr when called again.""" global stdscr if stdscr is None: stdscr = curses.initscr() try: curses.noecho() curses.cbreak() stdscr.keypad(1) try: curses.start_color() except curses.error: pass return func(stdscr, *args, **kwargs) finally: stdscr.keypad(0) curses.echo() curses.nocbreak() curses.endwin() def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): """main function for the curses convenience wrapper Initialise the two main objects: the interpreter and the repl. The repl does what a repl does and lots of other cool stuff like syntax highlighting and stuff. I've tried to keep it well factored but it needs some tidying up, especially in separating the curses stuff from the rest of the repl. Returns a tuple (exit value, output), where exit value is a tuple with arguments passed to SystemExit. """ global stdscr global DO_RESIZE global colors DO_RESIZE = False if platform.system() != 'Windows': old_sigwinch_handler = signal.signal(signal.SIGWINCH, lambda *_: sigwinch(scr)) # redraw window after being suspended old_sigcont_handler = signal.signal(signal.SIGCONT, lambda *_: sigcont(scr)) stdscr = scr try: curses.start_color() curses.use_default_colors() cols = make_colors(config) except curses.error: cols = FakeDict(-1) # FIXME: Gargh, bad design results in using globals without a refactor :( colors = cols scr.timeout(300) curses.raw(True) main_win, statusbar = init_wins(scr, config) interpreter = repl.Interpreter(locals_, getpreferredencoding()) clirepl = CLIRepl(main_win, interpreter, statusbar, config, idle) clirepl._C = cols sys.stdin = FakeStdin(clirepl) sys.stdout = FakeStream(clirepl, lambda: sys.stdout) sys.stderr = FakeStream(clirepl, lambda: sys.stderr) if args: exit_value = () try: bpython.args.exec_code(interpreter, args) except SystemExit as e: # The documentation of code.InteractiveInterpreter.runcode claims # that it reraises SystemExit. However, I can't manage to trigger # that. To be one the safe side let's catch SystemExit here anyway. exit_value = e.args if not interactive: curses.raw(False) return (exit_value, clirepl.getstdout()) else: sys.path.insert(0, '') try: clirepl.startup() except OSError as e: # Handle this with a proper error message. if e.errno != errno.ENOENT: raise if banner is not None: clirepl.write(banner) clirepl.write('\n') exit_value = clirepl.repl() if hasattr(sys, 'exitfunc'): sys.exitfunc() delattr(sys, 'exitfunc') main_win.erase() main_win.refresh() statusbar.win.clear() statusbar.win.refresh() curses.raw(False) # Restore signal handlers if platform.system() != 'Windows': signal.signal(signal.SIGWINCH, old_sigwinch_handler) signal.signal(signal.SIGCONT, old_sigcont_handler) return (exit_value, clirepl.getstdout()) def main(args=None, locals_=None, banner=None): translations.init() config, options, exec_args = argsparse(args) # Save stdin, stdout and stderr for later restoration orig_stdin = sys.stdin orig_stdout = sys.stdout orig_stderr = sys.stderr try: (exit_value, output) = curses_wrapper( main_curses, exec_args, config, options.interactive, locals_, banner=banner) finally: sys.stdin = orig_stdin sys.stderr = orig_stderr sys.stdout = orig_stdout # Fake stdout data so everything's still visible after exiting if config.flush_output and not options.quiet: sys.stdout.write(output) if hasattr(sys.stdout, 'flush'): sys.stdout.flush() return repl.extract_exit_value(exit_value) if __name__ == '__main__': sys.exit(main()) # vim: sw=4 ts=4 sts=4 ai et bpython-0.17.1/bpython/_py3compat.py0000644000175100017510000000424313240410473017357 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2012 the bpython authors. # Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Helper module for Python 3 compatibility. Defines the following attributes: - PythonLexer: Pygment's Python lexer matching the hosting runtime's Python version. - py3: True if the hosting Python runtime is of Python version 3 or later """ from __future__ import absolute_import import sys py3 = (sys.version_info[0] == 3) if py3: from pygments.lexers import Python3Lexer as PythonLexer else: from pygments.lexers import PythonLexer if py3 or sys.version_info[:3] >= (2, 7, 3): def prepare_for_exec(arg, encoding=None): return arg else: def prepare_for_exec(arg, encoding=None): return arg.encode(encoding) if py3: def try_decode(s, encoding): return s else: def try_decode(s, encoding): """Try to decode s which is str names. Return None if not decodable""" if not isinstance(s, unicode): try: return s.decode(encoding) except UnicodeDecodeError: return None return s bpython-0.17.1/bpython/_internal.py0000644000175100017510000000123613240407731017256 0ustar useruser00000000000000# encoding: utf-8 from __future__ import absolute_import import pydoc import sys from .pager import page # Ugly monkeypatching pydoc.pager = page class _Helper(object): def __init__(self): if hasattr(pydoc.Helper, "output"): # See issue #228 self.helper = pydoc.Helper(sys.stdin, None) else: self.helper = pydoc.Helper(sys.stdin, sys.stdout) def __repr__(self): return ("Type help() for interactive help, " "or help(object) for help about object.") def __call__(self, *args, **kwargs): self.helper(*args, **kwargs) _help = _Helper() # vim: sw=4 ts=4 sts=4 ai et bpython-0.17.1/bpython/curtsies.py0000644000175100017510000001756213240407731017155 0ustar useruser00000000000000# encoding: utf-8 from __future__ import absolute_import import collections import io import logging import sys from optparse import Option import curtsies import curtsies.window import curtsies.input import curtsies.events from .curtsiesfrontend.repl import BaseRepl from .curtsiesfrontend.coderunner import SystemExitFromCodeRunner from .curtsiesfrontend.interpreter import Interp from . import args as bpargs from . import translations from .translations import _ from .importcompletion import find_iterator from .curtsiesfrontend import events as bpythonevents from . import inspection from .repl import extract_exit_value logger = logging.getLogger(__name__) repl = None # global for `from bpython.curtsies import repl` # WARNING Will be a problem if more than one repl is ever instantiated this way class FullCurtsiesRepl(BaseRepl): def __init__(self, config, locals_, banner, interp=None): self.input_generator = curtsies.input.Input( keynames='curtsies', sigint_event=True, paste_threshold=None) self.window = curtsies.window.CursorAwareWindow( sys.stdout, sys.stdin, keep_last_line=True, hide_cursor=False, extra_bytes_callback=self.input_generator.unget_bytes) self._request_refresh = self.input_generator.event_trigger( bpythonevents.RefreshRequestEvent) self._schedule_refresh = self.input_generator.scheduled_event_trigger( bpythonevents.ScheduledRefreshRequestEvent) self._request_reload = self.input_generator.threadsafe_event_trigger( bpythonevents.ReloadEvent) self.interrupting_refresh = (self.input_generator .threadsafe_event_trigger(lambda: None)) self.request_undo = self.input_generator.event_trigger( bpythonevents.UndoEvent) with self.input_generator: pass # temp hack to get .original_stty BaseRepl.__init__(self, locals_=locals_, config=config, banner=banner, interp=interp, orig_tcattrs=self.input_generator.original_stty) def get_term_hw(self): return self.window.get_term_hw() def get_cursor_vertical_diff(self): return self.window.get_cursor_vertical_diff() def get_top_usable_line(self): return self.window.top_usable_row def on_suspend(self): self.window.__exit__(None, None, None) self.input_generator.__exit__(None, None, None) def after_suspend(self): self.input_generator.__enter__() self.window.__enter__() self.interrupting_refresh() def process_event_and_paint(self, e): """If None is passed in, just paint the screen""" try: if e is not None: self.process_event(e) except (SystemExitFromCodeRunner, SystemExit) as err: array, cursor_pos = self.paint( about_to_exit=True, user_quit=isinstance(err, SystemExitFromCodeRunner)) scrolled = self.window.render_to_terminal(array, cursor_pos) self.scroll_offset += scrolled raise else: array, cursor_pos = self.paint() scrolled = self.window.render_to_terminal(array, cursor_pos) self.scroll_offset += scrolled def mainloop(self, interactive=True, paste=None): if interactive: # Add custom help command # TODO: add methods to run the code self.initialize_interp() # run startup file self.process_event(bpythonevents.RunStartupFileEvent()) # handle paste if paste: self.process_event(paste) # do a display before waiting for first event self.process_event_and_paint(None) inputs = combined_events(self.input_generator) for unused in find_iterator: e = inputs.send(0) if e is not None: self.process_event_and_paint(e) for e in inputs: self.process_event_and_paint(e) 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, ( 'curtsies options', None, [ Option('--log', '-L', action='count', help=_("log debug messages to bpython.log")), Option('--paste', '-p', action='store_true', help=_("start by pasting lines of a file into session")), ])) if options.log is None: options.log = 0 logging_levels = [logging.ERROR, logging.INFO, logging.DEBUG] level = logging_levels[min(len(logging_levels) - 1, options.log)] logging.getLogger('curtsies').setLevel(level) logging.getLogger('bpython').setLevel(level) if options.log: handler = logging.FileHandler(filename='bpython.log') logging.getLogger('curtsies').addHandler(handler) logging.getLogger('curtsies').propagate = False logging.getLogger('bpython').addHandler(handler) logging.getLogger('bpython').propagate = False interp = None paste = None if exec_args: if not options: raise ValueError("don't pass in exec_args without options") exit_value = () if options.paste: paste = curtsies.events.PasteEvent() encoding = inspection.get_encoding_file(exec_args[0]) with io.open(exec_args[0], encoding=encoding) as f: sourcecode = f.read() paste.events.extend(sourcecode) else: try: interp = Interp(locals=locals_) bpargs.exec_code(interp, exec_args) except SystemExit as e: exit_value = e.args if not options.interactive: return extract_exit_value(exit_value) else: # expected for interactive sessions (vanilla python does it) sys.path.insert(0, '') if not options.quiet: print(bpargs.version_banner()) if banner is not None: print(banner) global repl repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) try: with repl.input_generator: with repl.window as win: with repl: repl.height, repl.width = win.t.height, win.t.width exit_value = repl.mainloop(True, paste) except (SystemExitFromCodeRunner, SystemExit) as e: exit_value = e.args return extract_exit_value(exit_value) def _combined_events(event_provider, paste_threshold): """Combines consecutive keypress events into paste events.""" timeout = yield 'nonsense_event' # so send can be used immediately queue = collections.deque() while True: e = event_provider.send(timeout) if isinstance(e, curtsies.events.Event): timeout = yield e continue elif e is None: timeout = yield None continue else: queue.append(e) e = event_provider.send(0) while not (e is None or isinstance(e, curtsies.events.Event)): queue.append(e) e = event_provider.send(0) if len(queue) >= paste_threshold: paste = curtsies.events.PasteEvent() paste.events.extend(queue) queue.clear() timeout = yield paste else: while len(queue): timeout = yield queue.popleft() def combined_events(event_provider, paste_threshold=3): g = _combined_events(event_provider, paste_threshold) next(g) return g if __name__ == '__main__': sys.exit(main()) bpython-0.17.1/bpython/lazyre.py0000644000175100017510000000420213240407731016605 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import re class LazyReCompile(object): """Compile regular expressions on first use This class allows one to store regular expressions and compiles them on first use.""" def __init__(self, regex, flags=0): self.regex = regex self.flags = flags self.compiled = None def compile_regex(method): def _impl(self, *args, **kwargs): if self.compiled is None: self.compiled = re.compile(self.regex, self.flags) return method(self, *args, **kwargs) return _impl @compile_regex def finditer(self, *args, **kwargs): return self.compiled.finditer(*args, **kwargs) @compile_regex def search(self, *args, **kwargs): return self.compiled.search(*args, **kwargs) @compile_regex def match(self, *args, **kwargs): return self.compiled.match(*args, **kwargs) @compile_regex def sub(self, *args, **kwargs): return self.compiled.sub(*args, **kwargs) bpython-0.17.1/bpython/__init__.py0000644000175100017510000000270213240407731017041 0ustar useruser00000000000000# The MIT License # # Copyright (c) 2008 Bob Farrell # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import os.path try: from ._version import __version__ as version except ImportError: version = 'unknown' __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) def embed(locals_=None, args=['-i', '-q'], banner=None): from .curtsies import main return main(args, locals_, banner) bpython-0.17.1/bpython/simplerepl.py0000644000175100017510000001064613240407731017464 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 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 # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """An example bpython repl without a nice UI for testing and to demonstrate the methods of bpython.curtsiesrepl.repl.BaseRepl that must be overridden. """ from __future__ import unicode_literals, print_function, absolute_import import time import logging from .curtsiesfrontend.repl import BaseRepl from .curtsiesfrontend import events as bpythonevents from . import translations from . import importcompletion from curtsies.configfile_keynames import keymap as key_dispatch logger = logging.getLogger(__name__) class SimpleRepl(BaseRepl): def __init__(self): self.requested_events = [] BaseRepl.__init__(self) def _request_refresh(self): self.requested_events.append(bpythonevents.RefreshRequestEvent()) def _schedule_refresh(self, when='now'): if when == 'now': self.request_refresh() else: dt = round(when - time.time(), 1) self.out('please refresh in {} seconds'.format(dt)) def _request_reload(self, files_modified=('?',)): e = bpythonevents.ReloadEvent() e.files_modified = files_modified self.requested_events.append(e) self.out('please hit enter to trigger a refresh') def request_undo(self, n=1): self.requested_events.append(bpythonevents.UndoEvent(n=n)) def out(self, msg): if hasattr(self, 'orig_stdout'): self.orig_stdout.write((msg + '\n').encode('utf8')) self.orig_stdout.flush() else: print(msg) def on_suspend(self): pass def after_suspend(self): self.out('please hit enter to trigger a refresh') def print_output(self): arr, cpos = self.paint() arr[cpos[0]:cpos[0] + 1, cpos[1]:cpos[1] + 1] = ['~'] def print_padded(s): return self.out(s.center(self.width + 8, 'X')) print_padded('') print_padded(' enter -> "/", rewind -> "\\", ') print_padded(' reload -> "|", pastebin -> "$", ') print_padded(' "~" is the cursor ') print_padded('') self.out('X``' + ('`' * (self.width + 2)) + '``X') for line in arr: self.out('X```' + unicode(line.ljust(self.width)) + '```X') logger.debug('line:') logger.debug(repr(line)) self.out('X``' + ('`' * (self.width + 2)) + '``X') self.out('X' * (self.width + 8)) return max(len(arr) - self.height, 0) def get_input(self): chars = list(self.orig_stdin.readline()[:-1]) while chars or self.requested_events: if self.requested_events: self.process_event(self.requested_events.pop()) continue c = chars.pop(0) if c in '/': c = '\n' elif c in '\\': c = key_dispatch[self.config.undo_key][0] elif c in '$': c = key_dispatch[self.config.pastebin_key][0] elif c in '|': c = key_dispatch[self.config.reimport_key][0] self.process_event(c) def main(args=None, locals_=None, banner=None): translations.init() while importcompletion.find_coroutine(): pass with SimpleRepl() as r: r.width = 50 r.height = 10 while True: r.print_output() r.get_input() if __name__ == '__main__': main() bpython-0.17.1/bpython/args.py0000644000175100017510000001062413240407731016240 0ustar useruser00000000000000# encoding: utf-8 """ Module to handle command line argument parsing, for all front-ends. """ from __future__ import print_function, absolute_import import code import imp import os import sys from optparse import OptionParser, OptionGroup from . import __version__ from .config import default_config_path, loadini, Struct from .translations import _ class OptionParserFailed(ValueError): """Raised by the RaisingOptionParser for a bogus commandline.""" class RaisingOptionParser(OptionParser): def error(self, msg): raise OptionParserFailed() def version_banner(): return 'bpython version %s on top of Python %s %s' % ( __version__, sys.version.split()[0], sys.executable) def parse(args, extras=None, ignore_stdin=False): """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra options: this should be a tuple of (title, description, options) title: The title for the option group description: A full description of the option group options: A list of optparse.Option objects to be added to the group e.g.: parse( ['-i', '-m', 'foo.py'], ('Front end-specific options', 'A full description of what these options are for', [optparse.Option('-f', action='store_true', dest='f', help='Explode'), optparse.Option('-l', action='store_true', dest='l', help='Love')])) Return a tuple of (config, options, exec_args) wherein "config" is the config object either parsed from a default/specified config file or default config options, "options" is the parsed options from OptionParser.parse_args, and "exec_args" are the args (if any) to be parsed to the executed file (if any). """ if args is None: args = sys.argv[1:] parser = RaisingOptionParser( usage=_('Usage: %prog [options] [file [args]]\n' 'NOTE: If bpython sees an argument it does ' 'not know, execution falls back to the ' 'regular Python interpreter.')) # This is not sufficient if bpython gains its own -m support # (instead of falling back to Python itself for that). # That's probably fixable though, for example by having that # option swallow all remaining arguments in a callback. parser.disable_interspersed_args() parser.add_option('--config', default=default_config_path(), help=_('Use CONFIG instead of default config file.')) parser.add_option('--interactive', '-i', action='store_true', help=_('Drop to bpython shell after running file ' 'instead of exiting.')) parser.add_option('--quiet', '-q', action='store_true', help=_("Don't flush the output to stdout.")) parser.add_option('--version', '-V', action='store_true', help=_('Print version and exit.')) if extras is not None: extras_group = OptionGroup(parser, extras[0], extras[1]) for option in extras[2]: extras_group.add_option(option) parser.add_option_group(extras_group) try: options, args = parser.parse_args(args) except OptionParserFailed: # Just let Python handle this os.execv(sys.executable, [sys.executable] + args) if options.version: print(version_banner()) print('(C) 2008-2016 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. ' 'See AUTHORS for detail.') raise SystemExit if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): interpreter = code.InteractiveInterpreter() interpreter.runsource(sys.stdin.read()) raise SystemExit config = Struct() loadini(config, options.config) return config, options, args 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: 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 bpython-0.17.1/bpython/urwid.py0000644000175100017510000014352313240410473016440 0ustar useruser00000000000000# encoding: utf-8 # # The MIT License # # Copyright (c) 2010-2011 Marien Zwart # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """bpython backend based on Urwid. Based on Urwid 0.9.9. This steals many things from bpython's "cli" backend. This is still *VERY* rough. """ from __future__ import absolute_import, division, print_function import sys import os import time import locale import signal from optparse import Option from six.moves import range from six import iteritems, string_types from pygments.token import Token from . import args as bpargs, repl, translations from ._py3compat import py3 from .config import getpreferredencoding from .formatter import theme_map from .importcompletion import find_coroutine from .translations import _ from .keys import urwid_key_dispatch as key_dispatch import urwid if not py3: import inspect Parenthesis = Token.Punctuation.Parenthesis # Urwid colors are: # 'black', 'dark red', 'dark green', 'brown', 'dark blue', # 'dark magenta', 'dark cyan', 'light gray', 'dark gray', # 'light red', 'light green', 'yellow', 'light blue', # 'light magenta', 'light cyan', 'white' # and bpython has: # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default COLORMAP = { 'k': 'black', 'r': 'dark red', # or light red? 'g': 'dark green', # or light green? 'y': 'yellow', 'b': 'dark blue', # or light blue? 'm': 'dark magenta', # or light magenta? 'c': 'dark cyan', # or light cyan? 'w': 'white', 'd': 'default', } try: from twisted.internet import protocol from twisted.protocols import basic except ImportError: pass else: class EvalProtocol(basic.LineOnlyReceiver): delimiter = '\n' def __init__(self, myrepl): self.repl = myrepl def lineReceived(self, line): # HACK! # TODO: deal with encoding issues here... self.repl.main_loop.process_input(line) self.repl.main_loop.process_input(['enter']) class EvalFactory(protocol.ServerFactory): def __init__(self, myrepl): self.repl = myrepl def buildProtocol(self, addr): return EvalProtocol(self.repl) # If Twisted is not available urwid has no TwistedEventLoop attribute. # Code below will try to import reactor before using TwistedEventLoop. # I assume TwistedEventLoop will be available if that import succeeds. if urwid.VERSION < (1, 0, 0) and hasattr(urwid, 'TwistedEventLoop'): class TwistedEventLoop(urwid.TwistedEventLoop): """TwistedEventLoop modified to properly stop the reactor. urwid 0.9.9 and 0.9.9.1 crash the reactor on ExitMainLoop instead of stopping it. One obvious way this breaks is if anything used the reactor's thread pool: that thread pool is not shut down if the reactor is not stopped, which means python hangs on exit (joining the non-daemon threadpool threads that never exit). And the default resolver is the ThreadedResolver, so if we looked up any names we hang on exit. That is bad enough that we hack up urwid a bit here to exit properly. """ def handle_exit(self, f): def wrapper(*args, **kwargs): try: return f(*args, **kwargs) except urwid.ExitMainLoop: # This is our change. self.reactor.stop() except: # This is the same as in urwid. # We are obviously not supposed to ever hit this. import sys print(sys.exc_info()) self._exc_info = sys.exc_info() self.reactor.crash() return wrapper else: TwistedEventLoop = getattr(urwid, 'TwistedEventLoop', None) class StatusbarEdit(urwid.Edit): """Wrapper around urwid.Edit used for the prompt in Statusbar. This class only adds a single signal that is emitted if the user presses Enter.""" signals = urwid.Edit.signals + ['prompt_enter'] def __init__(self, *args, **kwargs): self.single = False urwid.Edit.__init__(self, *args, **kwargs) def keypress(self, size, key): if self.single: urwid.emit_signal(self, 'prompt_enter', self, key) elif key == 'enter': urwid.emit_signal(self, 'prompt_enter', self, self.get_edit_text()) else: return urwid.Edit.keypress(self, size, key) urwid.register_signal(StatusbarEdit, 'prompt_enter') class Statusbar(object): """Statusbar object, ripped off from bpython.cli. This class provides the status bar at the bottom of the screen. It has message() and prompt() methods for user interactivity, as well as settext() and clear() methods for changing its appearance. The check() method needs to be called repeatedly if the statusbar is going to be aware of when it should update its display after a message() has been called (it'll display for a couple of seconds and then disappear). It should be called as: foo = Statusbar('Initial text to display') or, for a blank statusbar: foo = Statusbar() The "widget" attribute is an urwid widget. """ signals = ['prompt_result'] def __init__(self, config, s=None, main_loop=None): self.config = config self.timer = None self.main_loop = main_loop self.s = s or '' self.text = urwid.Text(('main', self.s)) # use wrap mode 'clip' to just cut off at the end of line self.text.set_wrap_mode('clip') self.edit = StatusbarEdit(('main', '')) urwid.connect_signal(self.edit, 'prompt_enter', self._on_prompt_enter) self.widget = urwid.Columns([self.text, self.edit]) def _check(self, callback, userdata=None): """This is the method is called from the timer to reset the status bar.""" self.timer = None self.settext(self.s) def message(self, s, n=3): """Display a message for a short n seconds on the statusbar and return it to its original state.""" self.settext(s) self.timer = self.main_loop.set_alarm_in(n, self._check) def _reset_timer(self): """Reset the timer from message.""" if self.timer is not None: self.main_loop.remove_alarm(self.timer) self.timer = None def prompt(self, s=None, single=False): """Prompt the user for some input (with the optional prompt 's'). After the user hit enter the signal 'prompt_result' will be emitted and the status bar will be reset. If single is True, the first keypress will be returned.""" self._reset_timer() self.edit.single = single self.edit.set_caption(('main', s or '?')) self.edit.set_edit_text('') # hide the text and display the edit widget if not self.edit in self.widget.widget_list: self.widget.widget_list.append(self.edit) if self.text in self.widget.widget_list: self.widget.widget_list.remove(self.text) self.widget.set_focus_column(0) def settext(self, s, permanent=False): """Set the text on the status bar to a new value. If permanent is True, the new value will be permanent. If that status bar is in prompt mode, the prompt will be aborted. """ self._reset_timer() # hide the edit and display the text widget if self.edit in self.widget.widget_list: self.widget.widget_list.remove(self.edit) if not self.text in self.widget.widget_list: self.widget.widget_list.append(self.text) self.text.set_text(('main', s)) if permanent: self.s = s def clear(self): """Clear the status bar.""" self.settext('') def _on_prompt_enter(self, edit, new_text): """Reset the statusbar and pass the input from the prompt to the caller via 'prompt_result'.""" self.settext(self.s) urwid.emit_signal(self, 'prompt_result', new_text) urwid.register_signal(Statusbar, 'prompt_result') def decoding_input_filter(keys, raw): """Input filter for urwid which decodes each key with the locale's preferred encoding.'""" encoding = locale.getpreferredencoding() converted_keys = list() for key in keys: if isinstance(key, string_types): converted_keys.append(key.decode(encoding)) else: converted_keys.append(key) return converted_keys def format_tokens(tokensource): for token, text in tokensource: if text == '\n': continue # TODO: something about inversing Parenthesis while token not in theme_map: token = token.parent yield (theme_map[token], text) class BPythonEdit(urwid.Edit): """Customized editor *very* tightly interwoven with URWIDRepl. Changes include: - The edit text supports markup, not just the caption. This works by calling set_edit_markup from the change event as well as whenever markup changes while text does not. - The widget can be made readonly, which currently just means it is no longer selectable and stops drawing the cursor. This is currently a one-way operation, but that is just because I only need and test the readwrite->readonly transition. - move_cursor_to_coords is ignored (except for internal calls from keypress or mouse_event). - arrow up/down are ignored. - an "edit-pos-changed" signal is emitted when edit_pos changes. """ signals = ['edit-pos-changed'] def __init__(self, config, *args, **kwargs): self._bpy_text = '' self._bpy_attr = [] self._bpy_selectable = True self._bpy_may_move_cursor = False self.config = config self.tab_length = config.tab_length urwid.Edit.__init__(self, *args, **kwargs) def set_edit_pos(self, pos): urwid.Edit.set_edit_pos(self, pos) self._emit("edit-pos-changed", self.edit_pos) def get_edit_pos(self): return self._edit_pos edit_pos = property(get_edit_pos, set_edit_pos) def make_readonly(self): self._bpy_selectable = False # This is necessary to prevent the listbox we are in getting # fresh cursor coords of None from get_cursor_coords # immediately after we go readonly and then getting a cached # canvas that still has the cursor set. It spots that # inconsistency and raises. self._invalidate() def set_edit_markup(self, markup): """Call this when markup changes but the underlying text does not. You should arrange for this to be called from the 'change' signal. """ if markup: self._bpy_text, self._bpy_attr = urwid.decompose_tagmarkup(markup) else: # decompose_tagmarkup in some urwids fails on the empty list self._bpy_text, self._bpy_attr = '', [] # This is redundant when we're called off the 'change' signal. # I'm assuming this is cheap, making that ok. self._invalidate() def get_text(self): return self._caption + self._bpy_text, self._attrib + self._bpy_attr def selectable(self): return self._bpy_selectable def get_cursor_coords(self, *args, **kwargs): # urwid gets confused if a nonselectable widget has a cursor position. if not self._bpy_selectable: return None return urwid.Edit.get_cursor_coords(self, *args, **kwargs) def render(self, size, focus=False): # XXX I do not want to have to do this, but listbox gets confused # if I do not (getting None out of get_cursor_coords because # we just became unselectable, then having this render a cursor) if not self._bpy_selectable: focus = False return urwid.Edit.render(self, size, focus=focus) def get_pref_col(self, size): # Need to make this deal with us being nonselectable if not self._bpy_selectable: return 'left' return urwid.Edit.get_pref_col(self, size) def move_cursor_to_coords(self, *args): if self._bpy_may_move_cursor: return urwid.Edit.move_cursor_to_coords(self, *args) return False def keypress(self, size, key): if urwid.command_map[key] in ['cursor up', 'cursor down']: # Do not handle up/down arrow, leave them for the repl. return key self._bpy_may_move_cursor = True try: if urwid.command_map[key] == 'cursor max left': self.edit_pos = 0 elif urwid.command_map[key] == 'cursor max right': self.edit_pos = len(self.get_edit_text()) elif urwid.command_map[key] == 'clear word': # ^w if self.edit_pos == 0: return line = self.get_edit_text() # delete any space left of the cursor p = len(line[:self.edit_pos].strip()) line = line[:p] + line[self.edit_pos:] # delete a full word np = line.rfind(' ', 0, p) if np == -1: line = line[p:] np = 0 else: line = line[:np] + line[p:] self.set_edit_text(line) self.edit_pos = np elif urwid.command_map[key] == 'clear line': line = self.get_edit_text() self.set_edit_text(line[self.edit_pos:]) self.edit_pos = 0 elif key == 'backspace': line = self.get_edit_text() cpos = len(line) - self.edit_pos if not (cpos or len(line) % self.tab_length or line.strip()): self.set_edit_text(line[:-self.tab_length]) else: return urwid.Edit.keypress(self, size, key) else: # TODO: Add in specific keypress fetching code here return urwid.Edit.keypress(self, size, key) return None finally: self._bpy_may_move_cursor = False def mouse_event(self, *args): self._bpy_may_move_cursor = True try: return urwid.Edit.mouse_event(self, *args) finally: self._bpy_may_move_cursor = False class BPythonListBox(urwid.ListBox): """Like `urwid.ListBox`, except that it does not eat up and down keys. """ def keypress(self, size, key): if key not in ["up", "down"]: return urwid.ListBox.keypress(self, size, key) return key class Tooltip(urwid.BoxWidget): """Container inspired by Overlay to position our tooltip. bottom_w should be a BoxWidget. The top window currently has to be a listbox to support shrinkwrapping. This passes keyboard events to the bottom instead of the top window. It also positions the top window relative to the cursor position from the bottom window and hides it if there is no cursor. """ def __init__(self, bottom_w, listbox): self.__super.__init__() self.bottom_w = bottom_w self.listbox = listbox # TODO: this linebox should use the 'main' color. self.top_w = urwid.LineBox(listbox) self.tooltip_focus = False def selectable(self): return self.bottom_w.selectable() def keypress(self, size, key): return self.bottom_w.keypress(size, key) def mouse_event(self, size, event, button, col, row, focus): # TODO: pass to top widget if visible and inside it. if not hasattr(self.bottom_w, 'mouse_event'): return False return self.bottom_w.mouse_event( size, event, button, col, row, focus) def get_cursor_coords(self, size): return self.bottom_w.get_cursor_coords(size) def render(self, size, focus=False): maxcol, maxrow = size bottom_c = self.bottom_w.render(size, focus) cursor = bottom_c.cursor if not cursor: # Hide the tooltip if there is no cursor. return bottom_c cursor_x, cursor_y = cursor if cursor_y * 2 < maxrow: # Cursor is in the top half. Tooltip goes below it: y = cursor_y + 1 rows = maxrow - y else: # Cursor is in the bottom half. Tooltip fills the area above: y = 0 rows = cursor_y # HACK: shrink-wrap the tooltip. This is ugly in multiple ways: # - It only works on a listbox. # - It assumes the wrapping LineBox eats one char on each edge. # - It is a loop. # (ideally it would check how much free space there is, # instead of repeatedly trying smaller sizes) while 'bottom' in self.listbox.ends_visible((maxcol - 2, rows - 3)): rows -= 1 # If we're displaying above the cursor move the top edge down: if not y: y = cursor_y - rows # Render *both* windows focused. This is probably not normal in urwid, # but it works nicely. top_c = self.top_w.render((maxcol, rows), focus and self.tooltip_focus) combi_c = urwid.CanvasOverlay(top_c, bottom_c, 0, y) # Use the cursor coordinates from the bottom canvas. canvas = urwid.CompositeCanvas(combi_c) canvas.cursor = cursor return canvas class URWIDInteraction(repl.Interaction): def __init__(self, config, statusbar, frame): repl.Interaction.__init__(self, config, statusbar) self.frame = frame urwid.connect_signal(statusbar, 'prompt_result', self._prompt_result) self.callback = None def confirm(self, q, callback): """Ask for yes or no and call callback to return the result""" def callback_wrapper(result): callback(result.lower() in (_('y'), _('yes'))) self.prompt(q, callback_wrapper, single=True) def notify(self, s, n=10, wait_for_keypress=False): return self.statusbar.message(s, n) def prompt(self, s, callback=None, single=False): """Prompt the user for input. The result will be returned via calling callback. Note that there can only be one prompt active. But the callback can already start a new prompt.""" if self.callback is not None: raise Exception('Prompt already in progress') self.callback = callback self.statusbar.prompt(s, single=single) self.frame.set_focus('footer') def _prompt_result(self, text): self.frame.set_focus('body') if self.callback is not None: # The callback might want to start another prompt, so reset it # before calling the callback. callback = self.callback self.callback = None callback(text) class URWIDRepl(repl.Repl): _time_between_redraws = .05 # seconds def __init__(self, event_loop, palette, interpreter, config): repl.Repl.__init__(self, interpreter, config) self._redraw_handle = None self._redraw_pending = False self._redraw_time = 0 self.listbox = BPythonListBox(urwid.SimpleListWalker([])) self.tooltip = urwid.ListBox(urwid.SimpleListWalker([])) self.tooltip.grid = None self.overlay = Tooltip(self.listbox, self.tooltip) self.stdout_hist = '' self.frame = urwid.Frame(self.overlay) if urwid.get_encoding_mode() == 'narrow': input_filter = decoding_input_filter else: input_filter = None # This constructs a raw_display.Screen, which nabs sys.stdin/out. self.main_loop = urwid.MainLoop( self.frame, palette, event_loop=event_loop, unhandled_input=self.handle_input, input_filter=input_filter, handle_mouse=False) # String is straight from bpython.cli self.statusbar = Statusbar(config, _(" <%s> Rewind <%s> Save <%s> Pastebin " " <%s> Pager <%s> Show Source ") % (config.undo_key, config.save_key, config.pastebin_key, config.last_output_key, config.show_source_key), self.main_loop) self.frame.set_footer(self.statusbar.widget) self.interact = URWIDInteraction(self.config, self.statusbar, self.frame) self.edits = [] self.edit = None self.current_output = None self._completion_update_suppressed = False # Bulletproof: this is a value extract_exit_value accepts. self.exit_value = () load_urwid_command_map(config) # Subclasses of Repl need to implement echo, current_line, cw def echo(self, orig_s): s = orig_s.rstrip('\n') if s: if self.current_output is None: self.current_output = urwid.Text(('output', s)) if self.edit is None: self.listbox.body.append(self.current_output) # Focus the widget we just added to force the # listbox to scroll. This causes output to scroll # if the user runs a blocking call that prints # more than a screenful, instead of staying # scrolled to the previous input line and then # jumping to the bottom when done. self.listbox.set_focus(len(self.listbox.body) - 1) else: self.listbox.body.insert(-1, self.current_output) # The edit widget should be focused and *stay* focused. # XXX TODO: make sure the cursor stays in the same spot. self.listbox.set_focus(len(self.listbox.body) - 1) else: # XXX this assumes this all has "output" markup applied. self.current_output.set_text( ('output', self.current_output.text + s)) if orig_s.endswith('\n'): self.current_output = None # If we hit this repeatedly in a loop the redraw is rather # slow (testcase: pprint(__builtins__). So if we have recently # drawn the screen already schedule a call in the future. # # Unfortunately we may hit this function repeatedly through a # blocking call triggered by the user, in which case our # timeout will not run timely as we do not return to urwid's # eventloop. So we manually check if our timeout has long # since expired, and redraw synchronously if it has. if self._redraw_handle is None: self.main_loop.draw_screen() def maybe_redraw(loop, self): if self._redraw_pending: loop.draw_screen() self._redraw_pending = False self._redraw_handle = None self._redraw_handle = self.main_loop.set_alarm_in( self._time_between_redraws, maybe_redraw, self) self._redraw_time = time.time() else: self._redraw_pending = True now = time.time() if now - self._redraw_time > 2 * self._time_between_redraws: # The timeout is well past expired, assume we're # blocked and redraw synchronously. self.main_loop.draw_screen() self._redraw_time = now def _get_current_line(self): if self.edit is None: return '' return self.edit.get_edit_text() def _set_current_line(self, line): self.edit.set_edit_text(line) current_line = property(_get_current_line, _set_current_line, None, "Return the current line (the one the cursor is in).") def cw(self): """Return the current word (incomplete word left of cursor).""" if self.edit is None: return pos = self.edit.edit_pos text = self.edit.get_edit_text() if pos != len(text): # Disable autocomplete if not at end of line, like cli does. return # Stolen from cli. TODO: clean up and split out. if (not text or (not text[-1].isalnum() and text[-1] not in ('.', '_'))): return # Seek backwards in text for the first non-identifier char: for i, c in enumerate(reversed(text)): if not c.isalnum() and c not in ('.', '_'): break else: # No non-identifiers, return everything. return text # Return everything to the right of the non-identifier. return text[-i:] @property def cpos(self): if self.edit is not None: return len(self.current_line) - self.edit.edit_pos return 0 def _get_cursor_offset(self): return self.edit.edit_pos def _set_cursor_offset(self, offset): self.edit.edit_pos = offset cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, "The cursor offset from the beginning of the line") def _populate_completion(self): widget_list = self.tooltip.body while widget_list: widget_list.pop() # This is just me flailing around wildly. TODO: actually write. if self.complete(): if self.funcprops: # This is mostly just stolen from the cli module. func_name, args, is_bound = self.funcprops in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] if py3: kwonly = self.funcprops.argspec.kwonly kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} else: kwonly, kwonly_defaults = [], {} markup = [('bold name', func_name), ('name', ': (')] # the isinstance checks if we're in a positional arg # (instead of a keyword arg), I think if is_bound and isinstance(in_arg, int): in_arg += 1 # bpython.cli checks if this goes off the edge and # does clever wrapping. I do not (yet). for k, i in enumerate(args): if defaults and k + 1 > len(args) - len(defaults): kw = repr(defaults[k - (len(args) - len(defaults))]) else: kw = None if not k and str(i) == 'self': color = 'name' else: color = 'token' if k == in_arg or i == in_arg: color = 'bold ' + color if not py3: # See issue #138: We need to format tuple unpacking correctly # We use the undocumented function inspection.strseq() for # that. Fortunately, that madness is gone in Python 3. markup.append((color, inspect.strseq(i, str))) else: markup.append((color, str(i))) if kw is not None: markup.extend([('punctuation', '='), ('token', kw)]) if k != len(args) - 1: markup.append(('punctuation', ', ')) if varargs: if args: markup.append(('punctuation', ', ')) markup.append(('token', '*' + varargs)) if kwonly: if not varargs: if args: markup.append(('punctuation', ', ')) markup.append(('punctuation', '*')) for arg in kwonly: if arg == in_arg: color = 'bold token' else: color = 'token' markup.extend([('punctuation', ', '), (color, arg)]) if arg in kwonly_defaults: markup.extend([('punctuation', '='), ('token', repr(kwonly_defaults[arg]))]) if varkw: if args or varargs or kwonly: markup.append(('punctuation', ', ')) markup.append(('token', '**' + varkw)) markup.append(('punctuation', ')')) widget_list.append(urwid.Text(markup)) if self.matches_iter.matches: attr_map = {} focus_map = {'main': 'operator'} texts = [urwid.AttrMap(urwid.Text(('main', match)), attr_map, focus_map) for match in self.matches_iter.matches] width = max(text.original_widget.pack()[0] for text in texts) gridflow = urwid.GridFlow(texts, width, 1, 0, 'left') widget_list.append(gridflow) self.tooltip.grid = gridflow self.overlay.tooltip_focus = False else: self.tooltip.grid = None self.frame.body = self.overlay else: self.frame.body = self.listbox self.tooltip.grid = None if self.docstring: # TODO: use self.format_docstring? needs a width/height... docstring = self.docstring widget_list.append(urwid.Text(('comment', docstring))) def reprint_line(self, lineno, tokens): edit = self.edits[-len(self.buffer) + lineno - 1] edit.set_edit_markup(list(format_tokens(tokens))) def getstdout(self): """This method returns the 'spoofed' stdout buffer, for writing to a file or sending to a pastebin or whatever.""" return self.stdout_hist + '\n' def ask_confirmation(self, q): """Ask for yes or no and return boolean""" try: reply = self.statusbar.prompt(q) except ValueError: return False return reply.lower() in ('y', 'yes') def reevaluate(self): """Clear the buffer, redraw the screen and re-evaluate the history""" self.evaluating = True self.stdout_hist = '' self.f_string = '' self.buffer = [] self.scr.erase() self.s_hist = [] # Set cursor position to -1 to prevent paren matching self.cpos = -1 self.prompt(False) self.iy, self.ix = self.scr.getyx() for line in self.history: if py3: self.stdout_hist += line + '\n' else: self.stdout_hist += line.encode(locale.getpreferredencoding()) + '\n' self.print_line(line) self.s_hist[-1] += self.f_string # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. self.scr.addstr('\n') more = self.push(line) self.prompt(more) self.iy, self.ix = self.scr.getyx() self.cpos = 0 indent = repl.next_indentation(self.s, self.config.tab_length) self.s = '' self.scr.refresh() if self.buffer: for unused in range(indent): self.tab() self.evaluating = False #map(self.push, self.history) #^-- That's how simple this method was at first :( def write(self, s): """For overriding stdout defaults""" if '\x04' in s: for block in s.split('\x04'): self.write(block) return if s.rstrip() and '\x03' in s: t = s.split('\x03')[1] else: t = s if not py3 and isinstance(t, unicode): t = t.encode(locale.getpreferredencoding()) if not self.stdout_hist: self.stdout_hist = t else: self.stdout_hist += t self.echo(s) self.s_hist.append(s.rstrip()) def push(self, s, insert_into_history=True): # Restore the original SIGINT handler. This is needed to be able # to break out of infinite loops. If the interpreter itself # sees this it prints 'KeyboardInterrupt' and returns (good). orig_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, signal.default_int_handler) # Pretty blindly adapted from bpython.cli try: return repl.Repl.push(self, s, insert_into_history) except SystemExit as e: self.exit_value = e.args raise urwid.ExitMainLoop() except KeyboardInterrupt: # KeyboardInterrupt happened between the except block around # user code execution and this code. This should be rare, # but make sure to not kill bpython here, so leaning on # ctrl+c to kill buggy code running inside bpython is safe. self.keyboard_interrupt() finally: signal.signal(signal.SIGINT, orig_handler) def start(self): self.prompt(False) def keyboard_interrupt(self): # If the user is currently editing, interrupt him. This # mirrors what the regular python REPL does. if self.edit is not None: # XXX this is a lot of code, and I am not sure it is # actually enough code. Needs some testing. self.edit.make_readonly() self.edit = None self.buffer = [] self.echo('KeyboardInterrupt') self.prompt(False) else: # I do not quite remember if this is reachable, but let's # be safe. self.echo('KeyboardInterrupt') def prompt(self, more): # Clear current output here, or output resulting from the # current prompt run will end up appended to the edit widget # sitting above this prompt: self.current_output = None # XXX is this the right place? self.rl_history.reset() # XXX what is s_hist? # We need the caption to use unicode as urwid normalizes later # input to be the same type, using ascii as encoding. If the # caption is bytes this breaks typing non-ascii into bpython. if not more: if py3: caption = ('prompt', self.ps1) else: caption = ('prompt', self.ps1.decode(getpreferredencoding())) self.stdout_hist += self.ps1 else: if py3: caption = ('prompt_more', self.ps2) else: caption = ('prompt_more', self.ps2.decode(getpreferredencoding())) self.stdout_hist += self.ps2 self.edit = BPythonEdit(self.config, caption=caption) urwid.connect_signal(self.edit, 'change', self.on_input_change) urwid.connect_signal(self.edit, 'edit-pos-changed', self.on_edit_pos_changed) # Do this after connecting the change signal handler: self.edit.insert_text(4 * self.next_indentation() * ' ') self.edits.append(self.edit) self.listbox.body.append(self.edit) self.listbox.set_focus(len(self.listbox.body) - 1) # Hide the tooltip self.frame.body = self.listbox def on_input_change(self, edit, text): # TODO: we get very confused here if "text" contains newlines, # so we cannot put our edit widget in multiline mode yet. # That is probably fixable... tokens = self.tokenize(text, False) edit.set_edit_markup(list(format_tokens(tokens))) if not self._completion_update_suppressed: # If we call this synchronously the get_edit_text() in repl.cw # still returns the old text... self.main_loop.set_alarm_in( 0, lambda *args: self._populate_completion()) def on_edit_pos_changed(self, edit, position): """Gets called when the cursor position inside the edit changed. Rehighlight the current line because there might be a paren under the cursor now.""" tokens = self.tokenize(self.current_line, False) edit.set_edit_markup(list(format_tokens(tokens))) def handle_input(self, event): # Since most of the input handling here should be handled in the edit # instead, we return here early if the edit doesn't have the focus. if self.frame.get_focus() != 'body': return if event == 'enter': inp = self.edit.get_edit_text() self.history.append(inp) self.edit.make_readonly() # XXX what is this s_hist thing? if py3: self.stdout_hist += inp else: self.stdout_hist += inp.encode(locale.getpreferredencoding()) self.stdout_hist += '\n' self.edit = None # This may take a while, so force a redraw first: self.main_loop.draw_screen() more = self.push(inp) self.prompt(more) elif event == 'ctrl d': # ctrl+d on an empty line exits, otherwise deletes if self.edit is not None: if not self.edit.get_edit_text(): raise urwid.ExitMainLoop() else: self.main_loop.process_input(['delete']) elif urwid.command_map[event] == 'cursor up': # "back" from bpython.cli self.rl_history.enter(self.edit.get_edit_text()) self.edit.set_edit_text('') self.edit.insert_text(self.rl_history.back()) elif urwid.command_map[event] == 'cursor down': # "fwd" from bpython.cli self.rl_history.enter(self.edit.get_edit_text()) self.edit.set_edit_text('') self.edit.insert_text(self.rl_history.forward()) elif urwid.command_map[event] == 'next selectable': self.tab() elif urwid.command_map[event] == 'prev selectable': self.tab(True) #else: # self.echo(repr(event)) def tab(self, back=False): """Process the tab key being hit. If the line is blank or has only whitespace: indent. If there is text before the cursor: cycle completions. If `back` is True cycle backwards through completions, and return instead of indenting. Returns True if the key was handled. """ self._completion_update_suppressed = True try: # Heavily inspired by cli's tab. text = self.edit.get_edit_text() if not text.lstrip() and not back: x_pos = len(text) - self.cpos num_spaces = x_pos % self.config.tab_length if not num_spaces: num_spaces = self.config.tab_length self.edit.insert_text(' ' * num_spaces) return True if not self.matches_iter: self.complete(tab=True) cw = self.current_string() or self.cw() if not cw: return True else: cw = self.matches_iter.current_word if self.matches_iter.is_cseq(): cursor, text = self.matches_iter.substitute_cseq() self.edit.set_edit_text(text) self.edit.edit_pos = cursor elif self.matches_iter.matches: if back: self.matches_iter.previous() else: next(self.matches_iter) cursor, text = self.matches_iter.cur_line() self.edit.set_edit_text(text) self.edit.edit_pos = cursor self.overlay.tooltip_focus = True if self.tooltip.grid: self.tooltip.grid.set_focus(self.matches_iter.index) return True finally: self._completion_update_suppressed = False def main(args=None, locals_=None, banner=None): translations.init() # TODO: maybe support displays other than raw_display? config, options, exec_args = bpargs.parse(args, ( 'Urwid options', None, [ Option('--twisted', '-T', action='store_true', help=_('Run twisted reactor.')), Option('--reactor', '-r', help=_('Select specific reactor (see --help-reactors). ' 'Implies --twisted.')), Option('--help-reactors', action='store_true', help=_('List available reactors for -r.')), Option('--plugin', '-p', help=_('twistd plugin to run (use twistd for a list). ' 'Use "--" to pass further options to the plugin.')), Option('--server', '-s', type='int', help=_('Port to run an eval server on (forces Twisted).')), ])) if options.help_reactors: try: from twisted.application import reactors # Stolen from twisted.application.app (twistd). for r in reactors.getReactorTypes(): print(' %-4s\t%s' % (r.shortName, r.description)) except ImportError: sys.stderr.write('No reactors are available. Please install ' 'twisted for reactor support.\n') return palette = [ (name, COLORMAP[color.lower()], 'default', 'bold' if color.isupper() else 'default') for name, color in iteritems(config.color_scheme)] palette.extend([ ('bold ' + name, color + ',bold', background, monochrome) for name, color, background, monochrome in palette]) if options.server or options.plugin: options.twisted = True if options.reactor: try: from twisted.application import reactors except ImportError: sys.stderr.write('No reactors are available. Please install ' 'twisted for reactor support.\n') return try: # XXX why does this not just return the reactor it installed? reactor = reactors.installReactor(options.reactor) if reactor is None: from twisted.internet import reactor except reactors.NoSuchReactor: sys.stderr.write('Reactor %s does not exist\n' % ( options.reactor,)) return event_loop = TwistedEventLoop(reactor) elif options.twisted: try: from twisted.internet import reactor except ImportError: sys.stderr.write('No reactors are available. Please install ' 'twisted for reactor support.\n') return event_loop = TwistedEventLoop(reactor) else: # None, not urwid.SelectEventLoop(), to work with # screens that do not support external event loops. event_loop = None # TODO: there is also a glib event loop. Do we want that one? extend_locals = {} if options.plugin: try: from twisted import plugin from twisted.application import service except ImportError: sys.stderr.write('No twisted plugins are available. Please install ' 'twisted for twisted plugin support.\n') return for plug in plugin.getPlugins(service.IServiceMaker): if plug.tapname == options.plugin: break else: sys.stderr.write('Plugin %s does not exist\n' % (options.plugin,)) return plugopts = plug.options() plugopts.parseOptions(exec_args) serv = plug.makeService(plugopts) extend_locals['service'] = serv reactor.callWhenRunning(serv.startService) exec_args = [] interpreter = repl.Interpreter(locals_, locale.getpreferredencoding()) # TODO: replace with something less hack-ish interpreter.locals.update(extend_locals) # This nabs sys.stdin/out via urwid.MainLoop myrepl = URWIDRepl(event_loop, palette, interpreter, config) if options.server: factory = EvalFactory(myrepl) reactor.listenTCP(options.server, factory, interface='127.0.0.1') if options.reactor: # Twisted sets a sigInt handler that stops the reactor unless # it sees a different custom signal handler. def sigint(*args): reactor.callFromThread(myrepl.keyboard_interrupt) signal.signal(signal.SIGINT, sigint) # Save stdin, stdout and stderr for later restoration orig_stdin = sys.stdin orig_stdout = sys.stdout orig_stderr = sys.stderr # urwid's screen start() and stop() calls currently hit sys.stdin # directly (via RealTerminal.tty_signal_keys), so start the screen # before swapping sys.std*, and swap them back before restoring # the screen. This also avoids crashes if our redirected sys.std* # are called before we get around to starting the mainloop # (urwid raises an exception if we try to draw to the screen # before starting it). def run_with_screen_before_mainloop(): try: # Currently we just set this to None because I do not # expect code hitting stdin to work. For example: exit() # (not sys.exit, site.py's exit) tries to close sys.stdin, # which breaks urwid's shutdown. bpython.cli sets this to # a fake object that reads input through curses and # returns it. When using twisted I do not think we can do # that because sys.stdin.read and friends block, and we # cannot re-enter the reactor. If using urwid's own # mainloop we *might* be able to do something similar and # re-enter its mainloop. sys.stdin = None #FakeStdin(myrepl) sys.stdout = myrepl sys.stderr = myrepl myrepl.main_loop.set_alarm_in(0, start) while True: try: myrepl.main_loop.run() except KeyboardInterrupt: # HACK: if we run under a twisted mainloop this should # never happen: we have a SIGINT handler set. # If we use the urwid select-based loop we just restart # that loop if interrupted, instead of trying to cook # up an equivalent to reactor.callFromThread (which # is what our Twisted sigint handler does) myrepl.main_loop.set_alarm_in( 0, lambda *args: myrepl.keyboard_interrupt()) continue break finally: sys.stdin = orig_stdin sys.stderr = orig_stderr sys.stdout = orig_stdout # This needs more thought. What needs to happen inside the mainloop? def start(main_loop, user_data): if exec_args: bpargs.exec_code(interpreter, exec_args) if not options.interactive: raise urwid.ExitMainLoop() if not exec_args: sys.path.insert(0, '') # this is CLIRepl.startup inlined. filename = os.environ.get('PYTHONSTARTUP') if filename and os.path.isfile(filename): with open(filename, 'r') as f: if py3: interpreter.runsource(f.read(), filename, 'exec') else: interpreter.runsource(f.read(), filename, 'exec', encode=False) if banner is not None: myrepl.write(banner) myrepl.write('\n') myrepl.start() # This bypasses main_loop.set_alarm_in because we must *not* # hit the draw_screen call (it's unnecessary and slow). def run_find_coroutine(): if find_coroutine(): main_loop.event_loop.alarm(0, run_find_coroutine) run_find_coroutine() myrepl.main_loop.screen.run_wrapper(run_with_screen_before_mainloop) if config.flush_output and not options.quiet: sys.stdout.write(myrepl.getstdout()) if hasattr(sys.stdout, "flush"): sys.stdout.flush() return repl.extract_exit_value(myrepl.exit_value) def load_urwid_command_map(config): urwid.command_map[key_dispatch[config.up_one_line_key]] = 'cursor up' urwid.command_map[key_dispatch[config.down_one_line_key]] = 'cursor down' urwid.command_map[key_dispatch['C-a']] = 'cursor max left' urwid.command_map[key_dispatch['C-e']] = 'cursor max right' urwid.command_map[key_dispatch[config.pastebin_key]] = 'pastebin' urwid.command_map[key_dispatch['C-f']] = 'cursor right' urwid.command_map[key_dispatch['C-b']] = 'cursor left' urwid.command_map[key_dispatch['C-d']] = 'delete' urwid.command_map[key_dispatch[config.clear_word_key]] = 'clear word' urwid.command_map[key_dispatch[config.clear_line_key]] = 'clear line' """ 'clear_screen': 'C-l', 'cut_to_buffer': 'C-k', 'down_one_line': 'C-n', 'exit': '', 'last_output': 'F9', 'pastebin': 'F8', 'save': 'C-s', 'show_source': 'F2', 'suspend': 'C-z', 'undo': 'C-r', 'up_one_line': 'C-p', 'yank_from_buffer': 'C-y'}, """ if __name__ == '__main__': sys.exit(main()) bpython-0.17.1/bpython/paste.py0000644000175100017510000000775113240407731016427 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2014-2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from locale import getpreferredencoding from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse from string import Template import errno import requests import subprocess import unicodedata from .translations import _ class PasteFailed(Exception): pass class PastePinnwand(object): def __init__(self, url, expiry, show_url, removal_url): self.url = url self.expiry = expiry self.show_url = show_url self.removal_url = removal_url def paste(self, s): """Upload to pastebin via json interface.""" url = urljoin(self.url, '/json/new') payload = { 'code': s, 'lexer': 'pycon', 'expiry': self.expiry } try: response = requests.post(url, data=payload, verify=True) response.raise_for_status() except requests.exceptions.RequestException as exc: raise PasteFailed(exc.message) data = response.json() paste_url_template = Template(self.show_url) paste_id = urlquote(data['paste_id']) paste_url = paste_url_template.safe_substitute(paste_id=paste_id) removal_url_template = Template(self.removal_url) removal_id = urlquote(data['removal_id']) removal_url = removal_url_template.safe_substitute( removal_id=removal_id) return (paste_url, removal_url) class PasteHelper(object): def __init__(self, executable): self.executable = executable def paste(self, s): """Call out to helper program for pastebin upload.""" try: helper = subprocess.Popen('', executable=self.executable, stdin=subprocess.PIPE, stdout=subprocess.PIPE) helper.stdin.write(s.encode(getpreferredencoding())) output = helper.communicate()[0].decode(getpreferredencoding()) paste_url = output.split()[0] except OSError as e: if e.errno == errno.ENOENT: raise PasteFailed(_('Helper program not found.')) else: raise PasteFailed(_('Helper program could not be run.')) if helper.returncode != 0: raise PasteFailed(_('Helper program returned non-zero exit ' 'status %d.' % (helper.returncode, ))) if not paste_url: raise PasteFailed(_('No output from helper program.')) else: parsed_url = urlparse(paste_url) if (not parsed_url.scheme or any(unicodedata.category(c) == 'Cc' for c in paste_url)): raise PasteFailed(_('Failed to recognize the helper ' 'program\'s output as an URL.')) return paste_url, None bpython-0.17.1/bpython/importcompletion.py0000644000175100017510000001545413240410473020713 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import from ._py3compat import py3, try_decode from .line import (current_word, current_import, current_from_import_from, current_from_import_import) import imp import os import sys import warnings from warnings import catch_warnings from six.moves import filter if sys.version_info[0] == 3 and sys.version_info[1] >= 3: import importlib.machinery SUFFIXES = importlib.machinery.all_suffixes() else: SUFFIXES = [suffix for suffix, mode, type in imp.get_suffixes()] # The cached list of all known modules modules = set() fully_loaded = False def module_matches(cw, prefix=''): """Modules names to replace cw with""" full = '%s.%s' % (prefix, cw) if prefix else cw matches = (name for name in modules if (name.startswith(full) and name.find('.', len(full)) == -1)) if prefix: return set(match[len(prefix)+1:] for match in matches) else: return set(matches) def attr_matches(cw, prefix='', only_modules=False): """Attributes to replace name with""" full = '%s.%s' % (prefix, cw) if prefix else cw module_name, _, name_after_dot = full.rpartition('.') if module_name not in sys.modules: return set() module = sys.modules[module_name] if only_modules: matches = (name for name in dir(module) if (name.startswith(name_after_dot) and '%s.%s' % (module_name, name)) in sys.modules) else: matches = (name for name in dir(module) if name.startswith(name_after_dot)) module_part, _, _ = cw.rpartition('.') if module_part: matches = ('%s.%s' % (module_part, m) for m in matches) generator = (try_decode(match, 'ascii') for match in matches) return set(filter(lambda x: x is not None, generator)) def module_attr_matches(name): """Only attributes which are modules to replace name with""" return attr_matches(name, prefix='', only_modules=True) def complete(cursor_offset, line): """Construct a full list of possibly completions for imports.""" tokens = line.split() if 'from' not in tokens and 'import' not in tokens: return None result = current_word(cursor_offset, line) if result is None: return None from_import_from = current_from_import_from(cursor_offset, line) if from_import_from is not None: import_import = current_from_import_import(cursor_offset, line) if import_import is not None: # `from a import ` completion matches = module_matches(import_import[2], from_import_from[2]) matches.update(attr_matches(import_import[2], from_import_from[2])) else: # `from ` completion matches = module_attr_matches(from_import_from[2]) matches.update(module_matches(from_import_from[2])) return matches cur_import = current_import(cursor_offset, line) if cur_import is not None: # `import ` completion matches = module_matches(cur_import[2]) matches.update(module_attr_matches(cur_import[2])) return matches else: return None def find_modules(path): """Find all modules (and packages) for a given directory.""" if not os.path.isdir(path): # Perhaps a zip file return try: filenames = os.listdir(path) except EnvironmentError: filenames = [] for name in filenames: if not any(name.endswith(suffix) for suffix in SUFFIXES): # Possibly a package if '.' in name: continue elif os.path.isdir(os.path.join(path, name)): # Unfortunately, CPython just crashes if there is a directory # which ends with a python extension, so work around. continue for suffix in SUFFIXES: if name.endswith(suffix): name = name[:-len(suffix)] break if py3 and name == "badsyntax_pep3120": # Workaround for issue #166 continue try: with catch_warnings(): warnings.simplefilter("ignore", ImportWarning) fo, pathname, _ = imp.find_module(name, [path]) except (ImportError, IOError, SyntaxError): continue except UnicodeEncodeError: # Happens with Python 3 when there is a filename in some # invalid encoding continue else: if fo is not None: fo.close() else: # Yay, package for subname in find_modules(pathname): if subname != '__init__': yield '%s.%s' % (name, subname) yield name def find_all_modules(path=None): """Return a list with all modules in `path`, which should be a list of directory names. If path is not given, sys.path will be used.""" if path is None: modules.update(try_decode(m, 'ascii') for m in sys.builtin_module_names) path = sys.path for p in path: if not p: p = os.curdir for module in find_modules(p): module = try_decode(module, 'ascii') if module is None: continue modules.add(module) yield def find_coroutine(): global fully_loaded if fully_loaded: return None try: next(find_iterator) except StopIteration: fully_loaded = True return True def reload(): """Refresh the list of known modules.""" modules.clear() for _ in find_all_modules(): pass find_iterator = find_all_modules() bpython-0.17.1/bpython/translations/0000755000175100017510000000000013240411345017444 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/fr_FR/0000755000175100017510000000000013240411345020442 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/fr_FR/LC_MESSAGES/0000755000175100017510000000000013240411345022227 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/fr_FR/LC_MESSAGES/bpython.po0000644000175100017510000001740713240407731024267 0ustar useruser00000000000000# French (France) translations for bpython. # Copyright (C) 2010 bpython developers # This file is distributed under the same license as the bpython project. # msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-03-24 00:29+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" "Language: fr_FR\n" "X-Generator: Poedit 1.6.10\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back to " "the regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " "Python classique sera lancé" #: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." #: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." #: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." #: bpython/args.py:76 msgid "Print version and exit." msgstr "Afficher la version et quitter." #: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "o" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "oui" #: bpython/cli.py:1695 msgid "Rewind" msgstr "Rembobiner" #: bpython/cli.py:1696 msgid "Save" msgstr "Sauvegarder" #: bpython/cli.py:1697 msgid "Pastebin" msgstr "" #: bpython/cli.py:1698 msgid "Pager" msgstr "" #: bpython/cli.py:1699 msgid "Show Source" msgstr "Montrer le code source" #: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" #: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" #: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" #: bpython/paste.py:94 msgid "Helper program not found." msgstr "programme externe non trouvé." #: bpython/paste.py:96 msgid "Helper program could not be run." msgstr "impossible de lancer le programme externe." #: bpython/paste.py:100 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" "le programme externe a renvoyé un statut de sortie différent de zéro %d." #: bpython/paste.py:103 msgid "No output from helper program." msgstr "pas de sortie du programme externe." #: bpython/paste.py:109 msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." #: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" #: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" #: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" #: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" #: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" #: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" #: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" #: bpython/repl.py:713 msgid "overwrite" msgstr "" #: bpython/repl.py:715 msgid "append" msgstr "" #: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" #: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" #: bpython/repl.py:735 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." #: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." #: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." #: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " #: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "Pastebin abandonné." #: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" #: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." #: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" #: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" #: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" #: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" #: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" #: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" #: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" #: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" #: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" #: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " "Source " #: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." #: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." #: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." #: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" "plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " "donner plus d'options au plugin." #: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." #: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" #: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." #: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" #: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" #: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" #: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" #: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" #: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" bpython-0.17.1/bpython/translations/__init__.py0000644000175100017510000000230513240407731021561 0ustar useruser00000000000000# encoding: utf-8 from __future__ import absolute_import import gettext import locale import os.path import sys from .. import package_dir from .._py3compat import py3 translator = None if py3: def _(message): return translator.gettext(message) def ngettext(singular, plural, n): return translator.ngettext(singular, plural, n) else: def _(message): return translator.ugettext(message) def ngettext(singular, plural, n): return translator.ungettext(singular, plural, n) def init(locale_dir=None, languages=None): try: locale.setlocale(locale.LC_ALL, '') except locale.Error: # This means that the user's environment is broken. Let's just continue # with the default C locale. sys.stderr.write("Error: Your locale settings are not supported by " "the system. Using the fallback 'C' locale instead. " "Please fix your locale settings.\n") global translator if locale_dir is None: locale_dir = os.path.join(package_dir, 'translations') translator = gettext.translation('bpython', locale_dir, languages, fallback=True) bpython-0.17.1/bpython/translations/es_ES/0000755000175100017510000000000013240411345020442 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/es_ES/LC_MESSAGES/0000755000175100017510000000000013240411345022227 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/es_ES/LC_MESSAGES/bpython.po0000644000175100017510000001376513240407731024272 0ustar useruser00000000000000# Spanish (Spain) translations for bpython. # Copyright (C) 2010 bpython developers # This file is distributed under the same license as the bpython project. # Claudia Medde, 2010. # msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" #: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" #: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" #: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" #: bpython/args.py:76 msgid "Print version and exit." msgstr "" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "s" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "si" #: bpython/cli.py:1695 msgid "Rewind" msgstr "" #: bpython/cli.py:1696 msgid "Save" msgstr "" #: bpython/cli.py:1697 msgid "Pastebin" msgstr "" #: bpython/cli.py:1698 msgid "Pager" msgstr "" #: bpython/cli.py:1699 msgid "Show Source" msgstr "" #: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" #: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" #: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/paste.py:94 msgid "Helper program not found." msgstr "" #: bpython/paste.py:96 msgid "Helper program could not be run." msgstr "" #: bpython/paste.py:100 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" #: bpython/paste.py:103 msgid "No output from helper program." msgstr "" #: bpython/paste.py:109 msgid "Failed to recognize the helper program's output as an URL." msgstr "" #: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" #: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" #: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" #: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" #: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" #: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" #: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" #: bpython/repl.py:713 msgid "overwrite" msgstr "" #: bpython/repl.py:715 msgid "append" msgstr "" #: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" #: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" #: bpython/repl.py:735 msgid "No clipboard available." msgstr "" #: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" #: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" #: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" #: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" #: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" #: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" #: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" #: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" #: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" #: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" #: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" #: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" #: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" #: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" #: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" #: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" #: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" #: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" #: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" #: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" #: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" #: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" #: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" #: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" #: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" #: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" #: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" #: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" #: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" bpython-0.17.1/bpython/translations/it_IT/0000755000175100017510000000000013240411345020454 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/it_IT/LC_MESSAGES/0000755000175100017510000000000013240411345022241 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/it_IT/LC_MESSAGES/bpython.po0000644000175100017510000001376613240407731024305 0ustar useruser00000000000000# Italian (Italy) translations for bpython. # Copyright (C) 2010 bpython developers # This file is distributed under the same license as the bpython project. # Michele Orrù , 2010. # msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" #: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" #: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" #: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" #: bpython/args.py:76 msgid "Print version and exit." msgstr "" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "s" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "si" #: bpython/cli.py:1695 msgid "Rewind" msgstr "" #: bpython/cli.py:1696 msgid "Save" msgstr "" #: bpython/cli.py:1697 msgid "Pastebin" msgstr "" #: bpython/cli.py:1698 msgid "Pager" msgstr "" #: bpython/cli.py:1699 msgid "Show Source" msgstr "" #: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" #: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" #: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/paste.py:94 msgid "Helper program not found." msgstr "" #: bpython/paste.py:96 msgid "Helper program could not be run." msgstr "" #: bpython/paste.py:100 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" #: bpython/paste.py:103 msgid "No output from helper program." msgstr "" #: bpython/paste.py:109 msgid "Failed to recognize the helper program's output as an URL." msgstr "" #: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" #: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" #: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" #: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" #: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" #: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" #: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" #: bpython/repl.py:713 msgid "overwrite" msgstr "" #: bpython/repl.py:715 msgid "append" msgstr "" #: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" #: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" #: bpython/repl.py:735 msgid "No clipboard available." msgstr "" #: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" #: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" #: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" #: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" #: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" #: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" #: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" #: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" #: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" #: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" #: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" #: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" #: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" #: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" #: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" #: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" #: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" #: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" #: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" #: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" #: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" #: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" #: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" #: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" #: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" #: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" #: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" #: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" #: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" bpython-0.17.1/bpython/translations/nl_NL/0000755000175100017510000000000013240411345020446 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/nl_NL/LC_MESSAGES/0000755000175100017510000000000013240411345022233 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/nl_NL/LC_MESSAGES/bpython.po0000644000175100017510000001376013240407731024271 0ustar useruser00000000000000# Dutch (Netherlands) translations for bpython. # Copyright (C) 2011 bpython developers # This file is distributed under the same license as the bpython project. # Simon de Vlieger, 2011 . # msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" #: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" #: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" #: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" #: bpython/args.py:76 msgid "Print version and exit." msgstr "" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "j" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "ja" #: bpython/cli.py:1695 msgid "Rewind" msgstr "" #: bpython/cli.py:1696 msgid "Save" msgstr "" #: bpython/cli.py:1697 msgid "Pastebin" msgstr "" #: bpython/cli.py:1698 msgid "Pager" msgstr "" #: bpython/cli.py:1699 msgid "Show Source" msgstr "" #: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" #: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" #: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/paste.py:94 msgid "Helper program not found." msgstr "" #: bpython/paste.py:96 msgid "Helper program could not be run." msgstr "" #: bpython/paste.py:100 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" #: bpython/paste.py:103 msgid "No output from helper program." msgstr "" #: bpython/paste.py:109 msgid "Failed to recognize the helper program's output as an URL." msgstr "" #: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" #: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" #: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" #: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" #: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" #: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" #: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" #: bpython/repl.py:713 msgid "overwrite" msgstr "" #: bpython/repl.py:715 msgid "append" msgstr "" #: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" #: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" #: bpython/repl.py:735 msgid "No clipboard available." msgstr "" #: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" #: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" #: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" #: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" #: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" #: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" #: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" #: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" #: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" #: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" #: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" #: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" #: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" #: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" #: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" #: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" #: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" #: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" #: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" #: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" #: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" #: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" #: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" #: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" #: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" #: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" #: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" #: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" #: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" bpython-0.17.1/bpython/translations/de/0000755000175100017510000000000013240411345020034 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/de/LC_MESSAGES/0000755000175100017510000000000013240411345021621 5ustar useruser00000000000000bpython-0.17.1/bpython/translations/de/LC_MESSAGES/bpython.po0000644000175100017510000001650513240407731023657 0ustar useruser00000000000000# German translations for bpython. # Copyright (C) 2012-2013 bpython developers # This file is distributed under the same license as the bpython project. # Sebastian Ramacher , 2012-2013. # msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-03-24 00:27+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" "Language: de\n" "X-Generator: Poedit 1.6.10\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back to " "the regular Python interpreter." msgstr "" #: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." #: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." #: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." #: bpython/args.py:76 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." #: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "j" #: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "ja" #: bpython/cli.py:1695 msgid "Rewind" msgstr "Rückgängig" #: bpython/cli.py:1696 msgid "Save" msgstr "Speichern" #: bpython/cli.py:1697 msgid "Pastebin" msgstr "" #: bpython/cli.py:1698 msgid "Pager" msgstr "" #: bpython/cli.py:1699 msgid "Show Source" msgstr "Quellcode anzeigen" #: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" #: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" #: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" #: bpython/paste.py:94 msgid "Helper program not found." msgstr "Hilfsprogramm konnte nicht gefunden werden." #: bpython/paste.py:96 msgid "Helper program could not be run." msgstr "Hilfsprogramm konnte nicht ausgeführt werden." #: bpython/paste.py:100 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "Hilfsprogramm beendete mit Status %d." #: bpython/paste.py:103 msgid "No output from helper program." msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." #: bpython/paste.py:109 msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." #: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" #: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" #: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" #: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" #: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " #: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "Speichern abgebrochen." #: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" #: bpython/repl.py:713 msgid "overwrite" msgstr "überschreiben" #: bpython/repl.py:715 msgid "append" msgstr "anhängen" #: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" #: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." #: bpython/repl.py:735 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." #: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." #: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." #: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" #: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" #: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" #: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." #: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" #: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" #: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" #: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" #: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" #: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" #: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? " "(j/N)" #: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." #: bpython/repl.py:1032 msgid "Error editing config file." msgstr "Fehler beim Bearbeiten der Konfigurationsdatei." #: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" #: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" #: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" #: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" #: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" #: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" #: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" #: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." #: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" #: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" #: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" #: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" #: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" #: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" bpython-0.17.1/bpython/clipboard.py0000644000175100017510000000506013240407731017241 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import subprocess import os import platform from locale import getpreferredencoding class CopyFailed(Exception): pass class XClipboard(object): """Manage clipboard with xclip.""" def copy(self, content): process = subprocess.Popen(['xclip', '-i', '-selection', 'clipboard'], stdin=subprocess.PIPE) process.communicate(content.encode(getpreferredencoding())) if process.returncode != 0: raise CopyFailed() class OSXClipboard(object): """Manage clipboard with pbcopy.""" def copy(self, content): process = subprocess.Popen(['pbcopy', 'w'], stdin=subprocess.PIPE) process.communicate(content.encode(getpreferredencoding())) if process.returncode != 0: raise CopyFailed() def command_exists(command): process = subprocess.Popen(['which', command], stderr=subprocess.STDOUT, stdout=subprocess.PIPE) process.communicate() return process.returncode == 0 def get_clipboard(): """Get best clipboard handling implementation for current system.""" if platform.system() == 'Darwin': if command_exists('pbcopy'): return OSXClipboard() if (platform.system() in ('Linux', 'FreeBSD', 'OpenBSD') and os.getenv('DISPLAY') is not None): if command_exists('xclip'): return XClipboard() return None bpython-0.17.1/bpython/test/0000755000175100017510000000000013240411345015702 5ustar useruser00000000000000bpython-0.17.1/bpython/test/test_filewatch.py0000644000175100017510000000172613240407731021273 0ustar useruser00000000000000# encoding: utf-8 import os try: import watchdog from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler has_watchdog = True except ImportError: has_watchdog = False from bpython.test import mock, unittest @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): def setUp(self): self.module = ModuleChangedEventHandler([], 1) self.module.observer = mock.Mock() def test_create_module_handler(self): self.assertIsInstance(self.module, ModuleChangedEventHandler) def test_add_module(self): self.module._add_module('something/test.py') self.assertIn(os.path.abspath('something/test'), self.module.dirs[os.path.abspath('something')]) def test_activate_throws_error_when_already_activated(self): self.module.activated = True with self.assertRaises(ValueError): self.module.activate() bpython-0.17.1/bpython/test/test.theme0000644000175100017510000000004113240407731017704 0ustar useruser00000000000000[syntax] keyword = y [interface]bpython-0.17.1/bpython/test/test_manual_readline.py0000644000175100017510000002501713240410473022441 0ustar useruser00000000000000# encoding: utf-8 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): def setUp(self): self._line = "this is my test string" def tearDown(self): pass def test_left_arrow_at_zero(self): pos = 0 expected = (pos, self._line) result = left_arrow(pos, self._line) self.assertEquals(expected, result) def test_left_arrow_at_non_zero(self): for i in range(1, len(self._line)): expected = (i-1, self._line) result = left_arrow(i, self._line) self.assertEqual(expected, result) def test_right_arrow_at_end(self): pos = len(self._line) expected = (pos, self._line) result = right_arrow(pos, self._line) self.assertEquals(expected, result) def test_right_arrow_at_non_end(self): for i in range(len(self._line) - 1): expected = (i + 1, self._line) result = right_arrow(i, self._line) self.assertEquals(expected, result) def test_beginning_of_line(self): expected = (0, self._line) for i in range(len(self._line)): result = beginning_of_line(i, self._line) self.assertEquals(expected, result) def test_end_of_line(self): expected = (len(self._line), self._line) for i in range(len(self._line)): result = end_of_line(i, self._line) self.assertEquals(expected, result) def test_forward_word(self): line = "going from here to_here" start_pos = 11 next_word_pos = 15 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) start_pos = 15 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) def test_forward_word_tabs(self): line = "going from here to_here" start_pos = 11 next_word_pos = 15 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) start_pos = 15 next_word_pos = 28 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) def test_forward_word_end(self): line = "going from here to_here" start_pos = 16 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) start_pos = 22 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) start_pos = 23 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) def test_forward_word_empty(self): line = "" start_pos = 0 next_word_pos = 0 expected = (next_word_pos, line) result = forward_word(start_pos, line) self.assertEquals(expected, result) def test_back_word(self): line = "going to here from_here" start_pos = 14 prev_word_pos = 9 self.assertEquals(line[start_pos], 'f') self.assertEquals(line[prev_word_pos], 'h') expected = (prev_word_pos, line) result = back_word(start_pos, line) self.assertEquals(expected, result) def test_last_word_pos(self): line = "a word" expected = 2 result = last_word_pos(line) self.assertEquals(expected, result) def test_last_word_pos_single_word(self): line = "word" expected = 0 result = last_word_pos(line) self.assertEquals(expected, result) def test_delete(self): line = "deletion line" pos = 3 expected = (3, "deltion line") result = delete(pos, line) self.assertEquals(expected, result) def test_delete_from_cursor_back(self): line = "everything before this will be deleted" expected = (0, "this will be deleted") result = delete_from_cursor_back(line.find("this"), line) self.assertEquals(expected, result) def test_delete_from_cursor_forward(self): line = "everything after this will be deleted" pos = line.find("this") expected = (pos, "everything after ") result = delete_from_cursor_forward(line.find("this"), line)[:-1] self.assertEquals(expected, result) self.assertEquals(delete_from_cursor_forward(0, ''), (0, '', '')) def test_delete_rest_of_word(self): self.try_stages_kill([ 'z|s;df asdf d s;a;a', 'z|;df asdf d s;a;a', 'z| asdf d s;a;a', 'z| d s;a;a', 'z| s;a;a', 'z|;a;a', 'z|;a', 'z|', 'z|'], delete_rest_of_word) def test_delete_word_to_cursor(self): self.try_stages_kill([ ' a;d sdf ;a;s;d; fjksald|a', ' a;d sdf ;a;s;d; |a', ' a;d sdf |a', ' a;d |a', ' |a', '|a', '|a'], delete_word_to_cursor) def test_yank_prev_killed_text(self): pass def test_yank_prev_prev_killed_text(self): pass def try_stages(self, strings, func): if not all('|' in s for s in strings): raise ValueError("Need to use '|' to specify cursor") stages = [(s.index('|'), s.replace('|', '')) for s in strings] for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): self.assertEquals(func(initial_pos, initial), (final_pos, final)) def try_stages_kill(self, strings, func): if not all('|' in s for s in strings): raise ValueError("Need to use '|' to specify cursor") stages = [(s.index('|'), s.replace('|', '')) for s in strings] for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): self.assertEquals(func(initial_pos, initial)[:-1], (final_pos, final)) def test_transpose_character_before_cursor(self): self.try_stages(["as|df asdf", "ads|f asdf", "adfs| asdf", "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'), (0, 'a')) self.assertEquals(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 def test_backspace(self): self.assertEquals(backspace(2, 'as'), (1, 'a')) self.assertEquals(backspace(3, 'as '), (2, 'as')) def test_delete_word_from_cursor_back(self): self.try_stages_kill([ "asd;fljk asd;lfjas;dlkfj asdlk jasdf;ljk|", "asd;fljk asd;lfjas;dlkfj asdlk jasdf;|", "asd;fljk asd;lfjas;dlkfj asdlk |", "asd;fljk asd;lfjas;dlkfj |", "asd;fljk asd;lfjas;|", "asd;fljk asd;|", "asd;fljk |", "asd;|", "|", "|"], delete_word_from_cursor_back) self.try_stages_kill([ " (( asdf |", " (( |", "|"], delete_word_from_cursor_back) class TestEdits(unittest.TestCase): def setUp(self): self.edits = UnconfiguredEdits() def test_seq(self): def f(cursor_offset, line): return ('hi', 2) self.edits.add('a', f) self.assertIn('a', self.edits) self.assertEqual(self.edits['a'], f) self.assertEqual(self.edits.call('a', cursor_offset=3, line='hello'), ('hi', 2)) with self.assertRaises(KeyError): self.edits['b'] with self.assertRaises(KeyError): self.edits.call('b') def test_functions_with_bad_signatures(self): def f(something): return (1, 2) with self.assertRaises(TypeError): self.edits.add('a', f) def g(cursor_offset, line, something, something_else): return (1, 2) with self.assertRaises(TypeError): self.edits.add('a', g) def test_functions_with_bad_return_values(self): def f(cursor_offset, line): return ('hi',) with self.assertRaises(ValueError): self.edits.add('a', f) def g(cursor_offset, line): return ('hi', 1, 2, 3) with self.assertRaises(ValueError): self.edits.add('b', g) def test_config(self): def f(cursor_offset, line): return ('hi', 2) def g(cursor_offset, line): return ('hey', 3) self.edits.add_config_attr('att', f) self.assertNotIn('att', self.edits) class config(object): att = 'c' key_dispatch = {'c': 'c'} configured_edits = self.edits.mapping_with_config(config, key_dispatch) self.assertTrue(configured_edits.__contains__, 'c') self.assertNotIn('c', self.edits) with self.assertRaises(NotImplementedError): configured_edits.add_config_attr('att2', g) with self.assertRaises(NotImplementedError): configured_edits.add('d', g) self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'), ('hi', 2)) if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/fodder/0000755000175100017510000000000013240411345017145 5ustar useruser00000000000000bpython-0.17.1/bpython/test/fodder/__init__.py0000644000175100017510000000000013240407731021250 0ustar useruser00000000000000bpython-0.17.1/bpython/test/fodder/processed.py0000644000175100017510000000110313240407731021505 0ustar useruser00000000000000#careful! Whitespace is very important in this file class BlankLineBetweenMethods(object): def method1(self): pass def method2(self): pass def BlankLineInFunction(self): return 7 pass #StartTest-blank_lines_in_for_loop for i in range(2): pass pass #EndTest #StartTest-blank_line_in_try_catch try: 1 except: 2 #EndTest #StartTest-blank_line_in_try_catch_else try: 1 except: 2 else: 3 #EndTest #StartTest-blank_trailing_line def foo(): return 1 #EndTest def tabs(): return 1 bpython-0.17.1/bpython/test/fodder/encoding_utf8.py0000644000175100017510000000010413240407731022252 0ustar useruser00000000000000# -*- coding: utf-8 -*- def foo(): """Test äöü""" pass bpython-0.17.1/bpython/test/fodder/original.py0000644000175100017510000000114113240407731021324 0ustar useruser00000000000000# careful: whitespace is very important in this file # also, this code runs - so everything should be a noop class BlankLineBetweenMethods(object): def method1(self): pass def method2(self): pass def BlankLineInFunction(self): return 7 pass #StartTest-blank_lines_in_for_loop for i in range(2): pass pass #EndTest #StartTest-blank_line_in_try_catch try: 1 except: 2 #EndTest #StartTest-blank_line_in_try_catch_else try: 1 except: 2 else: 3 #EndTest #StartTest-blank_trailing_line def foo(): return 1 #EndTest def tabs(): return 1 bpython-0.17.1/bpython/test/fodder/encoding_latin1.py0000644000175100017510000000010213240407731022552 0ustar useruser00000000000000# -*- coding: latin1 -*- def foo(): """Test """ pass bpython-0.17.1/bpython/test/fodder/encoding_ascii.py0000644000175100017510000000007513240407731022463 0ustar useruser00000000000000# -*- coding: ascii -*- def foo(): """Test""" pass bpython-0.17.1/bpython/test/test_line_properties.py0000644000175100017510000003027613240407731022532 0ustar useruser00000000000000# encoding: utf-8 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, \ current_method_definition_name, current_single_word, \ current_expression_attribute, current_dotted_attribute def cursor(s): """'ab|c' -> (2, 'abc')""" cursor_offset = s.index('|') line = s[:cursor_offset] + s[cursor_offset + 1:] return cursor_offset, line def decode(s): """'ad' -> ((3, 'abcd'), (1, 3, 'bdc'))""" if not s.count('|') == 1: raise ValueError('match helper needs | to occur once') 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] d = {} for i, m in enumerate(matches): d[m.group(0)] = m.start() - i s = s[:m.start() - i] + s[m.end() - i:] assert len(d) in [1, 3], 'need all the parts just once! %r' % d if '<' in d: return (d['|'], s), (d['<'], d['>'], s[d['<']:d['>']]) else: return (d['|'], s), None def line_with_cursor(cursor_offset, line): return line[:cursor_offset] + '|' + line[cursor_offset:] def encode(cursor_offset, line, result): """encode(3, 'abdcd', (1, 3, 'bdc')) -> ad' Written for prettier assert error messages """ encoded_line = line_with_cursor(cursor_offset, line) if result is None: return encoded_line start, end, value = result assert line[start:end] == value if start < cursor_offset: encoded_line = encoded_line[:start] + '<' + encoded_line[start:] else: encoded_line = (encoded_line[:start + 1] + '<' + encoded_line[start + 1:]) if end < cursor_offset: encoded_line = encoded_line[:end + 1] + '>' + encoded_line[end + 1:] else: encoded_line = encoded_line[:end + 2] + '>' + encoded_line[end + 2:] return encoded_line class LineTestCase(unittest.TestCase): def assertAccess(self, s): r"""Asserts that self.func matches as described by s, which uses a little language to describe matches: abcdhijklmnopqrstuvwx|yz /|\ /|\ /|\ | | | the function should the current cursor position match this "efg" is between the x and y """ (cursor_offset, line), match = decode(s) result = self.func(cursor_offset, line) self.assertEqual( result, match, "%s(%r) result\n%r (%r) doesn't match expected\n%r (%r)" % ( self.func.__name__, line_with_cursor(cursor_offset, line), encode(cursor_offset, line, result), result, s, match)) class TestHelpers(LineTestCase): def test_I(self): self.assertEqual(cursor('asd|fgh'), (3, 'asdfgh')) def test_decode(self): self.assertEqual(decode('ad'), ((3, 'abdcd'), (1, 4, 'bdc'))) self.assertEqual(decode('a|d'), ((1, 'abdcd'), (1, 4, 'bdc'))) self.assertEqual(decode('ad|'), ((5, 'abdcd'), (1, 4, 'bdc'))) def test_encode(self): self.assertEqual(encode(3, 'abdcd', (1, 4, 'bdc')), 'ad') self.assertEqual(encode(1, 'abdcd', (1, 4, 'bdc')), 'a|d') self.assertEqual(encode(4, 'abdcd', (1, 4, 'bdc')), 'ad') self.assertEqual(encode(5, 'abdcd', (1, 4, 'bdc')), 'ad|') def test_assert_access(self): def dumb_func(cursor_offset, line): return (0, 2, 'ab') self.func = dumb_func self.assertAccess('d') class TestCurrentWord(LineTestCase): def setUp(self): self.func = current_word def test_simple(self): self.assertAccess('|') self.assertAccess('|asdf') self.assertAccess('') self.assertAccess('') self.assertAccess('') self.assertAccess('asdf + ') self.assertAccess(' + asdf') def test_inside(self): self.assertAccess('') self.assertAccess('') def test_dots(self): self.assertAccess('') self.assertAccess('') self.assertAccess('') self.assertAccess('stuff[stuff] + {123: 456} + ') self.assertAccess('stuff[]') self.assertAccess('stuff[asdf[]') def test_non_dots(self): self.assertAccess('].asdf|') self.assertAccess(').asdf|') self.assertAccess('foo[0].asdf|') self.assertAccess('foo().asdf|') self.assertAccess('foo().|') self.assertAccess('foo().asdf.|') self.assertAccess('foo[0].asdf.|') def test_open_paren(self): self.assertAccess('') # documenting current behavior - TODO is this intended? class TestCurrentDictKey(LineTestCase): def setUp(self): self.func = current_dict_key def test_simple(self): self.assertAccess('asdf|') self.assertAccess('asdf|') self.assertAccess('asdf[<>|') self.assertAccess('asdf[<>|]') self.assertAccess('object.dict[') self.assertAccess('asdf|') self.assertAccess('asdf[<(>|]') self.assertAccess('asdf[<(1>|]') self.assertAccess('asdf[<(1,>|]') self.assertAccess('asdf[<(1, >|]') self.assertAccess('asdf[<(1, 2)>|]') # TODO self.assertAccess('d[d[<12|>') self.assertAccess("d[<'a>|") class TestCurrentDict(LineTestCase): def setUp(self): self.func = current_dict def test_simple(self): self.assertAccess('asdf|') self.assertAccess('asdf|') self.assertAccess('[|') self.assertAccess('[|]') self.assertAccess('[abc|') self.assertAccess('asdf|') class TestCurrentString(LineTestCase): def setUp(self): self.func = current_string def test_closed(self): self.assertAccess('""') self.assertAccess('""') self.assertAccess('"<|asdf>"') self.assertAccess("''") self.assertAccess("'<|asdf>'") self.assertAccess("''''''") self.assertAccess('""""""') self.assertAccess('asdf.afd("a") + ""') def test_open(self): self.assertAccess('"') self.assertAccess('"') self.assertAccess('"<|asdf>') self.assertAccess("'") self.assertAccess("'<|asdf>") self.assertAccess("'''") self.assertAccess('"""') self.assertAccess('asdf.afd("a") + "') class TestCurrentObject(LineTestCase): def setUp(self): self.func = current_object def test_simple(self): self.assertAccess('.attr1|') self.assertAccess('.|') self.assertAccess('Object|') self.assertAccess('Object|.') self.assertAccess('.|') self.assertAccess('.attr2|') self.assertAccess('.att|r1.attr2') self.assertAccess('stuff[stuff] + {123: 456} + .attr2|') self.assertAccess('stuff[asd|fg]') self.assertAccess('stuff[asdf[asd|fg]') class TestCurrentAttribute(LineTestCase): def setUp(self): self.func = current_object_attribute def test_simple(self): self.assertAccess('Object.') self.assertAccess('Object.attr1.') self.assertAccess('Object..attr2') self.assertAccess('stuff[stuff] + {123: 456} + Object.attr1.') self.assertAccess('stuff[asd|fg]') self.assertAccess('stuff[asdf[asd|fg]') self.assertAccess('Object.attr1.<|attr2>') self.assertAccess('Object..attr2') class TestCurrentFromImportFrom(LineTestCase): def setUp(self): self.func = current_from_import_from def test_simple(self): self.assertAccess('from import path') self.assertAccess('from import path|') self.assertAccess('if True|: from sys import path') self.assertAccess('if True: |from sys import path') self.assertAccess('if True: from import p|ath') self.assertAccess('if True: from sys imp|ort path') self.assertAccess('if True: from sys import |path') self.assertAccess('if True: from sys import path.stu|ff') self.assertAccess('if True: from import sep|') self.assertAccess('from ') class TestCurrentFromImportImport(LineTestCase): def setUp(self): self.func = current_from_import_import def test_simple(self): self.assertAccess('from sys import ') self.assertAccess('from sys import ') self.assertAccess('from sys import |path') self.assertAccess('from sys| import path') self.assertAccess('from s|ys import path') self.assertAccess('from |sys import path') self.assertAccess('from xml.dom import ') # because syntax error self.assertAccess('from xml.dom import Node.as|d') class TestCurrentImport(LineTestCase): def setUp(self): self.func = current_import def test_simple(self): self.assertAccess('import ') self.assertAccess('import ') self.assertAccess('import |path') self.assertAccess('import path, ') self.assertAccess('import path another|') self.assertAccess('if True: import ') self.assertAccess('if True: import ') self.assertAccess('if True: import ') self.assertAccess('if True: import as something') class TestMethodDefinitionName(LineTestCase): def setUp(self): self.func = current_method_definition_name def test_simple(self): self.assertAccess('def ') self.assertAccess(' def bar(x, y)|:') self.assertAccess(' def (x, y)') class TestSingleWord(LineTestCase): def setUp(self): self.func = current_single_word def test_simple(self): self.assertAccess('foo.bar|') self.assertAccess('.foo|') self.assertAccess(' ') class TestCurrentExpressionAttribute(LineTestCase): def setUp(self): self.func = current_expression_attribute def test_simple(self): self.assertAccess('Object..') self.assertAccess('Object.<|attr1>.') self.assertAccess('Object.(|)') self.assertAccess('Object.another.(|)') self.assertAccess('asdf asdf asdf.(abc|)') def test_without_dot(self): self.assertAccess('Object|') self.assertAccess('Object|.') self.assertAccess('|Object.') def test_with_whitespace(self): self.assertAccess('Object. ') self.assertAccess('Object .') self.assertAccess('Object . ') self.assertAccess('Object .asdf attr|') self.assertAccess('Object . attr') self.assertAccess('Object. asdf attr|') self.assertAccess('Object. attr') self.assertAccess('Object . asdf attr|') self.assertAccess('Object . attr') def test_indexing(self): self.assertAccess('abc[def].') self.assertAccess('abc[def].<|ghi>') self.assertAccess('abc[def].') self.assertAccess('abc[def].gh |i') self.assertAccess('abc[def]|') def test_strings(self): self.assertAccess('"hey".') self.assertAccess('"hey"|') self.assertAccess('"hey"|.a') self.assertAccess('"hey".') self.assertAccess('"hey".asdf d|') self.assertAccess('"hey".<|>') class TestCurrentDottedAttribute(LineTestCase): def setUp(self): self.func = current_dotted_attribute def test_simple(self): self.assertAccess('|') self.assertAccess('(|') self.assertAccess('[|') self.assertAccess('m.body[0].value|') self.assertAccess('m.body[0].attr.value|') if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_curtsies.py0000644000175100017510000000566613240407731021175 0ustar useruser00000000000000# coding: utf-8 from __future__ import unicode_literals from collections import namedtuple from bpython.curtsies import combined_events from bpython.test import (FixLanguageTestCase as TestCase, unittest) import curtsies.events ScheduledEvent = namedtuple('ScheduledEvent', ['when', 'event']) class EventGenerator(object): def __init__(self, initial_events=(), scheduled_events=()): self._events = [] self._current_tick = 0 for e in initial_events: self.schedule_event(e, 0) for e, w in scheduled_events: self.schedule_event(e, w) def schedule_event(self, event, when): self._events.append(ScheduledEvent(when, event)) self._events.sort() def send(self, timeout=None): if timeout not in [None, 0]: raise ValueError('timeout value %r not supported' % timeout) if not self._events: return None if self._events[0].when <= self._current_tick: return self._events.pop(0).event if timeout == 0: return None elif timeout is None: e = self._events.pop(0) self._current_tick = e.when return e.event else: raise ValueError('timeout value %r not supported' % timeout) def tick(self, dt=1): self._current_tick += dt return self._current_tick class TestCurtsiesPasteDetection(TestCase): def test_paste_threshold(self): eg = EventGenerator(list('abc')) cb = combined_events(eg, paste_threshold=3) e = next(cb) self.assertIsInstance(e, curtsies.events.PasteEvent) self.assertEqual(e.events, list('abc')) self.assertEqual(next(cb), None) eg = EventGenerator(list('abc')) cb = combined_events(eg, paste_threshold=4) self.assertEqual(next(cb), 'a') self.assertEqual(next(cb), 'b') self.assertEqual(next(cb), 'c') self.assertEqual(next(cb), None) def test_set_timeout(self): eg = EventGenerator('a', zip('bcdefg', [1, 2, 3, 3, 3, 4])) eg.schedule_event(curtsies.events.SigIntEvent(), 5) eg.schedule_event('h', 6) cb = combined_events(eg, paste_threshold=3) self.assertEqual(next(cb), 'a') self.assertEqual(cb.send(0), None) self.assertEqual(next(cb), 'b') self.assertEqual(cb.send(0), None) eg.tick() self.assertEqual(cb.send(0), 'c') self.assertEqual(cb.send(0), None) eg.tick() self.assertIsInstance(cb.send(0), curtsies.events.PasteEvent) self.assertEqual(cb.send(0), None) self.assertEqual(cb.send(None), 'g') self.assertEqual(cb.send(0), None) eg.tick(1) self.assertIsInstance(cb.send(0), curtsies.events.SigIntEvent) self.assertEqual(cb.send(0), None) self.assertEqual(cb.send(None), 'h') self.assertEqual(cb.send(None), None) if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_keys.py0000644000175100017510000000521513240407731020275 0ustar useruser00000000000000# encoding: utf-8 from bpython import keys from bpython.test import unittest class TestCLIKeys(unittest.TestCase): def test_keymap_map(self): """Verify KeyMap.map being a dictionary with the correct length.""" self.assertEqual(len(keys.cli_key_dispatch.map), 43) def test_keymap_setitem(self): """Verify keys.KeyMap correctly setting items.""" keys.cli_key_dispatch['simon'] = 'awesome' self.assertEqual(keys.cli_key_dispatch['simon'], 'awesome') def test_keymap_delitem(self): """Verify keys.KeyMap correctly removing items.""" keys.cli_key_dispatch['simon'] = 'awesome' del keys.cli_key_dispatch['simon'] if 'simon' in keys.cli_key_dispatch.map: raise Exception('Key still exists in dictionary') def test_keymap_getitem(self): """Verify keys.KeyMap correctly looking up items.""" self.assertEqual(keys.cli_key_dispatch['C-['], (chr(27), '^[')) self.assertEqual(keys.cli_key_dispatch['F11'], ('KEY_F(11)',)) self.assertEqual(keys.cli_key_dispatch['C-a'], ('\x01', '^A')) def test_keymap_keyerror(self): """Verify keys.KeyMap raising KeyError when getting undefined key""" with self.assertRaises(KeyError): keys.cli_key_dispatch['C-asdf'] keys.cli_key_dispatch['C-qwerty'] class TestUrwidKeys(unittest.TestCase): def test_keymap_map(self): """Verify KeyMap.map being a dictionary with the correct length.""" self.assertEqual(len(keys.urwid_key_dispatch.map), 64) def test_keymap_setitem(self): """Verify keys.KeyMap correctly setting items.""" keys.urwid_key_dispatch['simon'] = 'awesome' self.assertEqual(keys.urwid_key_dispatch['simon'], 'awesome') def test_keymap_delitem(self): """Verify keys.KeyMap correctly removing items.""" keys.urwid_key_dispatch['simon'] = 'awesome' del keys.urwid_key_dispatch['simon'] if 'simon' in keys.urwid_key_dispatch.map: raise Exception('Key still exists in dictionary') def test_keymap_getitem(self): """Verify keys.KeyMap correctly looking up items.""" self.assertEqual(keys.urwid_key_dispatch['F11'], 'f11') self.assertEqual(keys.urwid_key_dispatch['C-a'], 'ctrl a') self.assertEqual(keys.urwid_key_dispatch['M-a'], 'meta a') def test_keymap_keyerror(self): """Verify keys.KeyMap raising KeyError when getting undefined key""" with self.assertRaises(KeyError): keys.urwid_key_dispatch['C-asdf'] keys.urwid_key_dispatch['C-qwerty'] if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_history.py0000644000175100017510000000475413240407731021032 0ustar useruser00000000000000# encoding: utf-8 from six.moves import range from bpython.history import History from bpython.test import unittest class TestHistory(unittest.TestCase): def setUp(self): self.history = History('#%d' % x for x in range(1000)) def test_is_at_start(self): self.history.first() self.assertNotEqual(self.history.index, 0) self.assertTrue(self.history.is_at_end) self.history.forward() self.assertFalse(self.history.is_at_end) def test_is_at_end(self): self.history.last() self.assertEqual(self.history.index, 0) self.assertTrue(self.history.is_at_start) self.assertFalse(self.history.is_at_end) def test_first(self): self.history.first() self.assertFalse(self.history.is_at_start) self.assertTrue(self.history.is_at_end) def test_last(self): self.history.last() self.assertTrue(self.history.is_at_start) self.assertFalse(self.history.is_at_end) def test_back(self): self.assertEqual(self.history.back(), '#999') self.assertNotEqual(self.history.back(), '#999') self.assertEqual(self.history.back(), '#997') for x in range(997): self.history.back() self.assertEqual(self.history.back(), '#0') def test_forward(self): self.history.first() self.assertEqual(self.history.forward(), '#1') self.assertNotEqual(self.history.forward(), '#1') self.assertEqual(self.history.forward(), '#3') # 1000 == entries 4 == len(range(1, 3) ===> '#1000' (so +1) for x in range(1000 - 4 - 1): self.history.forward() self.assertEqual(self.history.forward(), '#999') def test_append(self): self.history.append('print "foo\n"\n') self.history.append('\n') self.assertEqual(self.history.back(), 'print "foo\n"') def test_enter(self): self.history.enter('#lastnumber!') self.assertEqual(self.history.back(), '#lastnumber!') self.assertEqual(self.history.forward(), '#lastnumber!') def test_enter_2(self): self.history.enter('#50') self.assertEqual(self.history.back(), '#509') self.assertEqual(self.history.back(), '#508') self.assertEqual(self.history.forward(), '#509') def test_reset(self): self.history.enter('#lastnumber!') self.history.reset() self.assertEqual(self.history.back(), '#999') self.assertEqual(self.history.forward(), '') bpython-0.17.1/bpython/test/test_autocomplete.py0000644000175100017510000004004713240407731022025 0ustar useruser00000000000000# encoding: utf-8 from collections import namedtuple import inspect import keyword import sys try: import unittest2 as unittest except ImportError: import unittest try: import jedi has_jedi = True except ImportError: has_jedi = False from bpython import autocomplete from bpython._py3compat import py3 from bpython.test import mock is_py34 = sys.version_info[:2] >= (3, 4) if is_py34: glob_function = 'glob.iglob' else: glob_function = 'glob.glob' class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): with self.assertRaises(autocomplete.EvaluationError): autocomplete.safe_eval('1re', {}) class TestFormatters(unittest.TestCase): def test_filename(self): completer = autocomplete.FilenameCompletion() last_part_of_filename = completer.format self.assertEqual(last_part_of_filename('abc'), 'abc') self.assertEqual(last_part_of_filename('abc/'), 'abc/') self.assertEqual(last_part_of_filename('abc/efg'), 'efg') self.assertEqual(last_part_of_filename('abc/efg/'), 'efg/') self.assertEqual(last_part_of_filename('/abc'), 'abc') self.assertEqual(last_part_of_filename('ab.c/e.f.g/'), 'e.f.g/') def test_attribute(self): self.assertEqual(autocomplete.after_last_dot('abc.edf'), 'edf') def completer(matches): mock_completer = autocomplete.BaseCompletionType() mock_completer.matches = mock.Mock(return_value=matches) return mock_completer class TestGetCompleter(unittest.TestCase): def test_no_completers(self): self.assertTupleEqual(autocomplete.get_completer([], 0, ''), ([], None)) def test_one_completer_without_matches_returns_empty_list_and_none(self): a = completer([]) self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), ([], None)) def test_one_completer_returns_matches_and_completer(self): a = completer(['a']) self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), (['a'], a)) def test_two_completers_with_matches_returns_first_matches(self): a = completer(['a']) b = completer(['b']) self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], a)) def test_first_non_none_completer_matches_are_returned(self): a = completer([]) b = completer(['a']) self.assertEqual(autocomplete.get_completer([a, b], 0, ''), ([], None)) def test_only_completer_returns_None(self): a = completer(None) self.assertEqual(autocomplete.get_completer([a], 0, ''), ([], None)) def test_first_completer_returns_None(self): a = completer(None) b = completer(['a']) self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], b)) class TestCumulativeCompleter(unittest.TestCase): def completer(self, matches, ): mock_completer = autocomplete.BaseCompletionType() mock_completer.matches = mock.Mock(return_value=matches) return mock_completer def test_no_completers_fails(self): with self.assertRaises(ValueError): autocomplete.CumulativeCompleter([]) def test_one_empty_completer_returns_empty(self): a = self.completer([]) cumulative = autocomplete.CumulativeCompleter([a]) self.assertEqual(cumulative.matches(3, 'abc'), set()) def test_one_none_completer_returns_none(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) self.assertEqual(cumulative.matches(3, 'abc'), None) def test_two_completers_get_both(self): a = self.completer(['a']) b = self.completer(['b']) cumulative = autocomplete.CumulativeCompleter([a, b]) self.assertEqual(cumulative.matches(3, 'abc'), set(['a', 'b'])) class TestFilenameCompletion(unittest.TestCase): def setUp(self): self.completer = autocomplete.FilenameCompletion() def test_locate_fails_when_not_in_string(self): self.assertEqual(self.completer.locate(4, "abcd"), None) def test_locate_succeeds_when_in_string(self): self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) def test_issue_491(self): self.assertNotEqual(self.completer.matches(9, '"a[a.l-1]'), None) @mock.patch(glob_function, new=lambda text: []) def test_match_returns_none_if_not_in_string(self): self.assertEqual(self.completer.matches(2, 'abcd'), None) @mock.patch(glob_function, new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): self.assertEqual(self.completer.matches(2, '"a'), set()) @mock.patch(glob_function, new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_match_returns_files_when_files_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa', 'abcde']) @mock.patch(glob_function, new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @mock.patch('os.path.sep', new='/') def test_match_returns_dirs_when_dirs_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa/', 'abcde/']) @mock.patch(glob_function, new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_tilde_stays_pretty(self): self.assertEqual(sorted(self.completer.matches(4, '"~/a')), ['~/aaaaa', '~/abcde']) @mock.patch('os.path.sep', new='/') def test_formatting_takes_just_last_part(self): self.assertEqual(self.completer.format('/hello/there/'), 'there/') self.assertEqual(self.completer.format('/hello/there'), 'there') class MockNumPy(object): """This is a mock numpy object that raises an error when there is an atempt to convert it to a boolean.""" def __nonzero__(self): raise ValueError("The truth value of an array with more than one " "element is ambiguous. Use a.any() or a.all()") class TestDictKeyCompletion(unittest.TestCase): def test_set_of_keys_returned_when_matches_found(self): com = autocomplete.DictKeyCompletion() local = {'d': {"ab": 1, "cd": 2}} self.assertSetEqual(com.matches(2, "d[", locals_=local), set(["'ab']", "'cd']"])) def test_none_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() local = {'e': {"ab": 1, "cd": 2}} self.assertEqual(com.matches(2, "d[", locals_=local), None) def test_none_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local = {'l': ["ab", "cd"]} 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.assertEqual(com.matches(7, "mNumPy[", locals_=local), None) class Foo(object): a = 10 def __init__(self): self.b = 20 def method(self, x): pass class OldStyleFoo: a = 10 def __init__(self): self.b = 20 def method(self, x): pass skip_old_style = unittest.skipIf(py3, 'In Python 3 there are no old style classes') class Properties(Foo): @property def asserts_when_called(self): raise AssertionError("getter method called") class Slots(object): __slots__ = ['a', 'b'] class TestAttrCompletion(unittest.TestCase): @classmethod def setUpClass(cls): cls.com = autocomplete.AttrCompletion() def test_att_matches_found_on_instance(self): self.assertSetEqual(self.com.matches(2, 'a.', locals_={'a': Foo()}), set(['a.method', 'a.a', 'a.b'])) @skip_old_style def test_att_matches_found_on_old_style_instance(self): self.assertSetEqual(self.com.matches(2, 'a.', locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', 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(4, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): class OldStyleWithBrokenGetAttr: def __getattr__(self, attr): raise Exception() locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', self.com.matches(4, 'a.__', locals_=locals_)) def test_descriptor_attributes_not_run(self): com = autocomplete.AttrCompletion() self.assertSetEqual(com.matches(2, 'a.', locals_={'a': Properties()}), set(['a.b', 'a.a', 'a.method', 'a.asserts_when_called'])) def test_slots_not_crash(self): com = autocomplete.AttrCompletion() self.assertSetEqual(com.matches(2, 'A.', locals_={'A': Slots}), set(['A.b', 'A.a', 'A.mro'])) class TestExpressionAttributeCompletion(unittest.TestCase): @classmethod def setUpClass(cls): cls.com = autocomplete.ExpressionAttributeCompletion() def test_att_matches_found_on_instance(self): self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [Foo()]}), set(['method', 'a', '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(['method', 'a', '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(['method', 'a', '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(['method', 'a', '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()}) def test_literals_complete(self): self.assertSetEqual(self.com.matches(10, '[a][0][0].', locals_={'a': (Foo(),)}), set(['method', 'a', 'b'])) def test_dictionaries_complete(self): self.assertSetEqual(self.com.matches(7, 'a["b"].', locals_={'a': {'b': Foo()}}), set(['method', 'a', 'b'])) class TestMagicMethodCompletion(unittest.TestCase): def test_magic_methods_complete_after_double_underscores(self): com = autocomplete.MagicMethodCompletion() block = "class Something(object)\n def __" self.assertSetEqual(com.matches(10, ' def __', current_block=block), set(autocomplete.MAGIC_METHODS)) Comp = namedtuple('Completion', ['name', 'complete']) @unittest.skipUnless(has_jedi, "jedi required") class TestMultilineJediCompletion(unittest.TestCase): def test_returns_none_with_single_line(self): com = autocomplete.MultilineJediCompletion() self.assertEqual(com.matches(2, 'Va', current_block='Va', history=[]), None) def test_returns_non_with_blank_second_line(self): com = autocomplete.MultilineJediCompletion() self.assertEqual(com.matches(0, '', current_block='class Foo():\n', history=['class Foo():']), None) def matches_from_completions(self, cursor, line, block, history, completions): with mock.patch('bpython.autocomplete.jedi.Script') as Script: script = Script.return_value script.completions.return_value = completions com = autocomplete.MultilineJediCompletion() return com.matches(cursor, line, current_block=block, history=history) def test_completions_starting_with_different_letters(self): matches = self.matches_from_completions( 2, ' a', 'class Foo:\n a', ['adsf'], [Comp('Abc', 'bc'), Comp('Cbc', 'bc')]) self.assertEqual(matches, None) def test_completions_starting_with_different_cases(self): matches = self.matches_from_completions( 2, ' a', 'class Foo:\n a', ['adsf'], [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): def setUp(self): self.com = autocomplete.GlobalCompletion() def test_function(self): def function(): pass self.assertEqual(self.com.matches(8, 'function', locals_={'function': function}), set(('function(', ))) def test_completions_are_unicode(self): for m in self.com.matches(1, 'a', locals_={'abc': 10}): self.assertIsInstance(m, type(u'')) @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") def test_ignores_nonascii_encodable(self): self.assertEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), None) def test_mock_kwlist(self): with mock.patch.object(keyword, 'kwlist', new=['abcd']): self.assertEqual(self.com.matches(3, 'abc', locals_={}), None) def test_mock_kwlist_non_ascii(self): with mock.patch.object(keyword, 'kwlist', new=['abcß']): self.assertEqual(self.com.matches(3, 'abc', locals_={}), None) class TestParameterNameCompletion(unittest.TestCase): def test_set_of_params_returns_when_matches_found(self): def func(apple, apricot, banana, carrot): pass if py3: argspec = list(inspect.getfullargspec(func)) else: argspec = list(inspect.getargspec(func)) argspec = ["func", argspec, False] com = autocomplete.ParameterNameCompletion() self.assertSetEqual(com.matches(1, "a", argspec=argspec), set(['apple=', 'apricot='])) self.assertSetEqual(com.matches(2, "ba", argspec=argspec), set(['banana='])) self.assertSetEqual(com.matches(3, "car", argspec=argspec), set(['carrot='])) bpython-0.17.1/bpython/test/test_simpleeval.py0000644000175100017510000002030413240410473021454 0ustar useruser00000000000000# -*- coding: utf-8 -*- import ast import numbers from bpython.simpleeval import (simple_eval, evaluate_current_expression, EvaluationError, safe_get_attribute, safe_get_attribute_new_style) from bpython.test import unittest from bpython._py3compat import py3 class TestSimpleEval(unittest.TestCase): def assertMatchesStdlib(self, expr): self.assertEqual(ast.literal_eval(expr), simple_eval(expr)) def test_matches_stdlib(self): """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("[1]") self.assertMatchesStdlib("{(1,): [2,3,{}]}") def test_indexing(self): """Literals can be indexed into""" self.assertEqual(simple_eval('[1,2][0]'), 1) self.assertEqual(simple_eval('a', {'a': 1}), 1) def test_name_lookup(self): """Names can be lookup up in a namespace""" self.assertEqual(simple_eval('a', {'a': 1}), 1) self.assertEqual(simple_eval('map'), map) self.assertEqual(simple_eval('a[b]', {'a': {'c': 1}, 'b': 'c'}), 1) def test_allow_name_lookup(self): """Names can be lookup up in a namespace""" self.assertEqual(simple_eval('a', {'a': 1}), 1) def test_lookup_on_suspicious_types(self): class FakeDict(object): pass with self.assertRaises(ValueError): simple_eval('a[1]', {'a': FakeDict()}) class TrickyDict(dict): def __getitem__(self, index): self.fail("doing key lookup isn't safe") with self.assertRaises(ValueError): simple_eval('a[1]', {'a': TrickyDict()}) class SchrodingersDict(dict): def __getattribute__(inner_self, attr): self.fail("doing attribute lookup might have side effects") with self.assertRaises(ValueError): simple_eval('a[1]', {'a': SchrodingersDict()}) class SchrodingersCatsDict(dict): def __getattr__(inner_self, attr): self.fail("doing attribute lookup might have side effects") with self.assertRaises(ValueError): simple_eval('a[1]', {'a': SchrodingersDict()}) def test_operators_on_suspicious_types(self): class Spam(numbers.Number): def __add__(inner_self, other): self.fail("doing attribute lookup might have side effects") with self.assertRaises(ValueError): simple_eval('a + 1', {'a': Spam()}) def test_operators_on_numbers(self): self.assertEqual(simple_eval('-2'), -2) self.assertEqual(simple_eval('1 + 1'), 2) self.assertEqual(simple_eval('a - 2', {'a': 1}), -1) with self.assertRaises(ValueError): simple_eval('2 * 3') with self.assertRaises(ValueError): simple_eval('2 ** 3') def test_function_calls_raise(self): with self.assertRaises(ValueError): simple_eval('1()') def test_nonexistant_names_raise(self): with self.assertRaises(EvaluationError): simple_eval('a') def test_attribute_access(self): class Foo(object): abc = 1 self.assertEqual(simple_eval('foo.abc', {'foo': Foo()}), 1) class TestEvaluateCurrentExpression(unittest.TestCase): def assertEvaled(self, line, value, ns=None): assert line.count('|') == 1 cursor_offset = line.find('|') line = line.replace('|', '') self.assertEqual(evaluate_current_expression(cursor_offset, line, ns), value) def assertCannotEval(self, line, ns=None): assert line.count('|') == 1 cursor_offset = line.find('|') line = line.replace('|', '') with self.assertRaises(EvaluationError): evaluate_current_expression(cursor_offset, line, ns) def test_simple(self): self.assertEvaled('[1].a|bc', [1]) self.assertEvaled('[1].abc|', [1]) self.assertEvaled('[1].|abc', [1]) self.assertEvaled('[1]. |abc', [1]) self.assertEvaled('[1] .|abc', [1]) self.assertCannotEval('[1].abc |', [1]) self.assertCannotEval('[1]. abc |', [1]) self.assertCannotEval('[2][1].a|bc', [1]) def test_nonsense(self): self.assertEvaled('!@#$ [1].a|bc', [1]) self.assertEvaled('--- [2][0].a|bc', 2) self.assertCannotEval('"asdf".centered()[1].a|bc') self.assertEvaled('"asdf"[1].a|bc', 's') def test_with_namespace(self): self.assertEvaled('a[1].a|bc', 'd', {'a': 'adsf'}) self.assertCannotEval('a[1].a|bc', {}) class A(object): a = 'a' class B(A): b = 'b' class Property(object): @property def prop(self): raise AssertionError('Property __get__ executed') class Slots(object): __slots__ = ['s1', 's2', 's3'] if not py3: @property def s3(self): raise AssertionError('Property __get__ executed') class SlotsSubclass(Slots): @property def s4(self): raise AssertionError('Property __get__ executed') class OverriddenGetattr(object): def __getattr__(self, attr): raise AssertionError('custom __getattr__ executed') a = 1 class OverriddenGetattribute(object): def __getattribute__(self, attr): raise AssertionError('custom __getattribute__ executed') a = 1 class OverriddenMRO(object): def __mro__(self): raise AssertionError('custom mro executed') a = 1 member_descriptor = type(Slots.s1) class TestSafeGetAttribute(unittest.TestCase): def test_lookup_on_object(self): a = A() a.x = 1 self.assertEquals(safe_get_attribute_new_style(a, 'x'), 1) self.assertEquals(safe_get_attribute_new_style(a, 'a'), 'a') b = B() b.y = 2 self.assertEquals(safe_get_attribute_new_style(b, 'y'), 2) self.assertEquals(safe_get_attribute_new_style(b, 'a'), 'a') self.assertEquals(safe_get_attribute_new_style(b, 'b'), 'b') def test_avoid_running_properties(self): p = Property() self.assertEquals(safe_get_attribute_new_style(p, 'prop'), Property.prop) @unittest.skipIf(py3, 'Old-style classes not in Python 3') def test_raises_on_old_style_class(self): class Old: pass with self.assertRaises(ValueError): safe_get_attribute_new_style(Old, 'asdf') def test_lookup_with_slots(self): s = Slots() s.s1 = 's1' self.assertEquals(safe_get_attribute(s, 's1'), 's1') self.assertIsInstance(safe_get_attribute_new_style(s, 's1'), member_descriptor) with self.assertRaises(AttributeError): safe_get_attribute(s, 's2') self.assertIsInstance(safe_get_attribute_new_style(s, 's2'), member_descriptor) def test_lookup_on_slots_classes(self): sga = safe_get_attribute s = SlotsSubclass() self.assertIsInstance(sga(Slots, 's1'), member_descriptor) self.assertIsInstance(sga(SlotsSubclass, 's1'), member_descriptor) self.assertIsInstance(sga(SlotsSubclass, 's4'), property) self.assertIsInstance(sga(s, 's4'), property) @unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class") def test_lookup_with_property_and_slots(self): sga = safe_get_attribute s = SlotsSubclass() self.assertIsInstance(sga(Slots, 's3'), property) self.assertEquals(safe_get_attribute(s, 's3'), Slots.__dict__['s3']) self.assertIsInstance(sga(SlotsSubclass, 's3'), property) def test_lookup_on_overridden_methods(self): sga = safe_get_attribute self.assertEqual(sga(OverriddenGetattr(), 'a'), 1) self.assertEqual(sga(OverriddenGetattribute(), 'a'), 1) self.assertEqual(sga(OverriddenMRO(), 'a'), 1) with self.assertRaises(AttributeError): sga(OverriddenGetattr(), 'b') with self.assertRaises(AttributeError): sga(OverriddenGetattribute(), 'b') with self.assertRaises(AttributeError): sga(OverriddenMRO(), 'b') if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_interpreter.py0000644000175100017510000002047513240410473021667 0ustar useruser00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import sys import re from textwrap import dedent 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, unittest pypy = 'PyPy' in sys.version def remove_ansi(s): return re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]'.encode('ascii'), b'', s) class TestInterpreter(unittest.TestCase): def interp_errlog(self): i = interpreter.Interp() a = [] i.write = a.append return i, a def err_lineno(self, a): strings = [x.__unicode__() for x in a] for line in reversed(strings): clean_line = remove_ansi(line) m = re.search(r'line (\d+)[,]', clean_line) if m: return int(m.group(1)) return None def test_syntaxerror(self): i, a = self.interp_errlog() i.runsource('1.1.1.1') if pypy: expected = ( ' File ' + green('""') + ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n') else: expected = ( ' File ' + green('""') + ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) def test_traceback(self): i, a = self.interp_errlog() def f(): return 1 / 0 def gfunc(): return f() i.runsource('gfunc()') if pypy: global_not_found = "global name 'gfunc' is not defined" else: global_not_found = "name 'gfunc' is not defined" expected = ( 'Traceback (most recent call last):\n File ' + green('""') + ', line ' + bold(magenta('1')) + ', in ' + cyan('') + '\n gfunc()\n' + bold(red('NameError')) + ': ' + cyan(global_not_found) + '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") def test_runsource_bytes(self): i = interpreter.Interp(encoding=b'latin-1') i.runsource("a = b'\xfe'".encode('latin-1'), encode=False) self.assertIsInstance(i.locals['a'], str) self.assertEqual(i.locals['a'], b"\xfe") i.runsource("b = u'\xfe'".encode('latin-1'), encode=False) self.assertIsInstance(i.locals['b'], unicode) self.assertEqual(i.locals['b'], "\xfe") @unittest.skipUnless(py3, "Only a syntax error in Python 3") def test_runsource_bytes_over_128_syntax_error_py3(self): i = interpreter.Interp(encoding=b'latin-1') i.showsyntaxerror = mock.Mock(return_value=None) i.runsource("a = b'\xfe'") i.showsyntaxerror.assert_called_with(mock.ANY) @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_bytes_over_128_syntax_error_py2(self): i = interpreter.Interp(encoding=b'latin-1') i.runsource(b"a = b'\xfe'") self.assertIsInstance(i.locals['a'], type(b'')) self.assertEqual(i.locals['a'], b"\xfe") @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_unicode(self): i = interpreter.Interp(encoding=b'latin-1') i.runsource("a = u'\xfe'") self.assertIsInstance(i.locals['a'], type(u'')) self.assertEqual(i.locals['a'], u"\xfe") def test_getsource_works_on_interactively_defined_functions(self): source = 'def foo(x):\n return x + 1\n' i = interpreter.Interp() i.runsource(source) import inspect inspected_source = inspect.getsource(i.locals['foo']) self.assertEquals(inspected_source, source) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_unicode_autoencode_and_noencode(self): """error line numbers should be fixed""" # Since correct behavior for unicode is the same # for auto and False, run the same tests for encode in ['auto', False]: i, a = self.interp_errlog() i.runsource(u'[1 + 1,\nabcd]', encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(u'[1 + 1,\nabcd]', encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(u'#encoding: utf-8\nabcd', encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(u'#encoding: utf-8\nabcd', filename='x.py', encode=encode) self.assertIn('SyntaxError:', ''.join(''.join(remove_ansi(x.__unicode__()) for x in a))) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_unicode_encode(self): i, _ = self.interp_errlog() with self.assertRaises(ValueError): i.runsource(u'1 + 1', encode=True) i, _ = self.interp_errlog() with self.assertRaises(ValueError): i.runsource(u'1 + 1', filename='x.py', encode=True) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_noencode(self): i, a = self.interp_errlog() i.runsource(b'[1 + 1,\nabcd]', encode=False) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(b'[1 + 1,\nabcd]', filename='x.py', encode=False) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(dedent(b'''\ #encoding: utf-8 ["%s", abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=False) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() i.runsource(dedent(b'''\ #encoding: utf-8 ["%s", abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), filename='x.py', encode=False) self.assertEqual(self.err_lineno(a), 4) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_encode(self): i, a = self.interp_errlog() i.runsource(b'[1 + 1,\nabcd]', encode=True) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() with self.assertRaises(ValueError): i.runsource(b'[1 + 1,\nabcd]', filename='x.py', encode=True) i, a = self.interp_errlog() i.runsource(dedent(b'''\ #encoding: utf-8 [u"%s", abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=True) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() with self.assertRaises(ValueError): i.runsource(dedent(b'''\ #encoding: utf-8 [u"%s", abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), filename='x.py', encode=True) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_autoencode(self): i, a = self.interp_errlog() i.runsource(b'[1 + 1,\n abcd]') self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(b'[1 + 1,\nabcd]', filename='x.py') self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() i.runsource(dedent(b'''\ #encoding: utf-8 [u"%s", abcd]''' % (u'åß∂ƒ'.encode('utf8'),))) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() i.runsource(dedent(b'''\ #encoding: utf-8 [u"%s", abcd]''' % (u'åß∂ƒ'.encode('utf8'),))) self.assertEqual(self.err_lineno(a), 4) bpython-0.17.1/bpython/test/test_curtsies_repl.py0000644000175100017510000004220313240410473022200 0ustar useruser00000000000000# coding: utf-8 from __future__ import unicode_literals import code import os import sys import tempfile import io from functools import partial from contextlib import contextmanager from six.moves import StringIO from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython.curtsiesfrontend import events as bpythonevents from bpython import autocomplete from bpython import config from bpython import args from bpython._py3compat import py3 from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, unittest, TEST_CONFIG) from curtsies import events if py3: from importlib import invalidate_caches else: def invalidate_caches(): """Does not exist before Python 3.3""" def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, TEST_CONFIG) for key, value in conf.items(): if not hasattr(config_struct, key): raise ValueError("%r is not a valid config attribute" % (key, )) setattr(config_struct, key, value) return config_struct class TestCurtsiesRepl(TestCase): def setUp(self): self.repl = create_repl() def cfwp(self, source): return interpreter.code_finished_will_parse(source, self.repl.interp.compile) def test_code_finished_will_parse(self): self.repl.buffer = ['1 + 1'] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) self.repl.buffer = ['def foo(x):'] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (False, True)) self.repl.buffer = ['def foo(x)'] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) self.repl.buffer = ['def foo(x):', 'return 1'] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) self.repl.buffer = ['def foo(x):', ' return 1'] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) self.repl.buffer = ['def foo(x):', ' return 1', ''] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), 'Charset can not encode characters') def test_external_communication_encoding(self): with captured_output(): self.repl.display_lines.append('>>> "åß∂ƒ"') self.repl.send_session_to_external_editor() def test_get_last_word(self): self.repl.rl_history.entries = ['1', '2 3', '4 5 6'] self.repl._set_current_line('abcde') self.repl.get_last_word() self.assertEqual(self.repl.current_line, 'abcde6') self.repl.get_last_word() self.assertEqual(self.repl.current_line, 'abcde3') def test_last_word(self): self.assertEquals(curtsiesrepl._last_word(''), '') self.assertEquals(curtsiesrepl._last_word(' '), '') self.assertEquals(curtsiesrepl._last_word('a'), 'a') self.assertEquals(curtsiesrepl._last_word('a b'), 'b') # this is the behavior of bash - not currently implemented @unittest.skip def test_get_last_word_with_prev_line(self): self.repl.rl_history.entries = ['1', '2 3', '4 5 6'] self.repl._set_current_line('abcde') self.repl.up_one_line() self.assertEqual(self.repl.current_line, '4 5 6') self.repl.get_last_word() self.assertEqual(self.repl.current_line, '4 5 63') self.repl.get_last_word() self.assertEqual(self.repl.current_line, '4 5 64') self.repl.up_one_line() self.assertEqual(self.repl.current_line, '2 3') def mock_next(obj, return_value): if py3: obj.__next__.return_value = return_value else: obj.next.return_value = return_value class TestCurtsiesReplTab(TestCase): def setUp(self): self.repl = create_repl() self.repl.matches_iter = MagicIterMock() def add_matches(*args, **kwargs): self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] self.repl.complete = mock.Mock(side_effect=add_matches, return_value=True) def test_tab_with_no_matches_triggers_completion(self): self.repl._current_line = ' asdf' self.repl._cursor_offset = 5 self.repl.matches_iter.matches = [] self.repl.matches_iter.is_cseq.return_value = False self.repl.matches_iter.cur_line.return_value = (None, None) self.repl.on_tab() self.repl.complete.assert_called_once_with(tab=True) def test_tab_after_indentation_adds_space(self): self.repl._current_line = ' ' self.repl._cursor_offset = 4 self.repl.on_tab() self.assertEqual(self.repl._current_line, ' ') self.assertEqual(self.repl._cursor_offset, 8) def test_tab_at_beginning_of_line_adds_space(self): self.repl._current_line = '' self.repl._cursor_offset = 0 self.repl.on_tab() self.assertEqual(self.repl._current_line, ' ') self.assertEqual(self.repl._cursor_offset, 4) def test_tab_with_no_matches_selects_first(self): self.repl._current_line = ' aa' self.repl._cursor_offset = 3 self.repl.matches_iter.matches = [] self.repl.matches_iter.is_cseq.return_value = False mock_next(self.repl.matches_iter, None) self.repl.matches_iter.cur_line.return_value = (None, None) self.repl.on_tab() self.repl.complete.assert_called_once_with(tab=True) self.repl.matches_iter.cur_line.assert_called_once_with() def test_tab_with_matches_selects_next_match(self): self.repl._current_line = ' aa' self.repl._cursor_offset = 3 self.repl.complete() self.repl.matches_iter.is_cseq.return_value = False mock_next(self.repl.matches_iter, None) self.repl.matches_iter.cur_line.return_value = (None, None) self.repl.on_tab() self.repl.matches_iter.cur_line.assert_called_once_with() def test_tab_completes_common_sequence(self): self.repl._current_line = ' a' self.repl._cursor_offset = 2 self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] self.repl.matches_iter.is_cseq.return_value = True self.repl.matches_iter.substitute_cseq.return_value = (None, None) self.repl.on_tab() self.repl.matches_iter.substitute_cseq.assert_called_once_with() class TestCurtsiesReplFilenameCompletion(TestCase): def setUp(self): self.repl = create_repl() def test_list_win_visible_match_selected_on_tab_multiple_options(self): self.repl._current_line = " './'" self.repl._cursor_offset = 2 with mock.patch('bpython.autocomplete.get_completer') as m: m.return_value = (['./abc', './abcd', './bcd'], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() self.assertEqual(self.repl.current_match, './abc') self.assertEqual(self.repl.list_win_visible, True) def test_list_win_not_visible_and_cseq_if_cseq(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 with mock.patch('bpython.autocomplete.get_completer') as m: m.return_value = (['./abcd', './abce'], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() self.assertEqual(self.repl._current_line, " './abc'") self.assertEqual(self.repl.current_match, None) self.assertEqual(self.repl.list_win_visible, False) def test_list_win_not_visible_and_match_selected_if_one_option(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 with mock.patch('bpython.autocomplete.get_completer') as m: m.return_value = (['./abcd'], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() self.assertEqual(self.repl._current_line, " './abcd'") self.assertEqual(self.repl.current_match, None) self.assertEqual(self.repl.list_win_visible, False) # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy @contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: sys.stdout, sys.stderr = old_out, old_err def create_repl(**kwargs): config = setup_config({'editor': 'true'}) repl = curtsiesrepl.BaseRepl(config=config, **kwargs) os.environ['PAGER'] = 'true' os.environ.pop('PYTHONSTARTUP', None) repl.width = 50 repl.height = 20 return repl class TestFutureImports(TestCase): def test_repl(self): repl = create_repl() with captured_output() as (out, err): repl.push('from __future__ import division') repl.push('1 / 2') self.assertEqual(out.getvalue(), '0.5\n') def test_interactive(self): interp = code.InteractiveInterpreter(locals={}) with captured_output() as (out, err): with tempfile.NamedTemporaryFile(mode='w', suffix='.py') as f: f.write('from __future__ import division\n') f.write('print(1/2)\n') f.flush() args.exec_code(interp, [f.name]) repl = create_repl(interp=interp) repl.push('1 / 2') self.assertEqual(out.getvalue(), '0.5\n0.5\n') class TestPredictedIndent(TestCase): def setUp(self): self.repl = create_repl() def test_simple(self): self.assertEqual(self.repl.predicted_indent(''), 0) self.assertEqual(self.repl.predicted_indent('class Foo:'), 4) self.assertEqual(self.repl.predicted_indent('class Foo: pass'), 0) self.assertEqual(self.repl.predicted_indent('def asdf():'), 4) self.assertEqual(self.repl.predicted_indent('def asdf(): return 7'), 0) @unittest.skip def test_complex(self): self.assertEqual(self.repl.predicted_indent('[a, '), 1) self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf, '), 7) class TestCurtsiesReevaluate(TestCase): def setUp(self): self.repl = create_repl() def test_variable_is_cleared(self): self.repl._current_line = 'b = 10' self.repl.on_enter() self.assertIn('b', self.repl.interp.locals) self.repl.undo() 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 self.sys_path = sys.path sys.path = self.sys_path[:] # Because these tests create Python source files at runtime, # it's possible in Python >=3.3 for the importlib.machinery.FileFinder # for a directory to have an outdated cache when # * a module in that directory is imported, # * then a new module is created in that directory, # * then that new module is imported. # Automatic cache invalidation is based on the second-resolution mtime # of the directory, so we need to manually call invalidate_caches(). # # see https://docs.python.org/3/library/importlib.html # sections #importlib.machinery.FileFinder and # #importlib.invalidate_caches invalidate_caches() def tearDown(self): sys.dont_write_bytecode = self.dont_write_bytecode sys.path = self.sys_path 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): print(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): print(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): self.repl = create_repl() self.repl.pager = self.assert_pager_gets_unicode def assert_pager_gets_unicode(self, text): self.assertIsInstance(text, type('')) def test_help(self): self.repl.pager(self.repl.help_text()) @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), 'Charset can not encode characters') def test_show_source_not_formatted(self): self.repl.config.highlight_show_source = False self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' self.repl.show_source() @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), 'Charset can not encode characters') def test_show_source_formatted(self): self.repl.config.highlight_show_source = True self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' self.repl.show_source() class TestCurtsiesStartup(TestCase): def setUp(self): self.repl = create_repl() def write_startup_file(self, fname, encoding): with io.open(fname, mode='wt', encoding=encoding) as f: f.write('# coding: ') f.write(encoding) f.write('\n') f.write('from __future__ import unicode_literals\n') f.write('a = "äöü"\n') def test_startup_event_utf8(self): with tempfile.NamedTemporaryFile() as temp: self.write_startup_file(temp.name, 'utf-8') with mock.patch.dict('os.environ', {'PYTHONSTARTUP': temp.name}): self.repl.process_event(bpythonevents.RunStartupFileEvent()) self.assertIn('a', self.repl.interp.locals) def test_startup_event_latin1(self): with tempfile.NamedTemporaryFile() as temp: self.write_startup_file(temp.name, 'latin-1') with mock.patch.dict('os.environ', {'PYTHONSTARTUP': temp.name}): self.repl.process_event(bpythonevents.RunStartupFileEvent()) self.assertIn('a', self.repl.interp.locals) class TestCurtsiesPasteEvents(TestCase): def setUp(self): self.repl = create_repl() def test_control_events_in_small_paste(self): self.assertGreaterEqual(curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE, 6, 'test assumes UI lag could cause 6 events') p = events.PasteEvent() p.events = ['a', 'b', 'c', 'd', '', 'e'] self.repl.process_event(p) self.assertEqual(self.repl.current_line, 'eabcd') def test_control_events_in_large_paste(self): """Large paste events should ignore control characters""" p = events.PasteEvent() p.events = (['a', ''] + ['e'] * curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE) self.repl.process_event(p) self.assertEqual(self.repl.current_line, 'a' + 'e'*curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE) if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_repl.py0000644000175100017510000005207713240407731020274 0ustar useruser00000000000000# encoding: utf-8 from itertools import islice from six.moves import range import collections import inspect import os import shutil import socket import sys import tempfile 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, TEST_CONFIG pypy = 'PyPy' in sys.version def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, TEST_CONFIG) if 'autocomplete_mode' in conf: config_struct.autocomplete_mode = conf['autocomplete_mode'] return config_struct class FakeHistory(repl.History): def __init__(self): pass def reset(self): pass class FakeRepl(repl.Repl): def __init__(self, conf={}): repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf)) self.current_line = "" self.cursor_offset = 0 class FakeCliRepl(cli.CLIRepl, FakeRepl): def __init__(self): self.s = '' self.cpos = 0 self.rl_history = FakeHistory() class TestMatchesIterator(unittest.TestCase): def setUp(self): self.matches = ['bobby', 'bobbies', 'bobberina'] self.matches_iterator = repl.MatchesIterator() self.matches_iterator.current_word = 'bob' self.matches_iterator.orig_line = 'bob' self.matches_iterator.orig_cursor_offset = len('bob') self.matches_iterator.matches = self.matches def test_next(self): self.assertEqual(next(self.matches_iterator), self.matches[0]) for x in range(len(self.matches) - 1): next(self.matches_iterator) self.assertEqual(next(self.matches_iterator), self.matches[0]) self.assertEqual(next(self.matches_iterator), self.matches[1]) self.assertNotEqual(next(self.matches_iterator), self.matches[1]) def test_previous(self): self.assertEqual(self.matches_iterator.previous(), self.matches[2]) for x in range(len(self.matches) - 1): self.matches_iterator.previous() self.assertNotEqual(self.matches_iterator.previous(), self.matches[0]) self.assertEqual(self.matches_iterator.previous(), self.matches[1]) self.assertEqual(self.matches_iterator.previous(), self.matches[0]) def test_nonzero(self): """self.matches_iterator should be False at start, then True once we active a match. """ self.assertFalse(self.matches_iterator) next(self.matches_iterator) self.assertTrue(self.matches_iterator) def test_iter(self): slice = islice(self.matches_iterator, 0, 9) self.assertEqual(list(slice), self.matches * 3) def test_current(self): with self.assertRaises(ValueError): self.matches_iterator.current() next(self.matches_iterator) self.assertEqual(self.matches_iterator.current(), self.matches[0]) def test_update(self): slice = islice(self.matches_iterator, 0, 3) self.assertEqual(list(slice), self.matches) newmatches = ['string', 'str', 'set'] completer = mock.Mock() completer.locate.return_value = (0, 1, 's') self.matches_iterator.update(1, 's', newmatches, completer) newslice = islice(newmatches, 0, 3) self.assertNotEqual(list(slice), self.matches) self.assertEqual(list(newslice), newmatches) def test_cur_line(self): completer = mock.Mock() completer.locate.return_value = ( 0, self.matches_iterator.orig_cursor_offset, self.matches_iterator.orig_line) self.matches_iterator.completer = completer with self.assertRaises(ValueError): self.matches_iterator.cur_line() self.assertEqual(next(self.matches_iterator), self.matches[0]) self.assertEqual(self.matches_iterator.cur_line(), (len(self.matches[0]), self.matches[0])) def test_is_cseq(self): self.assertTrue(self.matches_iterator.is_cseq()) class TestArgspec(unittest.TestCase): def setUp(self): self.repl = FakeRepl() self.repl.push("def spam(a, b, c):\n", False) self.repl.push(" pass\n", False) self.repl.push("\n", False) self.repl.push("class Spam(object):\n", False) 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) def set_input_line(self, line): """Set current input line of the test REPL.""" self.repl.current_line = line self.repl.cursor_offset = len(line) def test_func_name(self): for (line, expected_name) in [("spam(", "spam"), ("spam(map([]", "map"), ("spam((), ", "spam")]: self.set_input_line(line) self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) def test_func_name_method_issue_479(self): for (line, expected_name) in [("o.spam(", "spam"), ("o.spam(map([]", "map"), ("o.spam((), ", "spam")]: self.set_input_line(line) 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) # Should not explode self.repl.get_args() def test_kw_arg_position(self): self.set_input_line("spam(a=0") self.assertTrue(self.repl.get_args()) 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.arg_pos, "b") self.set_input_line("spam(1, c=2") self.assertTrue(self.repl.get_args()) 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.funcprops) # Argument position self.assertEqual(self.repl.arg_pos, 1) def test_issue127(self): self.set_input_line("x=range(") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, "range") self.set_input_line("{x:range(") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, "range") self.set_input_line("foo(1, 2, x,range(") self.assertEqual(self.repl.current_func.__name__, "range") self.set_input_line("(x,range(") self.assertEqual(self.repl.current_func.__name__, "range") 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()) @unittest.skipIf(pypy, 'pypy pydoc doesn\'t have this') def test_issue583(self): self.repl = FakeRepl() self.repl.push("a = 1.2\n", False) self.set_input_line("a.is_integer(") self.repl.set_docstring() self.assertIsNot(self.repl.docstring, None) def test_methods_of_expressions(self): self.set_input_line("'a'.capitalize(") self.assertTrue(self.repl.get_args()) self.set_input_line("(1 + 1.1).as_integer_ratio(") self.assertTrue(self.repl.get_args()) class TestArgspecInternal(unittest.TestCase): def test_function_expressions(self): te = self.assertTupleEqual fa = lambda line: repl.Repl._funcname_and_argnum(line) for line, (func, argnum) in [ ('spam(', ('spam', 0)), ('spam((), ', ('spam', 1)), ('spam.eggs((), ', ('spam.eggs', 1)), ('spam[abc].eggs((), ', ('spam[abc].eggs', 1)), ('spam[0].eggs((), ', ('spam[0].eggs', 1)), ('spam[a + b]eggs((), ', ('spam[a + b]eggs', 1)), ('spam().eggs((), ', ('spam().eggs', 1)), ('spam(1, 2).eggs((), ', ('spam(1, 2).eggs', 1)), ('spam(1, f(1)).eggs((), ', ('spam(1, f(1)).eggs', 1)), ('[0].eggs((), ', ('[0].eggs', 1)), ('[0][0]((), {}).eggs((), ', ('[0][0]((), {}).eggs', 1)), ('a + spam[0].eggs((), ', ('spam[0].eggs', 1)), ("spam(", ("spam", 0)), ("spam(map([]", ("map", 0)), ("spam((), ", ("spam", 1)) ]: te(fa(line), (func, argnum)) class TestGetSource(unittest.TestCase): def setUp(self): self.repl = FakeRepl() def set_input_line(self, line): """Set current input line of the test REPL.""" self.repl.current_line = line self.repl.cursor_offset = len(line) def assert_get_source_error_for_current_function(self, func, msg): self.repl.current_func = func with self.assertRaises(repl.SourceNotFound): self.repl.get_source_of_current_name() try: self.repl.get_source_of_current_name() except repl.SourceNotFound as e: self.assertEqual(e.args[0], msg) else: self.fail("Should have raised SourceNotFound") def test_current_function(self): self.set_input_line('INPUTLINE') self.repl.current_func = inspect.getsource self.assertIn("text of the source code", self.repl.get_source_of_current_name()) self.assert_get_source_error_for_current_function( [], "No source code found for INPUTLINE") self.assert_get_source_error_for_current_function( list.pop, "No source code found for INPUTLINE") @unittest.skipIf(pypy, 'different errors for PyPy') def test_current_function_cpython(self): self.set_input_line('INPUTLINE') self.assert_get_source_error_for_current_function( collections.defaultdict.copy, "No source code found for INPUTLINE") 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') self.assertIn('dup(self)', self.repl.get_source_of_current_name()) # TODO add tests for various failures without using current function class TestEditConfig(TestCase): def setUp(self): self.repl = FakeRepl() self.repl.interact.confirm = lambda msg: True self.repl.interact.notify = lambda msg: None self.repl.config.editor = 'true' def test_create_config(self): tmp_dir = tempfile.mkdtemp() try: config_path = os.path.join(tmp_dir, 'newdir', 'config') self.repl.config.config_path = config_path self.repl.edit_config() self.assertTrue(os.path.exists(config_path)) finally: shutil.rmtree(tmp_dir) self.assertFalse(os.path.exists(config_path)) class TestRepl(unittest.TestCase): def set_input_line(self, line): """Set current input line of the test REPL.""" self.repl.current_line = line self.repl.cursor_offset = len(line) def setUp(self): self.repl = FakeRepl() def test_current_string(self): self.set_input_line('a = "2"') # TODO factor cpos out of repl.Repl self.repl.cpos = 0 self.assertEqual(self.repl.current_string(), '"2"') self.set_input_line('a = "2" + 2') self.assertEqual(self.repl.current_string(), '') def test_push(self): self.repl = FakeRepl() self.repl.push("foobar = 2") self.assertEqual(self.repl.interp.locals['foobar'], 2) # COMPLETE TESTS # 1. Global tests def test_simple_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("d") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['def', 'del', 'delattr(', 'dict(', 'dir(', 'divmod(']) def test_substring_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) self.set_input_line("time") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['RuntimeError(', 'RuntimeWarning(']) def test_fuzzy_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) self.set_input_line("doc") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['UnboundLocalError(', '__doc__'] if not py3 else ['ChildProcessError(', 'UnboundLocalError(', '__doc__']) # 2. Attribute tests def test_simple_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("Foo.b") code = "class Foo():\n\tdef bar(self):\n\t\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, ['Foo.bar']) def test_substring_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) self.set_input_line("Foo.az") code = "class Foo():\n\tdef baz(self):\n\t\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, ['Foo.baz']) def test_fuzzy_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) self.set_input_line("Foo.br") code = "class Foo():\n\tdef bar(self):\n\t\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, ['Foo.bar']) # 3. Edge cases def test_updating_namespace_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("foo") self.repl.push("foobar = 2") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['foobar']) def test_file_should_not_appear_in_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("_") self.assertTrue(self.repl.complete()) 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): def setUp(self): self.repl = FakeCliRepl() def test_atbol(self): self.assertTrue(self.repl.atbol()) self.repl.s = "\t\t" self.assertTrue(self.repl.atbol()) self.repl.s = "\t\tnot an empty line" self.assertFalse(self.repl.atbol()) def test_addstr(self): self.repl.complete = mock.Mock(True) self.repl.s = "foo" self.repl.addstr("bar") self.assertEqual(self.repl.s, "foobar") self.repl.cpos = 3 self.repl.addstr('buzz') self.assertEqual(self.repl.s, "foobuzzbar") class TestCliReplTab(unittest.TestCase): def setUp(self): self.repl = FakeCliRepl() # 3 Types of tab complete def test_simple_tab_complete(self): self.repl.matches_iter = MagicIterMock() if py3: self.repl.matches_iter.__bool__.return_value = False else: self.repl.matches_iter.__nonzero__.return_value = False self.repl.complete = mock.Mock() self.repl.print_line = mock.Mock() 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" self.repl.tab() self.assertTrue(self.repl.complete.called) self.repl.complete.assert_called_with(tab=True) self.assertEqual(self.repl.s, "foobar") @unittest.skip("disabled while non-simple completion is disabled") def test_substring_tab_complete(self): self.repl.s = "bar" self.repl.config.autocomplete_mode = autocomplete.FUZZY self.repl.tab() self.assertEqual(self.repl.s, "foobar") self.repl.tab() self.assertEqual(self.repl.s, "foofoobar") @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_tab_complete(self): self.repl.s = "br" self.repl.config.autocomplete_mode = autocomplete.FUZZY self.repl.tab() self.assertEqual(self.repl.s, "foobar") # Edge Cases def test_normal_tab(self): """make sure pressing the tab key will still in some cases add a tab""" self.repl.s = "" self.repl.config = mock.Mock() self.repl.config.tab_length = 4 self.repl.complete = mock.Mock() self.repl.print_line = mock.Mock() self.repl.tab() self.assertEqual(self.repl.s, " ") def test_back_parameter(self): self.repl.matches_iter = mock.Mock() self.repl.matches_iter.matches = True 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.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" self.repl.cpos = 0 self.repl.tab(back=True) self.assertTrue(self.repl.matches_iter.previous.called) self.assertTrue(self.repl.s, "previtem") # Attribute Tests @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_tab_complete(self): """Test fuzzy attribute with no text""" self.repl.s = "Foo." self.repl.config.autocomplete_mode = autocomplete.FUZZY self.repl.tab() self.assertEqual(self.repl.s, "Foo.foobar") @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_tab_complete2(self): """Test fuzzy attribute with some text""" self.repl.s = "Foo.br" self.repl.config.autocomplete_mode = autocomplete.FUZZY self.repl.tab() self.assertEqual(self.repl.s, "Foo.foobar") # Expand Tests def test_simple_expand(self): self.repl.s = "f" self.cpos = 0 self.repl.matches_iter = mock.Mock() self.repl.matches_iter.is_cseq.return_value = True self.repl.matches_iter.substitute_cseq.return_value = (3, "foo") self.repl.print_line = mock.Mock() self.repl.tab() self.assertEqual(self.repl.s, "foo") @unittest.skip("disabled while non-simple completion is disabled") def test_substring_expand_forward(self): self.repl.config.autocomplete_mode = autocomplete.SUBSTRING self.repl.s = "ba" self.repl.tab() self.assertEqual(self.repl.s, "bar") @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_expand(self): pass if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_config.py0000644000175100017510000000524013240410473020562 0ustar useruser00000000000000# encoding: utf-8 import os import tempfile import textwrap from bpython.test import unittest from bpython import config TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") class TestConfig(unittest.TestCase): def load_temp_config(self, content, struct=None): """Write config to a temporary file and load it.""" if struct is None: struct = config.Struct() with tempfile.NamedTemporaryFile() as f: f.write(content.encode('utf8')) f.flush() config.loadini(struct, f.name) return struct def test_load_theme(self): struct = config.Struct() struct.color_scheme = dict() config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, dict()) expected = {"keyword": "y"} self.assertEquals(struct.color_scheme, expected) defaults = {"name": "c"} expected.update(defaults) config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults) self.assertEquals(struct.color_scheme, expected) def test_keybindings_default_contains_no_duplicates(self): struct = self.load_temp_config("") keys = (attr for attr in dir(struct) if attr.endswith('_key')) mapped_keys = [getattr(struct, key) for key in keys if getattr(struct, key)] mapped_keys_set = set(mapped_keys) self.assertEqual(len(mapped_keys), len(mapped_keys_set)) def test_keybindings_use_default(self): struct = self.load_temp_config(textwrap.dedent(""" [keyboard] help = F1 """)) self.assertEqual(struct.help_key, 'F1') def test_keybindings_use_other_default(self): struct = self.load_temp_config(textwrap.dedent(""" [keyboard] help = C-h """)) self.assertEqual(struct.help_key, 'C-h') self.assertEqual(struct.backspace_key, '') def test_keybindings_use_other_default_issue_447(self): struct = self.load_temp_config(textwrap.dedent(""" [keyboard] help = F2 show_source = F9 """)) self.assertEqual(struct.help_key, 'F2') self.assertEqual(struct.show_source_key, 'F9') def test_keybindings_unset(self): struct = self.load_temp_config(textwrap.dedent(""" [keyboard] help = """)) self.assertFalse(struct.help_key) def test_keybindings_unused(self): struct = self.load_temp_config(textwrap.dedent(""" [keyboard] help = F4 """)) self.assertEqual(struct.help_key, 'F4') bpython-0.17.1/bpython/test/test_preprocess.py0000644000175100017510000000650213240407731021507 0ustar useruser00000000000000# encoding: utf-8 from code import compile_command as compiler from functools import partial import difflib import inspect import re 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 skip = unittest.skip preproc = partial(preprocess, compiler=compiler) def get_fodder_source(test_name): pattern = r'#StartTest-%s\n(.*?)#EndTest' % (test_name,) orig, xformed = [re.search(pattern, inspect.getsource(module), re.DOTALL) for module in [original, processed]] if not orig: raise ValueError("Can't locate test %s in original fodder file" % (test_name,)) if not xformed: raise ValueError("Can't locate test %s in processed fodder file" % (test_name,)) return orig.group(1), xformed.group(1) class TestPreprocessing(unittest.TestCase): def assertCompiles(self, source): finished, parsable = code_finished_will_parse(source, compiler) return finished and parsable def test_indent_empty_lines_nops(self): self.assertEqual(preproc('hello'), 'hello') self.assertEqual(preproc('hello\ngoodbye'), 'hello\ngoodbye') self.assertEqual(preproc('a\n b\nc\n'), 'a\n b\nc\n') def assertShowWhitespaceEqual(self, a, b): self.assertEqual( a, b, ''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True), b.replace(' ', '~').splitlines(True), fromfile='actual', tofile='expected', n=5))) def assertDefinitionIndented(self, obj): name = obj.__name__ obj2 = getattr(processed, name) orig = inspect.getsource(obj) xformed = inspect.getsource(obj2) self.assertShowWhitespaceEqual(preproc(orig), xformed) self.assertCompiles(xformed) def assertLinesIndented(self, test_name): orig, xformed = get_fodder_source(test_name) self.assertShowWhitespaceEqual(preproc(orig), xformed) self.assertCompiles(xformed) def assertIndented(self, obj_or_name): if isinstance(obj_or_name, str): self.assertLinesIndented(obj_or_name) else: self.assertDefinitionIndented(obj_or_name) def test_empty_line_between_methods(self): self.assertIndented(original.BlankLineBetweenMethods) def test_empty_line_within_class(self): self.assertIndented(original.BlankLineInFunction) def test_blank_lines_in_for_loop(self): self.assertIndented('blank_lines_in_for_loop') @skip("More advanced technique required: need to try compiling and " "backtracking") def test_blank_line_in_try_catch(self): self.assertIndented('blank_line_in_try_catch') @skip("More advanced technique required: need to try compiling and " "backtracking") def test_blank_line_in_try_catch_else(self): self.assertIndented('blank_line_in_try_catch_else') def test_blank_trailing_line(self): self.assertIndented('blank_trailing_line') def test_tabs(self): self.assertIndented(original.tabs) bpython-0.17.1/bpython/test/test_curtsies_painting.py0000644000175100017510000007022713240407731023061 0ustar useruser00000000000000# coding: utf8 from __future__ import unicode_literals import itertools import os import pydoc import string import sys from contextlib import contextmanager from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython.test import mock from bpython import config, inspection from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend import replpainter from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, \ CONTIGUITY_BROKEN_MSG from bpython.test import FixLanguageTestCase as TestCase, TEST_CONFIG def setup_config(): config_struct = config.Struct() config.loadini(config_struct, TEST_CONFIG) config_struct.cli_suggestion_width = 1 return config_struct class ClearEnviron(TestCase): @classmethod def setUpClass(cls): cls.mock_environ = mock.patch.dict('os.environ', {}, clear=True) cls.mock_environ.start() TestCase.setUpClass() @classmethod def tearDownClass(cls): cls.mock_environ.stop() TestCase.tearDownClass() class CurtsiesPaintingTest(FormatStringTest, ClearEnviron): def setUp(self): class TestRepl(BaseRepl): def _request_refresh(inner_self): pass self.repl = TestRepl(config=setup_config()) self.repl.height, self.repl.width = (5, 10) @property def locals(self): return self.repl.coderunner.interp.locals def assert_paint(self, screen, cursor_row_col): array, cursor_pos = self.repl.paint() self.assertFSArraysEqual(array, screen) self.assertEqual(cursor_pos, cursor_row_col) def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None, **paint_kwargs): array, cursor_pos = self.repl.paint(**paint_kwargs) self.assertFSArraysEqualIgnoringFormatting(array, screen) if cursor_row_col is not None: self.assertEqual(cursor_pos, cursor_row_col) class TestCurtsiesPaintingTest(CurtsiesPaintingTest): def test_history_is_cleared(self): self.assertEqual(self.repl.rl_history.entries, ['']) class TestCurtsiesPaintingSimple(CurtsiesPaintingTest): def test_startup(self): screen = fsarray([cyan('>>> '), cyan('Welcome to')]) self.assert_paint(screen, (0, 4)) def test_enter_text(self): [self.repl.add_normal_character(c) for c in '1 + 1'] screen = fsarray([cyan('>>> ') + bold(green('1') + cyan(' ') + yellow('+') + cyan(' ') + green('1')), cyan('Welcome to')]) self.assert_paint(screen, (0, 9)) def test_run_line(self): try: orig_stdout = sys.stdout sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in '1 + 1'] self.repl.on_enter(insert_into_history=False) screen = fsarray(['>>> 1 + 1', '2', 'Welcome to']) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: sys.stdout = orig_stdout def test_completion(self): self.repl.height, self.repl.width = (5, 32) self.repl.current_line = 'an' self.cursor_offset = 2 if config.supports_box_chars(): screen = ['>>> an', '┌──────────────────────────────┐', '│ and any( │', '└──────────────────────────────┘', 'Welcome to bpython! Press f'] else: screen = ['>>> an', '+------------------------------+', '| and any( |', '+------------------------------+', 'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_argspec(self): def foo(x, y, z=10): "docstring!" pass 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('=') + bold(cyan('10')) + yellow(')')] self.assertFSArraysEqual(fsarray(array), fsarray(screen)) def test_formatted_docstring(self): actual = replpainter.formatted_docstring( 'Returns the results\n\n' 'Also has side effects', 40, config=setup_config()) expected = fsarray(['Returns the results', '', 'Also has side effects']) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_unicode_docstrings(self): "A bit of a special case in Python 2" # issue 653 def foo(): u"åß∂ƒ" actual = replpainter.formatted_docstring( foo.__doc__, 40, config=setup_config()) expected = fsarray([u'åß∂ƒ']) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_nonsense_docstrings(self): for docstring in [123, {}, [], ]: try: replpainter.formatted_docstring( docstring, 40, config=setup_config()) except Exception: self.fail('bad docstring caused crash: {!r}'.format(docstring)) def test_weird_boto_docstrings(self): # Boto does something like this. # botocore: botocore/docs/docstring.py class WeirdDocstring(str): # a mighty hack. See botocore/docs/docstring.py def expandtabs(self, tabsize=8): return u'asdfåß∂ƒ'.expandtabs(tabsize) def foo(): pass foo.__doc__ = WeirdDocstring() wd = pydoc.getdoc(foo) actual = replpainter.formatted_docstring(wd, 40, config=setup_config()) expected = fsarray([u'asdfåß∂ƒ']) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_paint_lasts_events(self): actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], config=setup_config()) if config.supports_box_chars(): expected = fsarray(["┌─┐", "│c│", "│b│", "└─┘"]) else: expected = fsarray(["+-+", "|c|", "|b|", "+-+"]) self.assertFSArraysEqualIgnoringFormatting(actual, expected) @contextmanager def output_to_repl(repl): old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = repl.stdout, repl.stderr yield finally: sys.stdout, sys.stderr = old_out, old_err class HigherLevelCurtsiesPaintingTest(CurtsiesPaintingTest): def refresh(self): self.refresh_requests.append(RefreshRequestEvent()) def send_refreshes(self): while self.refresh_requests: self.repl.process_event(self.refresh_requests.pop()) _, _ = self.repl.paint() def enter(self, line=None): """Enter a line of text, avoiding autocompletion windows autocomplete could still happen if the entered line has autocompletion that would happen then, but intermediate stages won't happen""" if line is not None: self.repl._set_cursor_offset(len(line), update_completion=False) self.repl.current_line = line with output_to_repl(self.repl): self.repl.on_enter(insert_into_history=False) self.assertEqual(self.repl.rl_history.entries, ['']) self.send_refreshes() def undo(self): with output_to_repl(self.repl): self.repl.undo() self.send_refreshes() def setUp(self): self.refresh_requests = [] class TestRepl(BaseRepl): def _request_refresh(inner_self): self.refresh() self.repl = TestRepl(banner='', config=setup_config()) self.repl.height, self.repl.width = (5, 32) def send_key(self, key): self.repl.process_event('' if key == ' ' else key) self.repl.paint() # has some side effects we need to be wary of class TestCurtsiesRewindRedraw(HigherLevelCurtsiesPaintingTest): def test_rewind(self): self.repl.current_line = '1 + 1' self.enter() screen = ['>>> 1 + 1', '2', '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) self.repl.undo() screen = ['>>> '] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_rewind_contiguity_loss(self): self.enter('1 + 1') self.enter('2 + 2') self.enter('def foo(x):') self.repl.current_line = ' return x + 1' screen = ['>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> def foo(x):', '... return x + 1'] self.assert_paint_ignoring_formatting(screen, (5, 8)) self.repl.scroll_offset = 1 self.assert_paint_ignoring_formatting(screen[1:], (4, 8)) self.undo() screen = ['2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (3, 4)) self.undo() screen = ['2', '>>> '] self.assert_paint_ignoring_formatting(screen, (1, 4)) self.undo() screen = [CONTIGUITY_BROKEN_MSG[:self.repl.width], '>>> ', '', '', '', ' '] # TODO why is that there? Necessary? self.assert_paint_ignoring_formatting(screen, (1, 4)) screen = ['>>> '] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_inconsistent_history_doesnt_happen_if_onscreen(self): self.enter('1 + 1') screen = ['>>> 1 + 1', '2', '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) self.enter("2 + 2") screen = ['>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.display_lines[0] = self.repl.display_lines[0] * 2 self.undo() screen = ['>>> 1 + 1', '2', '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_rewind_inconsistent_history(self): self.enter('1 + 1') self.enter('2 + 2') self.enter('3 + 3') screen = ['>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> 3 + 3', '6', '>>> '] self.assert_paint_ignoring_formatting(screen, (6, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) self.repl.display_lines[0] = self.repl.display_lines[0] * 2 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], '>>> 2 + 2', '4', '>>> ', '', ' '] self.assert_paint_ignoring_formatting(screen, (3, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4)) self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4)) def test_rewind_inconsistent_history_more_lines_same_screen(self): self.repl.width = 60 sys.a = 5 self.enter('import sys') self.enter('for i in range(sys.a):') self.enter(' print(sys.a)') self.enter('') self.enter('1 + 1') self.enter('2 + 2') screen = ['>>> import sys', '>>> for i in range(sys.a):', '... print(sys.a)', '... ', '5', '5', '5', '5', '5', '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 6 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], '6', # everything will jump down a line - that's perfectly # reasonable '>>> 1 + 1', '2', '>>> ', ' '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4)) def test_rewind_inconsistent_history_more_lines_lower_screen(self): self.repl.width = 60 sys.a = 5 self.enter("import sys") self.enter("for i in range(sys.a):") self.enter(" print(sys.a)") self.enter("") self.enter("1 + 1") self.enter("2 + 2") screen = [">>> import sys", ">>> for i in range(sys.a):", "... print(sys.a)", '... ', '5', '5', '5', '5', '5', '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 8 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], '8', '8', '8', '>>> 1 + 1', '2', '>>> '] self.assert_paint_ignoring_formatting(screen) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[-5:]) def test_rewind_inconsistent_history_more_lines_raise_screen(self): self.repl.width = 60 sys.a = 5 self.enter("import sys") self.enter("for i in range(sys.a):") self.enter(" print(sys.a)") self.enter("") self.enter("1 + 1") self.enter("2 + 2") screen = [">>> import sys", ">>> for i in range(sys.a):", "... print(sys.a)", '... ', '5', '5', '5', '5', '5', '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 1 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], '1', '>>> 1 + 1', '2', '>>> ', ' '] self.assert_paint_ignoring_formatting(screen) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-1]) def test_rewind_history_not_quite_inconsistent(self): self.repl.width = 50 sys.a = 5 self.enter("for i in range(__import__('sys').a):") self.enter(" print(i)") self.enter("") self.enter("1 + 1") self.enter("2 + 2") screen = [">>> for i in range(__import__('sys').a):", "... print(i)", "... ", '0', '1', '2', '3', '4', '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (12, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) sys.a = 6 self.undo() screen = ['5', # everything will jump down a line - that's perfectly # reasonable '>>> 1 + 1', '2', '>>> '] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_rewind_barely_consistent(self): self.enter("1 + 1") self.enter("2 + 2") self.enter("3 + 3") screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> 3 + 3', '6', '>>> '] self.assert_paint_ignoring_formatting(screen, (6, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) self.repl.display_lines[2] = self.repl.display_lines[2] * 2 self.undo() screen = ['>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_clear_screen(self): self.enter("1 + 1") self.enter("2 + 2") screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.request_paint_to_clear_screen = True screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ', '', '', '', ''] self.assert_paint_ignoring_formatting(screen, (4, 4)) def test_scroll_down_while_banner_visible(self): self.repl.status_bar.message('STATUS_BAR') self.enter("1 + 1") self.enter("2 + 2") screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ', 'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) def test_clear_screen_while_banner_visible(self): self.repl.status_bar.message('STATUS_BAR') self.enter("1 + 1") self.enter("2 + 2") screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ', 'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) self.repl.request_paint_to_clear_screen = True screen = ['2', '>>> 2 + 2', '4', '>>> ', '', '', '', 'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_cursor_stays_at_bottom_of_screen(self): """infobox showing up during intermediate render was causing this to fail, #371""" self.repl.width = 50 self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): self.repl.on_enter(insert_into_history=False) screen = [">>> __import__('random').__name__", "'random'"] self.assert_paint_ignoring_formatting(screen) with output_to_repl(self.repl): self.repl.process_event(self.refresh_requests.pop()) screen = [">>> __import__('random').__name__", "'random'", ""] self.assert_paint_ignoring_formatting(screen) with output_to_repl(self.repl): self.repl.process_event(self.refresh_requests.pop()) screen = [">>> __import__('random').__name__", "'random'", ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_unhighlight_paren_bugs(self): """two previous bugs, parent didn't highlight until next render and paren didn't unhighlight until enter""" self.assertEqual(self.repl.rl_history.entries, ['']) self.enter('(') self.assertEqual(self.repl.rl_history.entries, ['']) screen = [">>> (", "... "] self.assertEqual(self.repl.rl_history.entries, ['']) self.assert_paint_ignoring_formatting(screen) self.assertEqual(self.repl.rl_history.entries, ['']) with output_to_repl(self.repl): self.assertEqual(self.repl.rl_history.entries, ['']) self.repl.process_event(')') self.assertEqual(self.repl.rl_history.entries, ['']) screen = fsarray([cyan(">>> ") + on_magenta(bold(red('('))), green("... ") + on_magenta(bold(red(')')))]) self.assert_paint(screen, (1, 5)) with output_to_repl(self.repl): self.repl.process_event(' ') screen = fsarray([cyan(">>> ") + yellow('('), green("... ") + yellow(')') + bold(cyan(" "))]) self.assert_paint(screen, (1, 6)) def test_472(self): [self.send_key(c) for c in "(1, 2, 3)"] with output_to_repl(self.repl): self.send_key('\n') self.send_refreshes() self.send_key('') self.repl.paint() [self.send_key('') for _ in range(4)] self.send_key('') self.send_key('4') self.repl.on_enter() self.send_refreshes() screen = [">>> (1, 2, 3)", '(1, 2, 3)', '>>> (1, 4, 3)', '(1, 4, 3)', '>>> '] self.assert_paint_ignoring_formatting(screen, (4, 4)) def completion_target(num_names, chars_in_first_name=1): class Class(object): pass if chars_in_first_name < 1: raise ValueError('need at least one char in each name') elif chars_in_first_name == 1 and num_names > len(string.ascii_letters): raise ValueError('need more chars to make so many names') names = gen_names() if num_names > 0: setattr(Class, 'a' * chars_in_first_name, 1) next(names) # use the above instead of first name for _, name in zip(range(num_names - 1), names): setattr(Class, name, 0) return Class() def gen_names(): for letters in itertools.chain( itertools.combinations_with_replacement(string.ascii_letters, 1), itertools.combinations_with_replacement(string.ascii_letters, 2)): yield ''.join(letters) class TestCompletionHelpers(TestCase): def test_gen_names(self): self.assertEqual(list(zip([1, 2, 3], gen_names())), [(1, 'a'), (2, 'b'), (3, 'c')]) def test_completion_target(self): target = completion_target(14) self.assertEqual(len([x for x in dir(target) if not x.startswith('_')]), 14) class TestCurtsiesInfoboxPaint(HigherLevelCurtsiesPaintingTest): def test_simple(self): self.repl.width, self.repl.height = (20, 30) self.locals['abc'] = completion_target(3, 50) self.repl.current_line = 'abc' self.repl.cursor_offset = 3 self.repl.process_event('.') screen = ['>>> abc.', '+------------------+', '| aaaaaaaaaaaaaaaa |', '| b |', '| c |', '+------------------+'] self.assert_paint_ignoring_formatting(screen, (0, 8)) def test_fill_screen(self): self.repl.width, self.repl.height = (20, 15) self.locals['abc'] = completion_target(20, 100) self.repl.current_line = 'abc' self.repl.cursor_offset = 3 self.repl.process_event('.') screen = ['>>> abc.', '+------------------+', '| aaaaaaaaaaaaaaaa |', '| b |', '| c |', '| d |', '| e |', '| f |', '| g |', '| h |', '| i |', '| j |', '| k |', '| l |', '+------------------+'] self.assert_paint_ignoring_formatting(screen, (0, 8)) def test_lower_on_screen(self): self.repl.get_top_usable_line = lambda: 10 # halfway down terminal self.repl.width, self.repl.height = (20, 15) self.locals['abc'] = completion_target(20, 100) self.repl.current_line = 'abc' self.repl.cursor_offset = 3 self.repl.process_event('.') screen = ['>>> abc.', '+------------------+', '| aaaaaaaaaaaaaaaa |', '| b |', '| c |', '| d |', '| e |', '| f |', '| g |', '| h |', '| i |', '| j |', '| k |', '| l |', '+------------------+'] # behavior before issue #466 self.assert_paint_ignoring_formatting( screen, try_preserve_history_height=0) self.assert_paint_ignoring_formatting( screen, min_infobox_height=100) # behavior after issue #466 screen = ['>>> abc.', '+------------------+', '| aaaaaaaaaaaaaaaa |', '| b |', '| c |', '+------------------+'] self.assert_paint_ignoring_formatting(screen) def test_at_bottom_of_screen(self): self.repl.get_top_usable_line = lambda: 17 # two lines from bottom self.repl.width, self.repl.height = (20, 15) self.locals['abc'] = completion_target(20, 100) self.repl.current_line = 'abc' self.repl.cursor_offset = 3 self.repl.process_event('.') screen = ['>>> abc.', '+------------------+', '| aaaaaaaaaaaaaaaa |', '| b |', '| c |', '| d |', '| e |', '| f |', '| g |', '| h |', '| i |', '| j |', '| k |', '| l |', '+------------------+'] # behavior before issue #466 self.assert_paint_ignoring_formatting( screen, try_preserve_history_height=0) self.assert_paint_ignoring_formatting( screen, min_infobox_height=100) # behavior after issue #466 screen = ['>>> abc.', '+------------------+', '| aaaaaaaaaaaaaaaa |', '| b |', '| c |', '+------------------+'] self.assert_paint_ignoring_formatting(screen) bpython-0.17.1/bpython/test/test_crashers.py0000644000175100017510000000754213240407731021141 0ustar useruser00000000000000# encoding: utf-8 import fcntl import os import pty import struct import sys import termios import textwrap from bpython.test import unittest, TEST_CONFIG try: from twisted.internet import reactor from twisted.internet.defer import Deferred from twisted.internet.protocol import ProcessProtocol from twisted.trial.unittest import TestCase as TrialTestCase except ImportError: class TrialTestCase(object): pass reactor = None try: import urwid have_urwid = True except ImportError: have_urwid = False try: from nose.plugins.attrib import attr except ImportError: def attr(*args, **kwargs): def identity(func): return func return identity def set_win_size(fd, rows, columns): s = struct.pack('HHHH', rows, columns, 0, 0) fcntl.ioctl(fd, termios.TIOCSWINSZ, s) class CrashersTest(object): backend = "cli" def run_bpython(self, input): """ Run bpython (with `backend` as backend) in a subprocess and enter the given input. Uses a test config that disables the paste detection. Returns bpython's output. """ result = Deferred() class Protocol(ProcessProtocol): STATES = (SEND_INPUT, COLLECT) = range(2) def __init__(self): self.data = "" self.delayed_call = None self.states = iter(self.STATES) self.state = next(self.states) def outReceived(self, data): self.data += data if self.delayed_call is not None: self.delayed_call.cancel() self.delayed_call = reactor.callLater(0.5, self.next) def next(self): self.delayed_call = None if self.state == self.SEND_INPUT: index = self.data.find(">>> ") if index >= 0: self.data = self.data[index + 4:] self.transport.write(input) self.state = next(self.states) else: self.transport.closeStdin() if self.transport.pid is not None: self.delayed_call = None self.transport.signalProcess("TERM") def processExited(self, reason): if self.delayed_call is not None: self.delayed_call.cancel() result.callback(self.data) (master, slave) = pty.openpty() set_win_size(slave, 25, 80) reactor.spawnProcess( Protocol(), sys.executable, (sys.executable, "-m", "bpython." + self.backend, "--config", TEST_CONFIG), env=dict(TERM="vt100", LANG=os.environ.get("LANG", "")), usePTY=(master, slave, os.ttyname(slave))) return result @attr(speed='slow') def test_issue108(self): input = textwrap.dedent( """\ def spam(): u"y\\xe4y" \b spam(""") deferred = self.run_bpython(input) return deferred.addCallback(self.check_no_traceback) @attr(speed='slow') def test_issue133(self): input = textwrap.dedent( """\ def spam(a, (b, c)): pass \b spam(1""") return self.run_bpython(input).addCallback(self.check_no_traceback) def check_no_traceback(self, data): self.assertNotIn("Traceback", data) @unittest.skipIf(reactor is None, "twisted is not available") class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" @unittest.skipUnless(have_urwid, "urwid is required") @unittest.skipIf(reactor is None, "twisted is not available") class UrwidCrashersTest(TrialTestCase, CrashersTest): backend = "urwid" if __name__ == "__main__": unittest.main() bpython-0.17.1/bpython/test/test.config0000644000175100017510000000007713240407731020060 0ustar useruser00000000000000[general] hist_length = 0 hist_file = /dev/null paste_time = 0 bpython-0.17.1/bpython/test/test_inspection.py0000644000175100017510000001156113240407731021476 0ustar useruser00000000000000# -*- coding: utf-8 -*- import os from bpython._py3compat import py3 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 ''' foo_non_ascii = u'''def foo(): """Test äöü""" pass ''' class OldCallable: def __call__(self): pass class Callable(object): def __call__(self): pass class OldNoncallable: pass class Noncallable(object): pass def spam(): pass class CallableMethod(object): def method(self): pass class TestInspection(unittest.TestCase): def test_is_callable(self): self.assertTrue(inspection.is_callable(spam)) self.assertTrue(inspection.is_callable(Callable)) self.assertTrue(inspection.is_callable(Callable())) self.assertTrue(inspection.is_callable(OldCallable)) self.assertTrue(inspection.is_callable(OldCallable())) self.assertFalse(inspection.is_callable(Noncallable())) self.assertFalse(inspection.is_callable(OldNoncallable())) self.assertFalse(inspection.is_callable(None)) self.assertTrue(inspection.is_callable(CallableMethod().method)) @unittest.skipIf(py3, 'old-style classes only exist in Python 2') def test_is_new_style_py2(self): self.assertTrue(inspection.is_new_style(spam)) self.assertTrue(inspection.is_new_style(Noncallable)) self.assertFalse(inspection.is_new_style(OldNoncallable)) self.assertTrue(inspection.is_new_style(Noncallable())) self.assertFalse(inspection.is_new_style(OldNoncallable())) self.assertTrue(inspection.is_new_style(None)) @unittest.skipUnless(py3, 'only in Python 3 are all classes new-style') def test_is_new_style_py3(self): self.assertTrue(inspection.is_new_style(spam)) self.assertTrue(inspection.is_new_style(Noncallable)) self.assertTrue(inspection.is_new_style(OldNoncallable)) self.assertTrue(inspection.is_new_style(Noncallable())) self.assertTrue(inspection.is_new_style(OldNoncallable())) self.assertTrue(inspection.is_new_style(None)) def test_parsekeywordpairs(self): # See issue #109 def fails(spam=['-a', '-b']): pass default_arg_repr = "['-a', '-b']" self.assertEqual(str(['-a', '-b']), default_arg_repr, 'This test is broken (repr does not match), fix me.') 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.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.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") def test_get_encoding_ascii(self): self.assertEqual(inspection.get_encoding(encoding_ascii), 'ascii') self.assertEqual(inspection.get_encoding(encoding_ascii.foo), 'ascii') def test_get_encoding_latin1(self): self.assertEqual(inspection.get_encoding(encoding_latin1), 'latin1') self.assertEqual(inspection.get_encoding(encoding_latin1.foo), 'latin1') def test_get_encoding_utf8(self): self.assertEqual(inspection.get_encoding(encoding_utf8), 'utf-8') self.assertEqual(inspection.get_encoding(encoding_utf8.foo), 'utf-8') def test_get_source_ascii(self): self.assertEqual(inspection.get_source_unicode(encoding_ascii.foo), foo_ascii_only) def test_get_source_utf8(self): self.assertEqual(inspection.get_source_unicode(encoding_utf8.foo), foo_non_ascii) def test_get_source_latin1(self): self.assertEqual(inspection.get_source_unicode(encoding_latin1.foo), foo_non_ascii) def test_get_source_file(self): path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fodder') encoding = inspection.get_encoding_file( os.path.join(path, 'encoding_ascii.py')) self.assertEqual(encoding, 'ascii') encoding = inspection.get_encoding_file( os.path.join(path, 'encoding_latin1.py')) self.assertEqual(encoding, 'latin1') encoding = inspection.get_encoding_file( os.path.join(path, 'encoding_utf8.py')) self.assertEqual(encoding, 'utf-8') if __name__ == '__main__': unittest.main() bpython-0.17.1/bpython/test/test_args.py0000644000175100017510000000453313240410473020255 0ustar useruser00000000000000# encoding: utf-8 import re import subprocess import sys import tempfile from textwrap import dedent from bpython import args from bpython.test import (FixLanguageTestCase as TestCase, unittest) try: from nose.plugins.attrib import attr except ImportError: def attr(*args, **kwargs): def identity(func): return func return identity @attr(speed='slow') class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent("""\ import sys sys.stderr.write(__file__) sys.stderr.flush()""")) f.flush() p = subprocess.Popen( [sys.executable] + (['-W', 'ignore'] if sys.version_info[:2] == (2, 6) else []) + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, universal_newlines=True) (_, stderr) = p.communicate() self.assertEquals(stderr.strip(), f.name) def test_exec_nonascii_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent('''\ #!/usr/bin/env python2 # coding: utf-8 "你好 # nonascii" ''')) f.flush() try: subprocess.check_call([ 'python', '-m', 'bpython.curtsies', f.name]) except subprocess.CalledProcessError: self.fail('Error running module with nonascii characters') def test_exec_nonascii_file_linenums(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent("""\ #!/usr/bin/env python2 # coding: utf-8 1/0 """)) f.flush() p = subprocess.Popen( [sys.executable, "-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, universal_newlines=True) (_, stderr) = p.communicate() self.assertIn('line 3', clean_colors(stderr)) def clean_colors(s): return re.sub(r'\x1b[^m]*m', '', s) class TestParse(TestCase): def test_version(self): with self.assertRaises(SystemExit): args.parse(['--version']) bpython-0.17.1/bpython/test/test_curtsies_parser.py0000644000175100017510000000160213240410473022530 0ustar useruser00000000000000# encoding: utf-8 from __future__ import unicode_literals from bpython.test import unittest from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold class TestExecArgs(unittest.TestCase): def test_parse(self): self.assertEquals(parse.parse('\x01y\x03print\x04'), yellow('print')) self.assertEquals( parse.parse('\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c' '\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04'), yellow('print') + cyan(' ') + green('1') + cyan(' ') + bold(yellow('+')) + cyan(' ') + green(u'2')) def test_peal_off_string(self): self.assertEquals(parse.peel_off_string('\x01RI\x03]\x04asdf'), ({'bg': 'I', 'string': ']', 'fg': 'R', 'colormarker': '\x01RI', 'bold': ''}, 'asdf')) bpython-0.17.1/bpython/test/test_importcompletion.py0000644000175100017510000000314413240407731022725 0ustar useruser00000000000000# encoding: utf-8 from __future__ import unicode_literals from bpython import importcompletion from bpython.test import unittest class TestSimpleComplete(unittest.TestCase): def setUp(self): self.original_modules = importcompletion.modules importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', 'zzabc.f'] def tearDown(self): importcompletion.modules = self.original_modules def test_simple_completion(self): self.assertSetEqual(importcompletion.complete(10, 'import zza'), set(['zzabc', 'zzabd'])) def test_package_completion(self): self.assertSetEqual(importcompletion.complete(13, 'import zzabc.'), set(['zzabc.e', 'zzabc.f'])) class TestRealComplete(unittest.TestCase): @classmethod def setUpClass(cls): for _ in importcompletion.find_iterator: pass __import__('sys') __import__('os') @classmethod def tearDownClass(cls): importcompletion.find_iterator = importcompletion.find_all_modules() importcompletion.modules = set() def test_from_attribute(self): self.assertSetEqual( importcompletion.complete(19, 'from sys import arg'), set(['argv'])) def test_from_attr_module(self): self.assertSetEqual(importcompletion.complete(9, 'from os.p'), set(['os.path'])) def test_from_package(self): self.assertSetEqual(importcompletion.complete(17, 'from xml import d'), set(['dom'])) bpython-0.17.1/bpython/test/test_curtsies_coderunner.py0000644000175100017510000000271613240407731023412 0ustar useruser00000000000000# encoding: utf-8 import sys from bpython.test import mock, unittest from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput class TestCodeRunner(unittest.TestCase): def setUp(self): self.orig_stdout = sys.stdout self.orig_stderr = sys.stderr def tearDown(self): sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr def test_simple(self): c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) stdout = FakeOutput(c, lambda *args, **kwargs: None) stderr = FakeOutput(c, lambda *args, **kwargs: None) sys.stdout = stdout sys.stdout = stderr c.load_code('1 + 1') c.run_code() c.run_code() c.run_code() def test_exception(self): c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) def ctrlc(): raise KeyboardInterrupt() stdout = FakeOutput(c, lambda x: ctrlc()) stderr = FakeOutput(c, lambda *args, **kwargs: None) sys.stdout = stdout sys.stderr = stderr c.load_code('1 + 1') c.run_code() class TestFakeOutput(unittest.TestCase): def assert_unicode(self, s): self.assertIsInstance(s, type(u'')) def test_bytes(self): out = FakeOutput(mock.Mock(), self.assert_unicode) out.write('native string type') bpython-0.17.1/bpython/test/__init__.py0000644000175100017510000000140113240407731020013 0ustar useruser00000000000000# -*- coding: utf-8 -*- try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock from bpython.translations import init from bpython._py3compat import py3 from six.moves import builtins import os class FixLanguageTestCase(unittest.TestCase): @classmethod def setUpClass(cls): init(languages=['en']) class MagicIterMock(mock.MagicMock): if py3: __next__ = mock.Mock(return_value=None) else: next = mock.Mock(return_value=None) def builtin_target(obj): """Returns mock target string of a builtin""" return '%s.%s' % (builtins.__name__, obj.__name__) TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") bpython-0.17.1/bpython/filelock.py0000644000175100017510000000555513240407731017103 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2015-2016 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import try: import fcntl import errno has_fcntl = True except ImportError: has_fcntl = False try: import msvcrt has_msvcrt = True except ImportError: has_msvcrt = False class BaseLock(object): """Base class for file locking """ def __init__(self, fd): self.fd = fd self.locked = False def acquire(self): pass def release(self): pass def __enter__(self): self.acquire() return self def __exit__(self, *args): if self.locked: self.release() def __del__(self): if self.locked: self.release() class UnixFileLock(BaseLock): """Simple file locking for Unix using fcntl """ def __init__(self, fd, mode=None): super(UnixFileLock, self).__init__(fd) if mode is None: mode = fcntl.LOCK_EX self.mode = mode def acquire(self): try: fcntl.flock(self.fd, self.mode) self.locked = True except IOError as e: if e.errno != errno.ENOLCK: raise e def release(self): fcntl.flock(self.fd, fcntl.LOCK_UN) self.locked = False class WindowsFileLock(BaseLock): """Simple file locking for Windows using msvcrt """ def __init__(self, fd, mode=None): super(WindowsFileLock, self).__init__(fd) def acquire(self): msvcrt.locking(self.fd, msvcrt.LK_NBLCK, 1) self.locked = True def release(self): msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1) self.locked = False if has_fcntl: FileLock = UnixFileLock elif has_msvcrt: FileLock = WindowsFileLock else: FileLock = BaseLock # vim: sw=4 ts=4 sts=4 ai et bpython-0.17.1/bpython/sample-config0000644000175100017510000000546213240407731017405 0ustar useruser00000000000000# This is a standard python config file # Valid values can be True, False, integer numbers, strings # By default bpython will look for $XDG_CONFIG_HOME/bpython/config # ($XDG_CONFIG_HOME defaults to ~/.config) or you can specify a file with the # --config option on the command line # # see http://docs.bpython-interpreter.org/configuration.html # for all configurable options # General section tag [general] # Display the autocomplete list as you type (default: True). # When this is off, you can hit tab to see the suggestions. # auto_display_list = True # Syntax highlighting as you type (default: True). # syntax = True # Display the arg spec (list of arguments) for callables, # when possible (default: True). # arg_spec = True # History file (default: ~/.pythonhist): # hist_file = ~/.pythonhist # Number of lines to store in history (set to 0 to disable) (default: 100): # hist_length = 100 # Soft tab size (default: 4, see pep-8): # tab_length = 4 # Color schemes should be put in $XDG_CONFIG_HOME/bpython/ e.g. to use the theme # $XDG_CONFIG_HOME/bpython/foo.theme set color_scheme = foo. Leave blank or set # to "default" to use the default theme # color_scheme = default # External editor to use for editing the current line, block, or full history # Default is to try $EDITOR and $VISUAL, then vi - but if you uncomment # the line below that will take precedence # editor = vi # Whether to append .py to the filename while saving session to a file. # (default: False) # save_append_py = False # The name of a helper executable that should perform pastebin upload on # bpython's behalf. If unset, bpython uploads pastes to bpaste.net. (default: ) #pastebin_helper = gist.py # How long an undo must be expected to take before prompting for how # many lines should be undone. Set to -1 to never prompt, or 0 to # always prompt. # single_undo_time = 1.0 # Enable autoreload feature by default (default: False). # default_autoreload = False [keyboard] # All key bindings are shown commented out with their default binding # pastebin = F8 # last_output = F9 # reimport = F6 # help = F1 # toggle_file_watch = F5 # save = C-s # undo = C-r # up_one_line = C-p # down_one_line = C-n # cut_to_buffer = C-k # search = C-o # yank_from_buffer = C-y # backspace = C-h # clear_word = C-w # clear_line = C-u # clear_screen = C-l # show_source = F2 # exit = C-d # external_editor = F7 # edit_config = F3 # reverse_incremental_search = M-r # incremental_search = M-s [curtsies] # Allow the the completion and docstring box above the current line # (default: False) # list_above = False # Enables two fish (the shell) style features: # Previous line key will search for the current line (like reverse incremental # search) and right arrow will complete the current line with the first match # from history. (default: True) # right_arrow_completion = True bpython-0.17.1/bpython/line.py0000644000175100017510000001730113240407731016232 0ustar useruser00000000000000# encoding: utf-8 """Extracting and changing portions of the current line All functions take cursor offset from the beginning of the line and the line of Python code, and return None, or a tuple of the start index, end index, and the word.""" from __future__ import unicode_literals, absolute_import from itertools import chain from collections import namedtuple from .lazyre import LazyReCompile LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) current_word_re = LazyReCompile( r'(?= pos: start = m.start(1) end = m.end(1) word = m.group(1) if word is None: return None return LinePart(start, end, word) current_dict_key_re = LazyReCompile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') def current_dict_key(cursor_offset, line): """If in dictionary completion, return the current key""" matches = current_dict_key_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)) return None current_dict_re = LazyReCompile(r'''([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)''') def current_dict(cursor_offset, line): """If in dictionary completion, return the dict that should be used""" matches = current_dict_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(1), m.group(1)) return None current_string_re = LazyReCompile( '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|''' '''(?P.+))''') def current_string(cursor_offset, line): """If inside a string of nonzero length, return the string (excluding quotes) Weaker than bpython.Repl's current_string, because that checks that a string is a string based on previous lines in the buffer.""" 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 LinePart(m.start(i), m.end(i), m.group(i)) return None current_object_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]') def current_object(cursor_offset, line): """If in attribute completion, the object on which attribute should be looked up.""" match = current_word(cursor_offset, line) if match is None: return None start, end, word = match matches = current_object_re.finditer(word) s = '' for m in matches: if m.end(1) + start < cursor_offset: if s: s += '.' s += m.group(1) if not s: return None return LinePart(start, start + len(s), s) current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" # TODO replace with more general current_expression_attribute match = current_word(cursor_offset, line) if match is None: return None start, end, word = match matches = current_object_attribute_re.finditer(word) next(matches) for m in matches: if (m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset): return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) return None current_from_import_from_re = LazyReCompile( r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') def current_from_import_from(cursor_offset, line): """If in from import completion, the word after from returns None if cursor not in or just after one of the two interesting parts of an import: from (module) import (name1, name2) """ # TODO allow for as's tokens = line.split() if not ('from' in tokens or 'import' in tokens): return None matches = current_from_import_from_re.finditer(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 LinePart(m.start(1), m.end(1), m.group(1)) return None current_from_import_import_re_1 = LazyReCompile(r'from\s([\w0-9_.]*)\s+import') current_from_import_import_re_2 = LazyReCompile(r'([\w0-9_]+)') current_from_import_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_]*)') def current_from_import_import(cursor_offset, line): """If in from import completion, the word after import being completed returns None if cursor not in or just after one of these words """ baseline = current_from_import_import_re_1.search(line) if baseline is None: return None match1 = current_from_import_import_re_2.search(line[baseline.end():]) if match1 is None: return None matches = current_from_import_import_re_3.finditer(line[baseline.end():]) for m in chain((match1, ), matches): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: return LinePart(start, end, m.group(1)) return None current_import_re_1 = LazyReCompile(r'import') current_import_re_2 = LazyReCompile(r'([\w0-9_.]+)') current_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_.]*)') def current_import(cursor_offset, line): # TODO allow for multiple as's baseline = current_import_re_1.search(line) if baseline is None: return None match1 = current_import_re_2.search(line[baseline.end():]) if match1 is None: return None matches = current_import_re_3.finditer(line[baseline.end():]) for m in chain((match1, ), matches): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: return LinePart(start, end, m.group(1)) current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" 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 LinePart(m.start(1), m.end(1), m.group(1)) return None current_single_word_re = LazyReCompile(r"(?= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None def current_dotted_attribute(cursor_offset, line): """The dotted attribute-object pair before the cursor""" match = current_word(cursor_offset, line) if match is None: return None start, end, word = match if '.' in word[1:]: return LinePart(start, end, word) current_expression_attribute_re = LazyReCompile( r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') def current_expression_attribute(cursor_offset, line): """If after a dot, the attribute being completed""" # TODO replace with more general current_expression_attribute matches = current_expression_attribute_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)) return None bpython-0.17.1/bpython/curtsiesfrontend/0000755000175100017510000000000013240411345020324 5ustar useruser00000000000000bpython-0.17.1/bpython/curtsiesfrontend/events.py0000644000175100017510000000236213240407731022211 0ustar useruser00000000000000# encoding: utf-8 """Non-keyboard events used in bpython curtsies REPL""" import time import curtsies.events class ReloadEvent(curtsies.events.Event): """Request to rerun REPL session ASAP because imported modules changed""" def __init__(self, files_modified=('?',)): self.files_modified = files_modified def __repr__(self): return "" % (' & '.join(self.files_modified)) class RefreshRequestEvent(curtsies.events.Event): """Request to refresh REPL display ASAP""" def __repr__(self): return "" class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): """Request to refresh the REPL display at some point in the future Used to schedule the disappearance of status bar message that only shows for a few seconds""" def __init__(self, when): self.when = when # time.time() + how long def __repr__(self): return ("" % (self.when - time.time())) class RunStartupFileEvent(curtsies.events.Event): """Request to run the startup file.""" class UndoEvent(curtsies.events.Event): """Request to undo.""" def __init__(self, n=1): self.n = n bpython-0.17.1/bpython/curtsiesfrontend/filewatch.py0000644000175100017510000000537313240407731022660 0ustar useruser00000000000000# encoding: utf-8 import os from collections import defaultdict from bpython import importcompletion try: from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler except ImportError: def ModuleChangedEventHandler(*args): return None else: class ModuleChangedEventHandler(FileSystemEventHandler): def __init__(self, paths, on_change): self.dirs = defaultdict(set) self.on_change = on_change self.modules_to_add_later = [] self.observer = Observer() self.old_dirs = defaultdict(set) self.started = False self.activated = False for path in paths: self._add_module(path) def reset(self): self.dirs = defaultdict(set) del self.modules_to_add_later[:] self.old_dirs = defaultdict(set) self.observer.unschedule_all() def _add_module(self, path): """Add a python module to track changes""" path = os.path.abspath(path) for suff in importcompletion.SUFFIXES: if path.endswith(suff): path = path[:-len(suff)] break dirname = os.path.dirname(path) if dirname not in self.dirs: self.observer.schedule(self, dirname, recursive=False) self.dirs[dirname].add(path) def _add_module_later(self, path): self.modules_to_add_later.append(path) def track_module(self, path): """ Begins tracking this if activated, or remembers to track later. """ if self.activated: self._add_module(path) else: self._add_module_later(path) def activate(self): if self.activated: raise ValueError("%r is already activated." % (self,)) if not self.started: self.started = True self.observer.start() for dirname in self.dirs: self.observer.schedule(self, dirname, recursive=False) for module in self.modules_to_add_later: self._add_module(module) del self.modules_to_add_later[:] self.activated = True def deactivate(self): if not self.activated: raise ValueError("%r is not activated." % (self,)) self.observer.unschedule_all() self.activated = False def on_any_event(self, event): dirpath = os.path.dirname(event.src_path) paths = [path + '.py' for path in self.dirs[dirpath]] if event.src_path in paths: self.on_change(files_modified=[event.src_path]) bpython-0.17.1/bpython/curtsiesfrontend/interaction.py0000644000175100017510000001362113240407731023224 0ustar useruser00000000000000# encoding: utf-8 from __future__ import unicode_literals import greenlet import time import curtsies.events as events from bpython.repl import Interaction as BpythonInteraction from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython.curtsiesfrontend.manual_readline import edit_keys class StatusBar(BpythonInteraction): """StatusBar and Interaction for Repl Passing of control back and forth between calls that use interact api (notify, confirm, file_prompt) like bpython.Repl.write2file and events on the main thread happens via those calls and self.wait_for_request_or_notify. Calling one of these three is required for the main thread to regain control! This is probably a terrible idea, and better would be rewriting this functionality in a evented or callback style, but trying to integrate bpython.Repl code. """ def __init__(self, permanent_text="", request_refresh=lambda: None, schedule_refresh=lambda when: None): self._current_line = '' self.cursor_offset_in_line = 0 self.in_prompt = False self.in_confirm = False self.waiting_for_refresh = False self.prompt = '' self._message = '' self.message_start_time = time.time() self.message_time = 3 self.permanent_stack = [] if permanent_text: self.permanent_stack.append(permanent_text) self.main_context = greenlet.getcurrent() self.request_context = None self.request_refresh = request_refresh self.schedule_refresh = schedule_refresh def push_permanent_message(self, msg): self._message = '' self.permanent_stack.append(msg) def pop_permanent_message(self, msg): if msg in self.permanent_stack: self.permanent_stack.remove(msg) else: raise ValueError("Messsage %r was not in permanent_stack" % msg) @property def has_focus(self): return self.in_prompt or self.in_confirm or self.waiting_for_refresh def message(self, msg, schedule_refresh=True): """Sets a temporary message""" self.message_start_time = time.time() self._message = msg if schedule_refresh: self.schedule_refresh(time.time() + self.message_time) def _check_for_expired_message(self): if (self._message and time.time() > self.message_start_time + self.message_time): self._message = '' def process_event(self, e): """Returns True if shutting down""" assert self.in_prompt or self.in_confirm or self.waiting_for_refresh if isinstance(e, RefreshRequestEvent): self.waiting_for_refresh = False self.request_context.switch() elif isinstance(e, events.PasteEvent): for ee in e.events: # strip control seq self.add_normal_character(ee if len(ee) == 1 else ee[-1]) elif e in [''] or isinstance(e, events.SigIntEvent): self.request_context.switch(False) self.escape() elif e in edit_keys: self.cursor_offset_in_line, self._current_line = edit_keys[e]( self.cursor_offset_in_line, self._current_line) elif e == "": # TODO can this be removed? raise KeyboardInterrupt() elif e == "": # TODO this isn't a very intuitive behavior raise SystemExit() elif self.in_prompt and e in ("\n", "\r", "", "Ctrl-m>"): line = self._current_line self.escape() self.request_context.switch(line) elif self.in_confirm: if e in ('y', 'Y'): self.request_context.switch(True) else: self.request_context.switch(False) self.escape() else: # add normal character self.add_normal_character(e) def add_normal_character(self, e): if e == '': e = ' ' if len(e) > 1: return self._current_line = (self._current_line[:self.cursor_offset_in_line] + e + self._current_line[self.cursor_offset_in_line:]) self.cursor_offset_in_line += 1 def escape(self): """unfocus from statusbar, clear prompt state, wait for notify call""" self.in_prompt = False self.in_confirm = False self.prompt = '' self._current_line = '' @property def current_line(self): self._check_for_expired_message() if self.in_prompt: return self.prompt + self._current_line if self.in_confirm: return self.prompt if self._message: return self._message if self.permanent_stack: return self.permanent_stack[-1] return '' @property def should_show_message(self): return bool(self.current_line) # interaction interface - should be called from other greenlets def notify(self, msg, n=3, wait_for_keypress=False): self.request_context = greenlet.getcurrent() self.message_time = n self.message(msg, schedule_refresh=wait_for_keypress) self.waiting_for_refresh = True self.request_refresh() self.main_context.switch(msg) # below Really ought to be called from greenlets other than main because # they block def confirm(self, q): """Expected to return True or False, given question prompt q""" self.request_context = greenlet.getcurrent() self.prompt = q self.in_confirm = True return self.main_context.switch(q) def file_prompt(self, s): """Expected to return a file name, given """ self.request_context = greenlet.getcurrent() self.prompt = s self.in_prompt = True result = self.main_context.switch(s) return result bpython-0.17.1/bpython/curtsiesfrontend/_internal.py0000644000175100017510000000414113240407731022655 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 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 # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import pydoc import bpython._internal from bpython._py3compat import py3 from bpython.repl import getpreferredencoding class NopPydocPager(object): def __enter__(self): self._orig_pager = pydoc.pager pydoc.pager = self def __exit__(self, *args): pydoc.pager = self._orig_pager def __call__(self, text): return None class _Helper(bpython._internal._Helper): def __init__(self, repl=None): self._repl = repl pydoc.pager = self.pager super(_Helper, self).__init__() def pager(self, output): if not py3 and isinstance(output, str): output = output.decode(getpreferredencoding()) self._repl.pager(output) def __call__(self, *args, **kwargs): if self._repl.reevaluating: with NopPydocPager(): return super(_Helper, self).__call__(*args, **kwargs) else: return super(_Helper, self).__call__(*args, **kwargs) # vim: sw=4 ts=4 sts=4 ai et bpython-0.17.1/bpython/curtsiesfrontend/manual_readline.py0000644000175100017510000002515113240407731024026 0ustar useruser00000000000000# encoding: utf-8 """implementations of simple readline edit operations just the ones that fit the model of transforming the current line and the cursor location based on http://www.bigsmoke.us/readline/shortcuts""" from bpython.lazyre import LazyReCompile import inspect from six import iteritems from bpython._py3compat import py3 INDENT = 4 # TODO Allow user config of keybindings for these actions if not py3: getargspec = lambda func: inspect.getargspec(func)[0] else: getargspec = lambda func: inspect.signature(func).parameters class AbstractEdits(object): default_kwargs = { 'line': 'hello world', 'cursor_offset': 5, 'cut_buffer': 'there', } def __contains__(self, key): try: self[key] except KeyError: return False else: return True def add(self, key, func, overwrite=False): if key in self: if overwrite: del self[key] else: raise ValueError('key %r already has a mapping' % (key,)) params = getargspec(func) args = dict((k, v) for k, v in iteritems(self.default_kwargs) if k in params) r = func(**args) if len(r) == 2: if hasattr(func, 'kills'): raise ValueError('function %r returns two values, but has a ' 'kills attribute' % (func,)) self.simple_edits[key] = func elif len(r) == 3: if not hasattr(func, 'kills'): raise ValueError('function %r returns three values, but has ' 'no kills attribute' % (func,)) self.cut_buffer_edits[key] = func else: raise ValueError('return type of function %r not recognized' % (func,)) def add_config_attr(self, config_attr, func): if config_attr in self.awaiting_config: raise ValueError('config attribute %r already has a mapping' % (config_attr,)) self.awaiting_config[config_attr] = func def call(self, key, **kwargs): func = self[key] params = getargspec(func) args = dict((k, v) for k, v in kwargs.items() if k in params) return func(**args) def call_without_cut(self, key, **kwargs): """Looks up the function and calls it, returning only line and cursor offset""" r = self.call_for_two(key, **kwargs) return r[:2] def __getitem__(self, key): if key in self.simple_edits: return self.simple_edits[key] if key in self.cut_buffer_edits: return self.cut_buffer_edits[key] raise KeyError("key %r not mapped" % (key,)) def __delitem__(self, key): if key in self.simple_edits: del self.simple_edits[key] elif key in self.cut_buffer_edits: del self.cut_buffer_edits[key] else: raise KeyError("key %r not mapped" % (key,)) class UnconfiguredEdits(AbstractEdits): """Maps key to edit functions, and bins them by what parameters they take. Only functions with specific signatures can be added: * func(**kwargs) -> cursor_offset, line * func(**kwargs) -> cursor_offset, line, cut_buffer where kwargs are in among the keys of Edits.default_kwargs These functions will be run to determine their return type, so no side effects! More concrete Edits instances can be created by applying a config with Edits.mapping_with_config() - this creates a new Edits instance that uses a config file to assign config_attr bindings. Keys can't be added twice, config attributes can't be added twice. """ def __init__(self): self.simple_edits = {} self.cut_buffer_edits = {} self.awaiting_config = {} def mapping_with_config(self, config, key_dispatch): """Creates a new mapping object by applying a config object""" return ConfiguredEdits(self.simple_edits, self.cut_buffer_edits, self.awaiting_config, config, key_dispatch) def on(self, key=None, config=None): if not ((key is None) ^ (config is None)): raise ValueError("Must use exactly one of key, config") if key is not None: def add_to_keybinds(func): self.add(key, func) return func return add_to_keybinds else: def add_to_config(func): self.add_config_attr(config, func) return func return add_to_config class ConfiguredEdits(AbstractEdits): def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, key_dispatch): self.simple_edits = dict(simple_edits) self.cut_buffer_edits = dict(cut_buffer_edits) for attr, func in awaiting_config.items(): for key in key_dispatch[getattr(config, attr)]: super(ConfiguredEdits, self).add(key, func, overwrite=True) def add_config_attr(self, config_attr, func): raise NotImplementedError("Config already set on this mapping") def add(self, key, func): raise NotImplementedError("Config already set on this mapping") edit_keys = UnconfiguredEdits() # Because the edits.on decorator runs the functions, functions which depend # on other functions must be declared after their dependencies def kills_behind(func): func.kills = 'behind' return func def kills_ahead(func): func.kills = 'ahead' return func @edit_keys.on(config='left_key') @edit_keys.on('') def left_arrow(cursor_offset, line): return max(0, cursor_offset - 1), line @edit_keys.on(config='right_key') @edit_keys.on('') def right_arrow(cursor_offset, line): return min(len(line), cursor_offset + 1), line @edit_keys.on(config='beginning_of_line_key') @edit_keys.on('') def beginning_of_line(cursor_offset, line): return 0, line @edit_keys.on(config='end_of_line_key') @edit_keys.on('') def end_of_line(cursor_offset, line): return len(line), line forward_word_re = LazyReCompile(r"\S\s") @edit_keys.on('') @edit_keys.on('') @edit_keys.on('') def forward_word(cursor_offset, line): match = forward_word_re.search(line[cursor_offset:]+' ') delta = match.end() - 1 if match else 0 return (cursor_offset + delta, line) def last_word_pos(string): """returns the start index of the last word of given string""" match = forward_word_re.search(string[::-1]) index = match and len(string) - match.end() + 1 return index or 0 @edit_keys.on('') @edit_keys.on('') @edit_keys.on('') def back_word(cursor_offset, line): return (last_word_pos(line[:cursor_offset]), line) @edit_keys.on('') def delete(cursor_offset, line): return (cursor_offset, line[:cursor_offset] + line[cursor_offset+1:]) @edit_keys.on('') @edit_keys.on(config='backspace_key') def backspace(cursor_offset, line): if cursor_offset == 0: return cursor_offset, line if not line[:cursor_offset].strip(): # if just whitespace left of cursor # front_white = len(line[:cursor_offset]) - \ # len(line[:cursor_offset].lstrip()) to_delete = ((cursor_offset - 1) % INDENT) + 1 return (cursor_offset - to_delete, line[:cursor_offset - to_delete] + line[cursor_offset:]) return (cursor_offset - 1, line[:cursor_offset - 1] + line[cursor_offset:]) @edit_keys.on(config='clear_line_key') def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] delete_rest_of_word_re = LazyReCompile(r'\w\b') @edit_keys.on('') # option-d @kills_ahead def delete_rest_of_word(cursor_offset, line): m = delete_rest_of_word_re.search(line[cursor_offset:]) if not m: return cursor_offset, line, '' return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:], line[cursor_offset:m.start()+cursor_offset+1]) delete_word_to_cursor_re = LazyReCompile(r'\s\S') @edit_keys.on(config='clear_word_key') @kills_behind def delete_word_to_cursor(cursor_offset, line): start = 0 for match in delete_word_to_cursor_re.finditer(line[:cursor_offset]): start = match.start() + 1 return (start, line[:start] + line[cursor_offset:], line[start:cursor_offset]) @edit_keys.on('') def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): # TODO not implemented - just prev return (cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:]) @edit_keys.on(config='yank_from_buffer_key') def yank_prev_killed_text(cursor_offset, line, cut_buffer): return (cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:]) @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): 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 '') + line[cursor_offset - 1] + line[cursor_offset + 1:]) @edit_keys.on('') def transpose_word_before_cursor(cursor_offset, line): return cursor_offset, line # TODO Not implemented # TODO undo all changes to line: meta-r # bonus functions (not part of readline) @edit_keys.on('') def uppercase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented @edit_keys.on(config='cut_to_buffer_key') @kills_ahead def delete_from_cursor_forward(cursor_offset, line): return cursor_offset, line[:cursor_offset], line[cursor_offset:] @edit_keys.on('') def titlecase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented delete_word_from_cursor_back_re = LazyReCompile(r'^|\b\w') @edit_keys.on('') @edit_keys.on('') @kills_behind def delete_word_from_cursor_back(cursor_offset, line): """Whatever my option-delete does in bash on my mac""" if not line: return cursor_offset, line, '' start = None for match in delete_word_from_cursor_back_re.finditer(line): if match.start() < cursor_offset: start = match.start() if start is not None: return (start, line[:start] + line[cursor_offset:], line[start:cursor_offset]) else: return cursor_offset, line, '' bpython-0.17.1/bpython/curtsiesfrontend/replpainter.py0000644000175100017510000002053413240410570023226 0ustar useruser00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import logging import itertools from six.moves import range from curtsies import fsarray, fmtstr from curtsies.formatstring import linesplit from curtsies.fmtfuncs import bold from bpython.curtsiesfrontend.parse import func_for_letter from bpython._py3compat import py3 if not py3: import inspect logger = logging.getLogger(__name__) # All paint functions should # * return an array of the width they were asked for # * return an array not taller than the height they were asked for def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. Warning: if msg is empty, returns an empty list of lines""" display_lines = ([msg[start:end] for start, end in zip( range(0, len(msg), columns), range(columns, len(msg) + columns, columns))] if msg else ([''] if blank_line else [])) return display_lines def paint_history(rows, columns, display_lines): lines = [] for r, line in zip(range(rows), display_lines[-rows:]): lines.append(fmtstr(line[:columns])) r = fsarray(lines, width=columns) assert r.shape[0] <= rows, repr(r.shape) + ' ' + repr(rows) assert r.shape[1] <= columns, repr(r.shape) + ' ' + repr(columns) return r def paint_current_line(rows, columns, current_display_line): lines = display_linize(current_display_line, columns, True) return fsarray(lines, width=columns) def paginate(rows, matches, current, words_wide): if current not in matches: current = matches[0] per_page = rows * words_wide current_page = matches.index(current) // per_page return matches[per_page * current_page:per_page * (current_page + 1)] def matches_lines(rows, columns, matches, current, config, format): highlight_color = func_for_letter(config.color_scheme['operator'].lower()) if not matches: return [] color = func_for_letter(config.color_scheme['main']) max_match_width = max(len(m) for m in matches) words_wide = max(1, (columns - 1) // (max_match_width + 1)) matches = [format(m) for m in matches] if current: current = format(current) matches = paginate(rows, matches, current, words_wide) matches_lines = [fmtstr(' ').join(color(m.ljust(max_match_width)) if m != current else highlight_color( m.ljust(max_match_width)) for m in matches[i:i + words_wide]) for i in range(0, len(matches), words_wide)] logger.debug('match: %r' % current) logger.debug('matches_lines: %r' % matches_lines) return matches_lines def formatted_argspec(funcprops, arg_pos, columns, config): # Pretty directly taken from bpython.cli 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 = 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()) punctuation_color = func_for_letter(config.color_scheme['punctuation']) token_color = func_for_letter(config.color_scheme['token']) bolds = {token_color: lambda x: bold(token_color(x)), arg_color: lambda x: bold(arg_color(x))} s = func_color(func) + arg_color(': (') if is_bound_method and isinstance(arg_pos, int): # TODO what values could this have? 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 arg_pos in (i, arg) else arg_color if i == arg_pos or arg == arg_pos: color = bolds[color] if not py3: s += color(inspect.strseq(arg, unicode)) else: s += color(arg) if kw is not None: s += punctuation_color('=') if not py3: kw = kw.decode('ascii', 'replace') s += token_color(kw) if i != len(args) - 1: s += punctuation_color(', ') if _args: if args: s += punctuation_color(', ') s += token_color('*%s' % (_args,)) if py3 and kwonly: if not _args: if args: s += punctuation_color(', ') s += punctuation_color('*') marker = object() for arg in kwonly: s += punctuation_color(', ') color = token_color if arg_pos: color = bolds[color] s += color(arg) default = kwonly_defaults.get(arg, marker) if default is not marker: s += punctuation_color('=') s += token_color(repr(default)) if _kwargs: if args or _args or (py3 and kwonly): s += punctuation_color(', ') s += token_color('**%s' % (_kwargs,)) s += punctuation_color(')') return linesplit(s, columns) def formatted_docstring(docstring, columns, config): if isinstance(docstring, bytes): docstring = docstring.decode('utf8') elif isinstance(docstring, str if py3 else unicode): pass else: # TODO: fail properly here and catch possible exceptions in callers. return [] color = func_for_letter(config.color_scheme['comment']) return sum(([color(x) for x in (display_linize(line, columns) if line else fmtstr(''))] for line in docstring.split('\n')), []) 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 from_argspec = (formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) from_doc = (formatted_docstring(docstring, width, config) if docstring else []) from_matches = (matches_lines(max(1, rows - len(from_argspec) - 2), width, matches, match, config, format) if matches else []) lines = from_argspec + from_matches + from_doc def add_border(line): """Add colored borders left and right to a line.""" new_line = border_color(config.left_border + ' ') new_line += line.ljust(width)[:width] new_line += border_color(' ' + config.right_border) return new_line border_color = func_for_letter(config.color_scheme['main']) top_line = border_color(config.left_top_corner + config.top_border * (width + 2) + config.right_top_corner) bottom_line = border_color(config.left_bottom_corner + config.bottom_border * (width + 2) + config.right_bottom_corner) output_lines = list(itertools.chain((top_line, ), map(add_border, lines), (bottom_line, ))) r = fsarray(output_lines[:min(rows - 1, len(output_lines) - 1)] + output_lines[-1:]) return r def paint_last_events(rows, columns, names, config): if not names: return fsarray([]) width = min(max(len(name) for name in names), columns - 2) output_lines = [] output_lines.append(config.left_top_corner + config.top_border * width + config.right_top_corner) for name in reversed(names[max(0, len(names) - (rows - 2)):]): output_lines.append(config.left_border + name[:width].center(width) + config.right_border) output_lines.append(config.left_bottom_corner + config.bottom_border * width + config.right_bottom_corner) return fsarray(output_lines) def paint_statusbar(rows, columns, msg, config): func = func_for_letter(config.color_scheme['main']) return fsarray([func(msg.ljust(columns))[:columns]]) bpython-0.17.1/bpython/curtsiesfrontend/coderunner.py0000644000175100017510000001774113240410473023055 0ustar useruser00000000000000# encoding: utf-8 """For running Python code that could interrupt itself at any time in order to, for example, ask for a read on stdin, or a write on stdout The CodeRunner spawns a greenlet to run code in, and that code can suspend its own execution to ask the main greenlet to refresh the display or get information. Greenlets are basically threads that can explicitly switch control to each other. You can replace the word "greenlet" with "thread" in these docs if that makes more sense to you. """ import code import signal import greenlet import logging from bpython._py3compat import py3 from bpython.config import getpreferredencoding logger = logging.getLogger(__name__) class SigintHappened(object): """If this class is returned, a SIGINT happened while the main greenlet""" class SystemExitFromCodeRunner(SystemExit): """If this class is returned, a SystemExit happened while in the code greenlet""" class RequestFromCodeRunner(object): """Message from the code runner""" class Wait(RequestFromCodeRunner): """Running code would like the main loop to run for a bit""" class Refresh(RequestFromCodeRunner): """Running code would like the main loop to refresh the display""" class Done(RequestFromCodeRunner): """Running code is done running""" class Unfinished(RequestFromCodeRunner): """Source code wasn't executed because it wasn't fully formed""" class SystemExitRequest(RequestFromCodeRunner): """Running code raised a SystemExit""" def __init__(self, args): self.args = args class CodeRunner(object): """Runs user code in an interpreter. Running code requests a refresh by calling request_from_main_context(force_refresh=True), which suspends execution of the code and switches back to the main greenlet After load_code() is called with the source code to be run, the run_code() method should be called to start running the code. The running code may request screen refreshes and user input by calling request_from_main_context. When this are called, the running source code cedes control, and the current run_code() method call returns. The return value of run_code() determines whether the method ought to be called again to complete execution of the source code. Once the screen refresh has occurred or the requested user input has been gathered, run_code() should be called again, passing in any requested user input. This continues until run_code returns Done. The code greenlet is responsible for telling the main greenlet what it wants returned in the next run_code call - CodeRunner just passes whatever is passed in to run_code(for_code) to the code greenlet """ def __init__(self, interp=None, request_refresh=lambda: None): """ interp is an interpreter object to use. By default a new one is created. request_refresh is a function that will be called each time the running code asks for a refresh - to, for example, update the screen. """ self.interp = interp or code.InteractiveInterpreter() self.source = None self.main_context = greenlet.getcurrent() self.code_context = None self.request_refresh = request_refresh # waiting for response from main thread self.code_is_waiting = False # sigint happened while in main thread self.sigint_happened_in_main_context = False self.orig_sigint_handler = None @property def running(self): """Returns greenlet if code has been loaded greenlet has been started""" return self.source and self.code_context def load_code(self, source): """Prep code to be run""" assert self.source is None, "you shouldn't load code when some is " \ "already running" self.source = source self.code_context = None def _unload_code(self): """Called when done running code""" self.source = None self.code_context = None self.code_is_waiting = False def run_code(self, for_code=None): """Returns Truthy values if code finishes, False otherwise if for_code is provided, send that value to the code greenlet if source code is complete, returns "done" if source code is incomplete, returns "unfinished" """ if self.code_context is None: assert self.source is not None self.code_context = greenlet.greenlet(self._blocking_run_code) self.orig_sigint_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, self.sigint_handler) request = self.code_context.switch() else: assert self.code_is_waiting self.code_is_waiting = False signal.signal(signal.SIGINT, self.sigint_handler) if self.sigint_happened_in_main_context: self.sigint_happened_in_main_context = False request = self.code_context.switch(SigintHappened) else: request = self.code_context.switch(for_code) logger.debug('request received from code was %r', request) if not isinstance(request, RequestFromCodeRunner): raise ValueError("Not a valid value from code greenlet: %r" % request) if isinstance(request, (Wait, Refresh)): self.code_is_waiting = True if isinstance(request, Refresh): self.request_refresh() return False elif isinstance(request, (Done, Unfinished)): self._unload_code() signal.signal(signal.SIGINT, self.orig_sigint_handler) self.orig_sigint_handler = None return request elif isinstance(request, SystemExitRequest): self._unload_code() raise SystemExitFromCodeRunner(request.args) def sigint_handler(self, *args): """SIGINT handler to use while code is running or request being fulfilled""" if greenlet.getcurrent() is self.code_context: logger.debug('sigint while running user code!') raise KeyboardInterrupt() else: logger.debug('sigint while fulfilling code request sigint handler ' 'running!') self.sigint_happened_in_main_context = True def _blocking_run_code(self): try: unfinished = self.interp.runsource(self.source) except SystemExit as e: return SystemExitRequest(e.args) return Unfinished() if unfinished else Done() def request_from_main_context(self, force_refresh=False): """Return the argument passed in to .run_code(for_code) Nothing means calls to run_code must be... ??? """ if force_refresh: value = self.main_context.switch(Refresh()) else: value = self.main_context.switch(Wait()) if value is SigintHappened: raise KeyboardInterrupt() return value class FakeOutput(object): def __init__(self, coderunner, on_write, fileno=1): """Fakes sys.stdout or sys.stderr on_write should always take unicode fileno should be the fileno that on_write will output to (e.g. 1 for standard output). """ self.coderunner = coderunner self.on_write = on_write self.real_fileno = fileno def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): s = s.decode(getpreferredencoding(), 'ignore') self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_context(force_refresh=True) # Some applications which use curses require that sys.stdout # have a method called fileno. One example is pwntools. This # is not a widespread issue, but is annoying. def fileno(self): return self.real_fileno def writelines(self, l): for s in l: self.write(s) def flush(self): pass def isatty(self): return True bpython-0.17.1/bpython/curtsiesfrontend/interpreter.py0000644000175100017510000001044113240407731023245 0ustar useruser00000000000000# encoding: utf-8 import sys from six import iteritems, text_type from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation from pygments.token import Whitespace from pygments.formatter import Formatter from pygments.lexers import get_lexer_by_name from bpython.curtsiesfrontend.parse import parse from bpython.repl import Interpreter as ReplInterpreter from bpython.config import getpreferredencoding default_colors = { Generic.Error: 'R', Keyword: 'd', Name: 'c', Name.Builtin: 'g', Comment: 'b', String: 'm', Error: 'r', Literal: 'd', Number: 'M', Number.Integer: 'd', Operator: 'd', Punctuation: 'd', Token: 'd', Whitespace: 'd', Token.Punctuation.Parenthesis: 'R', Name.Function: 'd', Name.Class: 'd' } class BPythonFormatter(Formatter): """This is subclassed from the custom formatter for bpython. Its format() method receives the tokensource and outfile params passed to it from the Pygments highlight() method and slops them into the appropriate format string as defined above, then writes to the outfile object the final formatted string. This does not write real strings. It writes format string (FmtStr) objects. See the Pygments source for more info; it's pretty straightforward.""" def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in iteritems(color_scheme): self.f_strings[k] = '\x01%s' % (v,) Formatter.__init__(self, **options) def format(self, tokensource, outfile): o = '' for token, text in tokensource: while token not in self.f_strings: token = token.parent o += "%s\x03%s\x04" % (self.f_strings[token], text) outfile.write(parse(o.rstrip())) class Interp(ReplInterpreter): def __init__(self, locals=None, encoding=None): """Constructor. We include an argument for the outfile to pass to the formatter for it to write to. """ ReplInterpreter.__init__(self, locals, encoding) # typically changed after being instantiated # but used when interpreter used corresponding REPL def write(err_line): """Default stderr handler for tracebacks Accepts FmtStrs so interpreters can output them""" sys.stderr.write(text_type(err_line)) self.write = write self.outfile = self def writetb(self, lines): tbtext = ''.join(lines) lexer = get_lexer_by_name("pytb") self.format(tbtext, lexer) # TODO for tracebacks get_lexer_by_name("pytb", stripall=True) def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ('d')}) tokens = list(lexer.get_tokens(tbtext)) no_format_mode = False cur_line = [] for token, text in tokens: if text.endswith('\n'): cur_line.append((token, text)) if no_format_mode: traceback_code_formatter.format(cur_line, self.outfile) no_format_mode = False else: traceback_informative_formatter.format(cur_line, self.outfile) cur_line = [] elif text == ' ' and cur_line == []: no_format_mode = True cur_line.append((token, text)) else: cur_line.append((token, text)) assert cur_line == [], cur_line def code_finished_will_parse(s, compiler): """Returns a tuple of whether the buffer could be complete and whether it will parse True, True means code block is finished and no predicted parse error True, False means code block is finished because a parse error is predicted False, True means code block is unfinished False, False isn't possible - an predicted error makes code block done""" try: finished = bool(compiler(s)) code_will_parse = True except (ValueError, SyntaxError, OverflowError): finished = True code_will_parse = False return finished, code_will_parse bpython-0.17.1/bpython/curtsiesfrontend/__init__.py0000644000175100017510000000000013240407731022427 0ustar useruser00000000000000bpython-0.17.1/bpython/curtsiesfrontend/sitefix.py0000644000175100017510000000061513240407731022357 0ustar useruser00000000000000# encoding: utf-8 import sys from six.moves import builtins def resetquit(builtins): """Redefine builtins 'quit' and 'exit' not so close stdin """ def __call__(self, code=None): raise SystemExit(code) __call__.__name__ = 'FakeQuitCall' builtins.quit.__class__.__call__ = __call__ def monkeypatch_quit(): if 'site' in sys.modules: resetquit(builtins) bpython-0.17.1/bpython/curtsiesfrontend/parse.py0000644000175100017510000000436213240407731022021 0ustar useruser00000000000000# coding: utf-8 from __future__ import unicode_literals from functools import partial import re from bpython.lazyre import LazyReCompile from curtsies.termformatconstants import FG_COLORS, BG_COLORS, colors from curtsies.formatstring import fmtstr, FmtStr cnames = dict(zip('krgybmcwd', colors + ('default',))) def func_for_letter(l, default='k'): """Returns FmtStr constructor for a bpython-style color code""" if l == 'd': l = default elif l == 'D': l = default.upper() return partial(fmtstr, fg=cnames[l.lower()], bold=l.isupper()) def color_for_letter(l, default='k'): if l == 'd': l = default return cnames[l.lower()] def parse(s): """Returns a FmtStr object from a bpython-formatted colored string""" rest = s stuff = [] while True: if not rest: break start, rest = peel_off_string(rest) stuff.append(start) return (sum((fs_from_match(d) for d in stuff[1:]), fs_from_match(stuff[0])) if len(stuff) > 0 else FmtStr()) def fs_from_match(d): atts = {} if d['fg']: # this isn't according to spec as I understand it if d['fg'].isupper(): d['bold'] = True # TODO figure out why boldness isn't based on presence of \x02 color = cnames[d['fg'].lower()] if color != 'default': atts['fg'] = FG_COLORS[color] if d['bg']: if d['bg'] == 'I': # hack for finding the "inverse" color = colors[(colors.index(color) + (len(colors) // 2)) % len(colors)] else: color = cnames[d['bg'].lower()] if color != 'default': atts['bg'] = BG_COLORS[color] if d['bold']: atts['bold'] = True return fmtstr(d['string'], **atts) peel_off_string_re = LazyReCompile( r"""(?P\x01 (?P[krgybmcwdKRGYBMCWD]?) (?P[krgybmcwdKRGYBMCWDI]?)?) (?P\x02?) \x03 (?P[^\x04]*) \x04 (?P.*) """, re.VERBOSE | re.DOTALL) def peel_off_string(s): m = peel_off_string_re.match(s) assert m, repr(s) d = m.groupdict() rest = d['rest'] del d['rest'] return d, rest bpython-0.17.1/bpython/curtsiesfrontend/preprocess.py0000644000175100017510000000271713240407731023076 0ustar useruser00000000000000# encoding: utf-8 """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" from bpython.lazyre import LazyReCompile # TODO specifically catch IndentationErrors instead of any syntax errors indent_empty_lines_re = LazyReCompile(r'\s*') tabs_to_spaces_re = LazyReCompile(r'^\t+') def indent_empty_lines(s, compiler): """Indents blank lines that would otherwise cause early compilation Only really works if starting on a new line""" lines = s.split('\n') ends_with_newline = False if lines and not lines[-1]: ends_with_newline = True lines.pop() result_lines = [] for p_line, line, n_line in zip([''] + lines[:-1], lines, lines[1:] + ['']): if len(line) == 0: p_indent = indent_empty_lines_re.match(p_line).group() n_indent = indent_empty_lines_re.match(n_line).group() result_lines.append(min([p_indent, n_indent], key=len) + line) else: result_lines.append(line) return '\n'.join(result_lines) + ('\n' if ends_with_newline else '') def leading_tabs_to_spaces(s): lines = s.split('\n') result_lines = [] def tab_to_space(m): return len(m.group()) * 4 * ' ' for line in lines: result_lines.append(tabs_to_spaces_re.sub(tab_to_space, line)) return '\n'.join(result_lines) def preprocess(s, compiler): return indent_empty_lines(leading_tabs_to_spaces(s), compiler) bpython-0.17.1/bpython/curtsiesfrontend/repl.py0000644000175100017510000022245313240410473021651 0ustar useruser00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import contextlib import errno import greenlet import logging import os import re import signal import subprocess import sys import tempfile import threading import time import unicodedata from six.moves import range from pygments import format from bpython._py3compat import PythonLexer from pygments.formatters import TerminalFormatter import blessings import curtsies from curtsies import FSArray, fmtstr, FmtStr, Termmode from curtsies import fmtfuncs from curtsies import events import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound from bpython.config import (Struct, loadini, default_config_path, getpreferredencoding) from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ from bpython._py3compat import py3 from bpython.pager import get_pager_command from bpython.curtsiesfrontend import replpainter as paint from bpython.curtsiesfrontend import sitefix from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler from bpython.curtsiesfrontend.interaction import StatusBar from bpython.curtsiesfrontend.manual_readline import edit_keys from bpython.curtsiesfrontend import events as bpythonevents from bpython.curtsiesfrontend.parse import parse as bpythonparse from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter from bpython.curtsiesfrontend.preprocess import preprocess from bpython.curtsiesfrontend.interpreter import (Interp, code_finished_will_parse) from curtsies.configfile_keynames import keymap as key_dispatch if not py3: import imp import pkgutil logger = logging.getLogger(__name__) INCONSISTENT_HISTORY_MSG = "#<---History inconsistent with output shown--->" CONTIGUITY_BROKEN_MSG = "#<---History contiguity broken by rewind--->" HELP_MESSAGE = """ Thanks for using bpython! See http://bpython-interpreter.org/ for more information and http://docs.bpython-interpreter.org/ for docs. Please report issues at https://github.com/bpython/bpython/issues Features: Try using undo ({config.undo_key})! Edit the current line ({config.edit_current_block_key}) or the entire session ({config.external_editor_key}) in an external editor. (currently {config.editor}) Save sessions ({config.save_key}) or post them to pastebins ({config.pastebin_key})! Current pastebin helper: {config.pastebin_helper} Reload all modules and rerun session ({config.reimport_key}) to test out changes to a module. Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the current session when a module you've imported is modified. bpython -i your_script.py runs a file in interactive mode bpython -t your_script.py pastes the contents of a file into the session A config file at {config_file_location} customizes keys and behavior of bpython. You can also set which pastebin helper and which external editor to use. See {example_config_url} for an example config file. Press {config.edit_config_key} to edit this config file. """ EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config' EDIT_SESSION_HEADER = """### current bpython session - make changes and save to reevaluate session. ### lines beginning with ### will be ignored. ### To return to bpython without reevaluating make no changes to this file ### or save an empty file. """ # more than this many events will be assumed to be a true paste event, # i.e. control characters like '' will be stripped MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # This is needed for is_nop and should be removed once is_nop is fixed. if py3: unicode = str if sys.version_info >= (3, 4): def is_main_thread(): return threading.main_thread() == threading.current_thread() else: def is_main_thread(): return isinstance(threading.current_thread(), threading._MainThread) class FakeStdin(object): """The stdin object user code will reference In user code, sys.stdin.read() asks the user for interactive input, so this class returns control to the UI to get that input.""" def __init__(self, coderunner, repl, configured_edit_keys=None): self.coderunner = coderunner self.repl = repl self.has_focus = False # whether FakeStdin receives keypress events self.current_line = '' self.cursor_offset = 0 self.old_num_lines = 0 self.readline_results = [] if configured_edit_keys: self.rl_char_sequences = configured_edit_keys else: self.rl_char_sequences = edit_keys def process_event(self, e): assert self.has_focus logger.debug('fake input processing event %r', e) if isinstance(e, events.PasteEvent): for ee in e.events: if ee not in self.rl_char_sequences: self.add_input_character(ee) elif e in self.rl_char_sequences: self.cursor_offset, self.current_line = self.rl_char_sequences[e]( self.cursor_offset, self.current_line) elif isinstance(e, events.SigIntEvent): self.coderunner.sigint_happened_in_main_context = True self.has_focus = False self.current_line = '' self.cursor_offset = 0 self.repl.run_code_and_maybe_finish() elif e in ("",): self.get_last_word() elif e in [""]: pass elif e in ['']: if self.current_line == '': self.repl.send_to_stdin('\n') self.has_focus = False self.current_line = '' self.cursor_offset = 0 self.repl.run_code_and_maybe_finish(for_code='') else: pass elif e in ["\n", "\r", "", ""]: line = self.current_line self.repl.send_to_stdin(line + '\n') self.has_focus = False self.current_line = '' self.cursor_offset = 0 self.repl.run_code_and_maybe_finish(for_code=line + '\n') else: # add normal character self.add_input_character(e) if self.current_line.endswith(("\n", "\r")): pass else: self.repl.send_to_stdin(self.current_line) def add_input_character(self, e): if e == '': e = ' ' if e.startswith('<') and e.endswith('>'): return assert len(e) == 1, 'added multiple characters: %r' % e logger.debug('adding normal char %r to current line', e) c = e if py3 else e.encode('utf8') self.current_line = (self.current_line[:self.cursor_offset] + c + self.current_line[self.cursor_offset:]) self.cursor_offset += 1 def readline(self): self.has_focus = True self.repl.send_to_stdin(self.current_line) value = self.coderunner.request_from_main_context() self.readline_results.append(value) return value def readlines(self, size=-1): return list(iter(self.readline, '')) def __iter__(self): return iter(self.readlines()) def isatty(self): return True def flush(self): """Flush the internal buffer. This is a no-op. Flushing stdin doesn't make any sense anyway.""" def write(self, value): # XXX IPython expects sys.stdin.write to exist, there will no doubt be # others, so here's a hack to keep them happy raise IOError(errno.EBADF, "sys.stdin is read-only") def close(self): # hack to make closing stdin a nop # This is useful for multiprocessing.Process, which does work # for the most part, although output from other processes is # discarded. pass @property def encoding(self): return 'UTF8' # TODO write a read() method? class ReevaluateFakeStdin(object): """Stdin mock used during reevaluation (undo) so raw_inputs don't have to be reentered""" def __init__(self, fakestdin, repl): self.fakestdin = fakestdin self.repl = repl self.readline_results = fakestdin.readline_results[:] def readline(self): if self.readline_results: value = self.readline_results.pop(0) else: value = 'no saved input available' self.repl.send_to_stdout(value) return value class ImportLoader(object): def __init__(self, watcher, loader): self.watcher = watcher self.loader = loader def load_module(self, name): module = self.loader.load_module(name) if hasattr(module, '__file__'): self.watcher.track_module(module.__file__) return module if not py3: # Remember that pkgutil.ImpLoader is an old style class. class ImpImportLoader(pkgutil.ImpLoader): def __init__(self, watcher, *args): self.watcher = watcher pkgutil.ImpLoader.__init__(self, *args) def load_module(self, name): module = pkgutil.ImpLoader.load_module(self, name) if hasattr(module, '__file__'): self.watcher.track_module(module.__file__) return module class ImportFinder(object): def __init__(self, watcher, old_meta_path): self.watcher = watcher self.old_meta_path = old_meta_path def find_module(self, fullname, path=None): for finder in self.old_meta_path: loader = finder.find_module(fullname, path) if loader is not None: return ImportLoader(self.watcher, loader) if not py3: # Python 2 does not have the default finders stored in # sys.meta_path. Use imp to perform the actual importing. try: result = imp.find_module(fullname, path) return ImpImportLoader(self.watcher, fullname, *result) except ImportError: return None return None class BaseRepl(BpythonRepl): """Python Repl Reacts to events like - terminal dimensions and change events - keystrokes Behavior altered by - number of scroll downs that were necessary to render array after each display - initial cursor position outputs: - 2D array to be rendered BaseRepl is mostly view-independent state of Repl - but self.width and self.height are important for figuring out how to wrap lines for example. Usually self.width and self.height should be set by receiving a window resize event, not manually set to anything - as long as the first event received is a window resize event, this works fine. Subclasses are responsible for implementing several methods. """ def __init__(self, locals_=None, config=None, banner=None, interp=None, orig_tcattrs=None): """ locals_ is a mapping of locals to pass into the interpreter config is a bpython config.Struct with config attributes banner is a string to display briefly in the status bar interp is an interpreter instance to use original terminal state, useful for shelling out with normal terminal """ logger.debug("starting init") if config is None: config = Struct() loadini(config, default_config_path()) # If creating a new interpreter on undo would be unsafe because initial # state was passed in self.weak_rewind = bool(locals_ or interp) if interp is None: interp = Interp(locals=locals_) interp.write = self.send_to_stderr if banner is None: if config.help_key: banner = (_('Welcome to bpython!') + ' ' + _('Press <%s> for help.') % config.help_key) else: banner = None # only one implemented currently config.autocomplete_mode = autocomplete.SIMPLE if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 1 self.reevaluating = False self.fake_refresh_requested = False self.status_bar = StatusBar('', request_refresh=self.request_refresh, schedule_refresh=self.schedule_refresh) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(BaseRepl, self).__init__(interp, config) self.formatter = BPythonFormatter(config.color_scheme) # overwriting what bpython.Repl put there # interact is called to interact with the status bar, # so we're just using the same object self.interact = self.status_bar # line currently being edited, without ps1 (usually '>>> ') self._current_line = '' # current line of output - stdout and stdin go here self.current_stdouterr_line = '' # lines separated whenever logical line # length goes over what the terminal width # was at the time of original output self.display_lines = [] # this is every line that's been executed; it gets smaller on rewind self.history = [] # formatted version of lines in the buffer kept around so we can # unhighlight parens using self.reprint_line as called by bpython.Repl self.display_buffer = [] # how many times display has been scrolled down # because there wasn't room to display everything self.scroll_offset = 0 # from the left, 0 means first char self._cursor_offset = 0 self.orig_tcattrs = orig_tcattrs self.coderunner = CodeRunner(self.interp, self.request_refresh) # filenos match the backing device for libs that expect it, # but writing to them will do weird things to the display self.stdout = FakeOutput(self.coderunner, self.send_to_stdout, fileno=sys.__stdout__.fileno()) self.stderr = FakeOutput(self.coderunner, self.send_to_stderr, fileno=sys.__stderr__.fileno()) self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) # next paint should clear screen self.request_paint_to_clear_screen = False self.request_paint_to_pad_bottom = 0 # offscreen command yields results different from scrollback buffer self.inconsistent_history = False # history error message has already been displayed self.history_already_messed_up = False # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) self.last_events = [None] * 50 # displays prev events in a column on the right hand side self.presentation_mode = False self.paste_mode = False self.current_match = None self.list_win_visible = False # whether auto reloading active self.watching_files = config.default_autoreload # 'reverse_incremental_search', 'incremental_search' or None self.incr_search_mode = None self.incr_search_target = '' self.original_modules = set(sys.modules.keys()) self.width = None self.height = None self.status_bar.message(banner) self.watcher = ModuleChangedEventHandler([], self.request_reload) if self.watcher and config.default_autoreload: self.watcher.activate() # The methods below should be overridden, but the default implementations # below can be used as well. def get_cursor_vertical_diff(self): """Return how the cursor moved due to a window size change""" return 0 def get_top_usable_line(self): """Return the top line of display that can be rewritten""" return 0 def get_term_hw(self): """Returns the current width and height of the display area.""" return (50, 10) def _schedule_refresh(self, when='now'): """Arrange for the bpython display to be refreshed soon. This method will be called when the Repl wants the display to be refreshed at a known point in the future, and as such it should interrupt a pending request to the user for input. Because the worst-case effect of not refreshing is only having an out of date UI until the user enters input, a default NOP implementation is provided.""" # The methods below must be overridden in subclasses. def _request_refresh(self): """Arrange for the bpython display to be refreshed soon. This method will be called when the Repl wants to refresh the display, but wants control returned to it afterwards. (it is assumed that simply returning from process_event will cause an event refresh) The very next event received by process_event should be a RefreshRequestEvent.""" raise NotImplementedError def _request_reload(self, files_modified=('?',)): """Like request_refresh, but for reload requests events.""" raise NotImplementedError def request_undo(self, n=1): """Like request_refresh, but for undo request events.""" raise NotImplementedError def on_suspend(self): """Will be called on sigtstp. Do whatever cleanup would allow the user to use other programs.""" raise NotImplementedError def after_suspend(self): """Will be called when process foregrounded after suspend. See to it that process_event is called with None to trigger a refresh if not in the middle of a process_event call when suspend happened.""" raise NotImplementedError # end methods that should be overridden in subclass def request_refresh(self): """Request that the bpython display to be refreshed soon.""" if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: self._request_refresh() def request_reload(self, files_modified=()): """Request that a ReloadEvent be passed next into process_event""" if self.watching_files: self._request_reload(files_modified=files_modified) def schedule_refresh(self, when='now'): """Schedule a ScheduledRefreshRequestEvent for when. Such a event should interrupt if blockied waiting for keyboard input""" if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: self._schedule_refresh(when=when) def __enter__(self): self.orig_stdout = sys.stdout self.orig_stderr = sys.stderr self.orig_stdin = sys.stdin sys.stdout = self.stdout sys.stderr = self.stderr sys.stdin = self.stdin self.orig_sigwinch_handler = signal.getsignal(signal.SIGWINCH) self.orig_sigtstp_handler = signal.getsignal(signal.SIGTSTP) if is_main_thread(): # This turns off resize detection and ctrl-z suspension. signal.signal(signal.SIGWINCH, self.sigwinch_handler) signal.signal(signal.SIGTSTP, self.sigtstp_handler) self.orig_meta_path = sys.meta_path if self.watcher: sys.meta_path = [ImportFinder(self.watcher, self.orig_meta_path)] sitefix.monkeypatch_quit() return self def __exit__(self, *args): sys.stdin = self.orig_stdin sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr if is_main_thread(): # This turns off resize detection and ctrl-z suspension. signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) sys.meta_path = self.orig_meta_path def sigwinch_handler(self, signum, frame): old_rows, old_columns = self.height, self.width self.height, self.width = self.get_term_hw() cursor_dy = self.get_cursor_vertical_diff() self.scroll_offset -= cursor_dy logger.info('sigwinch! Changed from %r to %r', (old_rows, old_columns), (self.height, self.width)) logger.info('decreasing scroll offset by %d to %d', cursor_dy, self.scroll_offset) def sigtstp_handler(self, signum, frame): self.scroll_offset = len(self.lines_for_display) self.__exit__() self.on_suspend() os.kill(os.getpid(), signal.SIGTSTP) self.after_suspend() self.__enter__() def clean_up_current_line_for_exit(self): """Called when trying to exit to prep for final paint""" logger.debug('unhighlighting paren for exit') self.cursor_offset = -1 self.unhighlight_paren() # Event handling def process_event(self, e): """Returns True if shutting down, otherwise returns None. Mostly mutates state of Repl object""" logger.debug("processing event %r", e) if isinstance(e, events.Event): return self.process_control_event(e) else: self.last_events.append(e) self.last_events.pop(0) return self.process_key_event(e) def process_control_event(self, e): if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent): # This is a scheduled refresh - it's really just a refresh (so nop) pass elif isinstance(e, bpythonevents.RefreshRequestEvent): logger.info('received ASAP refresh request event') if self.status_bar.has_focus: self.status_bar.process_event(e) else: assert self.coderunner.code_is_waiting self.run_code_and_maybe_finish() elif self.status_bar.has_focus: return self.status_bar.process_event(e) # handles paste events for both stdin and repl elif isinstance(e, events.PasteEvent): ctrl_char = compress_paste_event(e) if ctrl_char is not None: return self.process_event(ctrl_char) with self.in_paste_mode(): # Might not really be a paste, UI might just be lagging if (len(e.events) <= MAX_EVENTS_POSSIBLY_NOT_PASTE and any(not is_simple_event(ee) for ee in e.events)): for ee in e.events: if self.stdin.has_focus: self.stdin.process_event(ee) else: self.process_event(ee) else: simple_events = just_simple_events(e.events) source = preprocess(''.join(simple_events), self.interp.compile) for ee in source: if self.stdin.has_focus: self.stdin.process_event(ee) else: self.process_simple_keypress(ee) elif isinstance(e, bpythonevents.RunStartupFileEvent): try: self.startup() except IOError as e: self.status_bar.message( _('Executing PYTHONSTARTUP failed: %s') % (e, )) elif isinstance(e, bpythonevents.UndoEvent): self.undo(n=e.n) elif self.stdin.has_focus: return self.stdin.process_event(e) elif isinstance(e, events.SigIntEvent): logger.debug('received sigint event') self.keyboard_interrupt() return elif isinstance(e, bpythonevents.ReloadEvent): if self.watching_files: self.clear_modules_and_reevaluate() self.status_bar.message( _('Reloaded at %s because %s modified.') % ( time.strftime('%X'), ' & '.join(e.files_modified))) else: raise ValueError("Don't know how to handle event type: %r" % e) def process_key_event(self, e): # To find the curtsies name for a keypress, try # python -m curtsies.events if self.status_bar.has_focus: return self.status_bar.process_event(e) if self.stdin.has_focus: return self.stdin.process_event(e) if (e in (key_dispatch[self.config.right_key] + key_dispatch[self.config.end_of_line_key] + ("",)) and self.config.curtsies_right_arrow_completion and self.cursor_offset == len(self.current_line)): self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) elif e in ("",) + key_dispatch[self.config.up_one_line_key]: self.up_one_line() elif e in ("",) + key_dispatch[self.config.down_one_line_key]: self.down_one_line() elif e in ("",): self.on_control_d() elif e in ("",): self.operate_and_get_next() elif e in ("",): self.get_last_word() elif e in key_dispatch[self.config.reverse_incremental_search_key]: self.incremental_search(reverse=True) elif e in key_dispatch[self.config.incremental_search_key]: self.incremental_search() elif (e in (("",) + key_dispatch[self.config.backspace_key]) and self.incr_search_mode): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) elif e in self.edit_keys.simple_edits: self.cursor_offset, self.current_line = self.edit_keys.call( e, cursor_offset=self.cursor_offset, line=self.current_line, cut_buffer=self.cut_buffer) elif e in key_dispatch[self.config.cut_to_buffer_key]: self.cut_to_buffer() elif e in key_dispatch[self.config.reimport_key]: self.clear_modules_and_reevaluate() elif e in key_dispatch[self.config.toggle_file_watch_key]: return self.toggle_file_watch() elif e in key_dispatch[self.config.clear_screen_key]: self.request_paint_to_clear_screen = True elif e in key_dispatch[self.config.show_source_key]: self.show_source() elif e in key_dispatch[self.config.help_key]: self.pager(self.help_text()) elif e in key_dispatch[self.config.exit_key]: raise SystemExit() elif e in ("\n", "\r", "", "", ""): self.on_enter() elif e == '': # tab self.on_tab() elif e in ("",): self.on_tab(back=True) elif e in key_dispatch[self.config.undo_key]: # ctrl-r for undo self.prompt_undo() elif e in key_dispatch[self.config.save_key]: # ctrl-s for save greenlet.greenlet(self.write2file).switch() elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin greenlet.greenlet(self.pastebin).switch() elif e in key_dispatch[self.config.copy_clipboard_key]: greenlet.greenlet(self.copy2clipboard).switch() elif e in key_dispatch[self.config.external_editor_key]: self.send_session_to_external_editor() elif e in key_dispatch[self.config.edit_config_key]: greenlet.greenlet(self.edit_config).switch() # TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() elif e in [""]: self.incr_search_mode = None elif e in [""]: self.add_normal_character(' ') else: self.add_normal_character(e) def get_last_word(self): previous_word = _last_word(self.rl_history.entry) word = _last_word(self.rl_history.back()) line = self.current_line self._set_current_line(line[:len(line) - len(previous_word)] + word, reset_rl_history=False) self._set_cursor_offset( self.cursor_offset - len(previous_word) + len(word), reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): if self.incr_search_mode is None: self.rl_history.enter(self.current_line) self.incr_search_target = '' else: if self.incr_search_target: line = (self.rl_history.back( False, search=True, target=self.incr_search_target, include_current=include_current) if reverse else self.rl_history.forward( False, search=True, target=self.incr_search_target, include_current=include_current)) self._set_current_line(line, reset_rl_history=False, clear_special_mode=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False, clear_special_mode=False) if reverse: self.incr_search_mode = 'reverse_incremental_search' else: self.incr_search_mode = 'incremental_search' def readline_kill(self, e): func = self.edit_keys[e] self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line) if self.last_events[-2] == e: # consecutive kill commands accumulative if func.kills == 'ahead': self.cut_buffer += cut elif func.kills == 'behind': self.cut_buffer = cut + self.cut_buffer else: raise ValueError("cut value other than 'ahead' or 'behind'") else: self.cut_buffer = cut def on_enter(self, insert_into_history=True, reset_rl_history=True): # so the cursor isn't touching a paren TODO: necessary? self._set_cursor_offset(-1, update_completion=False) if reset_rl_history: self.rl_history.reset() self.history.append(self.current_line) self.push(self.current_line, insert_into_history=insert_into_history) def on_tab(self, back=False): """Do something on tab key taken from bpython.cli Does one of the following: 1) add space to move up to the next %4==0 column 2) complete the current word with characters common to all completions 3) select the first or last match 4) select the next or previous match if already have a match """ def only_whitespace_left_of_cursor(): """returns true if all characters before cursor are whitespace""" return not self.current_line[:self.cursor_offset].strip() logger.debug('self.matches_iter.matches:%r', self.matches_iter.matches) if only_whitespace_left_of_cursor(): front_ws = (len(self.current_line[:self.cursor_offset]) - len(self.current_line[:self.cursor_offset].lstrip())) to_add = 4 - (front_ws % self.config.tab_length) for unused in range(to_add): self.add_normal_character(' ') return # run complete() if we don't already have matches if len(self.matches_iter.matches) == 0: self.list_win_visible = self.complete(tab=True) # 3. check to see if we can expand the current word if self.matches_iter.is_cseq(): cursor_and_line = self.matches_iter.substitute_cseq() self._cursor_offset, self._current_line = cursor_and_line # using _current_line so we don't trigger a completion reset if not self.matches_iter.matches: self.list_win_visible = self.complete() elif self.matches_iter.matches: self.current_match = (back and self.matches_iter.previous() or next(self.matches_iter)) cursor_and_line = self.matches_iter.cur_line() self._cursor_offset, self._current_line = cursor_and_line # using _current_line so we don't trigger a completion reset self.list_win_visible = True def on_control_d(self): if self.current_line == '': raise SystemExit() else: self.current_line = (self.current_line[:self.cursor_offset] + self.current_line[(self.cursor_offset + 1):]) def cut_to_buffer(self): self.cut_buffer = self.current_line[self.cursor_offset:] self.current_line = self.current_line[:self.cursor_offset] def yank_from_buffer(self): pass def operate_and_get_next(self): # If we have not navigated back in history # ctrl+o will have the same effect as enter self.on_enter(reset_rl_history=False) def up_one_line(self): self.rl_history.enter(self.current_line) self._set_current_line(tabs_to_spaces(self.rl_history.back( False, search=self.config.curtsies_right_arrow_completion)), update_completion=False, reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def down_one_line(self): self.rl_history.enter(self.current_line) self._set_current_line(tabs_to_spaces(self.rl_history.forward( False, search=self.config.curtsies_right_arrow_completion)), update_completion=False, reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def process_simple_keypress(self, e): # '\n' needed for pastes if e in ("", "", "", "\n", "\r"): self.on_enter() while self.fake_refresh_requested: self.fake_refresh_requested = False self.process_event(bpythonevents.RefreshRequestEvent()) elif isinstance(e, events.Event): pass # ignore events elif e == '': self.add_normal_character(' ') else: self.add_normal_character(e) def send_current_block_to_external_editor(self, filename=None): text = self.send_to_external_editor(self.get_current_block()) lines = [line for line in text.split('\n')] while lines and not lines[-1].split(): lines.pop() events = '\n'.join(lines + ([''] if len(lines) == 1 else ['', ''])) self.clear_current_block() with self.in_paste_mode(): for e in events: self.process_simple_keypress(e) self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): for_editor = EDIT_SESSION_HEADER for_editor += '\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else line[len(self.ps2):] if line.startswith(self.ps2) else '### ' + line for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) if text == for_editor: self.status_bar.message( _('Session not reevaluated because it was not edited')) return lines = text.split('\n') if not lines[-1].strip(): lines.pop() # strip last line if empty if lines[-1].startswith('### '): current_line = lines[-1][4:] else: current_line = '' from_editor = [line for line in lines if line[:3] != '###'] if all(not line.strip() for line in from_editor): self.status_bar.message( _('Session not reevaluated because saved file was blank')) return source = preprocess('\n'.join(from_editor), self.interp.compile) lines = source.split('\n') self.history = lines self.reevaluate(insert_into_history=True) self.current_line = current_line self.cursor_offset = len(self.current_line) self.status_bar.message(_('Session edited and reevaluated')) def clear_modules_and_reevaluate(self): if self.watcher: self.watcher.reset() cursor, line = self.cursor_offset, self.current_line 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.') % (time.strftime('%X'), )) def toggle_file_watch(self): if self.watcher: if self.watching_files: msg = _("Auto-reloading deactivated.") self.status_bar.message(msg) self.watcher.deactivate() self.watching_files = False else: msg = _("Auto-reloading active, watching for file changes...") self.status_bar.message(msg) self.watching_files = True self.watcher.activate() else: self.status_bar.message(_('Auto-reloading not available because ' 'watchdog not installed.')) # Handler Helpers def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return if self.incr_search_mode: print("Incr search mode") self.add_to_incremental_search(char) else: self._set_current_line((self.current_line[:self.cursor_offset] + char + self.current_line[self.cursor_offset:]), update_completion=False, reset_rl_history=False, clear_special_mode=False) self.cursor_offset += 1 if (self.config.cli_trim_prompts and self.current_line.startswith(self.ps1)): self.current_line = self.current_line[4:] self.cursor_offset = max(0, self.cursor_offset - 4) def add_to_incremental_search(self, char=None, backspace=False): """Modify the current search term while in incremental search. The only operations allowed in incremental search mode are adding characters and backspacing.""" if char is None and not backspace: raise ValueError("must provide a char or set backspace to True") if backspace: self.incr_search_target = self.incr_search_target[:-1] else: self.incr_search_target += char if self.incr_search_mode == 'reverse_incremental_search': self.incremental_search(reverse=True, include_current=True) elif self.incr_search_mode == 'incremental_search': self.incremental_search(include_current=True) else: raise ValueError('add_to_incremental_search not in a special mode') def update_completion(self, tab=False): """Update visible docstring and matches and box visibility""" # Update autocomplete info; self.matches_iter and self.funcprops # Should be called whenever the completion box might need to appear # or disappear; whenever current line or cursor offset changes, # unless this happened via selecting a match self.current_match = None self.list_win_visible = self.complete(tab) def predicted_indent(self, line): # TODO get rid of this! It's repeated code! Combine with Repl. logger.debug('line is %r', line) indent = len(re.match(r'[ ]*', line).group()) if line.endswith(':'): indent = max(0, indent + self.config.tab_length) elif line and line.count(' ') == len(line): indent = max(0, indent - self.config.tab_length) elif (line and ':' not in line and line.strip().startswith( ('return', 'pass', 'raise', 'yield'))): indent = max(0, indent - self.config.tab_length) logger.debug('indent we found was %s', indent) return indent def push(self, line, insert_into_history=True): """Push a line of code onto the buffer, start running the buffer If the interpreter successfully runs the code, clear the buffer """ if self.paste_mode: self.saved_indent = 0 else: self.saved_indent = self.predicted_indent(line) if self.config.syntax: display_line = bpythonparse(format( self.tokenize(line), self.formatter)) # self.tokenize requires that the line not be in self.buffer yet logger.debug('display line being pushed to buffer: %r -> %r', line, display_line) self.display_buffer.append(display_line) else: self.display_buffer.append(fmtstr(line)) if insert_into_history: self.insert_into_history(line) self.buffer.append(line) code_to_run = '\n'.join(self.buffer) logger.debug('running %r in interpreter', self.buffer) c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer), self.interp.compile) self.saved_predicted_parse_error = not code_will_parse if c: logger.debug('finished - buffer cleared') self.cursor_offset = 0 self.display_lines.extend(self.display_buffer_lines) self.display_buffer = [] self.buffer = [] self.coderunner.load_code(code_to_run) self.run_code_and_maybe_finish() def run_code_and_maybe_finish(self, for_code=None): r = self.coderunner.run_code(for_code=for_code) if r: logger.debug("----- Running finish command stuff -----") logger.debug("saved_indent: %r", self.saved_indent) err = self.saved_predicted_parse_error self.saved_predicted_parse_error = False indent = self.saved_indent if err: indent = 0 if self.rl_history.index == 0: self._set_current_line(' ' * indent, update_completion=True) else: self._set_current_line(self.rl_history.entries[-self.rl_history.index], reset_rl_history=False) self.cursor_offset = len(self.current_line) def keyboard_interrupt(self): # TODO factor out the common cleanup from running a line self.cursor_offset = -1 self.unhighlight_paren() self.display_lines.extend(self.display_buffer_lines) self.display_lines.extend(paint.display_linize( self.current_cursor_line, self.width)) self.display_lines.extend(paint.display_linize( "KeyboardInterrupt", self.width)) self.clear_current_block(remove_from_history=False) def unhighlight_paren(self): """Modify line in self.display_buffer to unhighlight a paren if possible. self.highlighted_paren should be a line in ? """ if self.highlighted_paren is not None and self.config.syntax: lineno, saved_tokens = self.highlighted_paren if lineno == len(self.display_buffer): # then this is the current line, so don't worry about it return self.highlighted_paren = None logger.debug('trying to unhighlight a paren on line %r', lineno) logger.debug('with these tokens: %r', saved_tokens) new = bpythonparse(format(saved_tokens, self.formatter)) self.display_buffer[lineno] = self.display_buffer[lineno] \ .setslice_with_length(0, len(new), new, len(self.display_buffer[lineno])) def clear_current_block(self, remove_from_history=True): self.display_buffer = [] if remove_from_history: for unused in self.buffer: self.history.pop() self.buffer = [] self.cursor_offset = 0 self.saved_indent = 0 self.current_line = '' self.cursor_offset = len(self.current_line) def get_current_block(self): return '\n'.join(self.buffer + [self.current_line]) def move_current_stdouterr_line_up(self): """Append self.current_stdouterr_line to self.display_lines then clean it.""" self.display_lines.extend(paint.display_linize( self.current_stdouterr_line, self.width)) self.current_stdouterr_line = '' def send_to_stdout(self, output): """Send unicode string to Repl stdout""" if not output: return lines = output.split('\n') if all(not line for line in lines): # If the string consist only of newline characters, # str.split returns one more empty strings. lines = lines[:-1] logger.debug('display_lines: %r', self.display_lines) if lines[0]: self.current_stdouterr_line += lines[0] else: self.move_current_stdouterr_line_up() if len(lines) > 1: self.display_lines.extend(paint.display_linize( self.current_stdouterr_line, self.width, blank_line=True)) self.display_lines.extend( sum((paint.display_linize(line, self.width, blank_line=True) for line in lines[1:-1]), [])) self.current_stdouterr_line = lines[-1] logger.debug('display_lines: %r', self.display_lines) def send_to_stderr(self, error): """Send unicode strings or FmtStr to Repl stderr Must be able to handle FmtStrs because interpreter pass in tracebacks already formatted.""" if not error: return lines = error.split('\n') if all(not line for line in lines): # If the string consist only of newline characters, # str.split returns one more empty strings. lines = lines[:-1] if lines[-1]: self.current_stdouterr_line += lines[-1] else: self.move_current_stdouterr_line_up() self.display_lines.extend(sum((paint.display_linize(line, self.width, blank_line=True) for line in lines[:-1]), [])) def send_to_stdin(self, line): if line.endswith('\n'): self.display_lines.extend( paint.display_linize(self.current_output_line, self.width)) self.current_output_line = '' # formatting, output @property def done(self): """Whether the last block is complete - which prompt to use, ps1 or ps2""" return not self.buffer @property def current_line_formatted(self): """The colored current line (no prompt, not wrapped)""" if self.config.syntax: fs = bpythonparse(format(self.tokenize(self.current_line), self.formatter)) if self.incr_search_mode: if self.incr_search_target in self.current_line: fs = fmtfuncs.on_magenta(self.incr_search_target).join( fs.split(self.incr_search_target)) elif (self.rl_history.saved_line and self.rl_history.saved_line in self.current_line): if (self.config.curtsies_right_arrow_completion and self.rl_history.index != 0): fs = fmtfuncs.on_magenta(self.rl_history.saved_line).join( fs.split(self.rl_history.saved_line)) logger.debug('Display line %r -> %r', self.current_line, fs) else: fs = fmtstr(self.current_line) if hasattr(self, 'old_fs') and str(fs) != str(self.old_fs): pass self.old_fs = fs return fs @property def lines_for_display(self): """All display lines (wrapped, colored, with prompts)""" return self.display_lines + self.display_buffer_lines @property def display_buffer_lines(self): """The display lines (wrapped, colored, +prompts) of current buffer""" lines = [] for display_line in self.display_buffer: prompt = func_for_letter(self.config.color_scheme['prompt']) more = func_for_letter(self.config.color_scheme['prompt_more']) display_line = ((more(self.ps2) if lines else prompt(self.ps1)) + display_line) for line in paint.display_linize(display_line, self.width): lines.append(line) return lines @property def display_line_with_prompt(self): """colored line with prompt""" prompt = func_for_letter(self.config.color_scheme['prompt']) more = func_for_letter(self.config.color_scheme['prompt_more']) if self.incr_search_mode == 'reverse_incremental_search': return (prompt('(reverse-i-search)`{}\': '.format( self.incr_search_target)) + self.current_line_formatted) elif self.incr_search_mode == 'incremental_search': return (prompt('(i-search)`%s\': '.format( self.incr_search_target)) + self.current_line_formatted) return ((prompt(self.ps1) if self.done else more(self.ps2)) + self.current_line_formatted) @property def current_cursor_line_without_suggestion(self): """Current line, either output/input or Python prompt + code""" value = (self.current_output_line + ('' if self.coderunner.running else self.display_line_with_prompt)) logger.debug('current cursor line: %r', value) return value @property def current_cursor_line(self): if self.config.curtsies_right_arrow_completion: suggest = func_for_letter( self.config.color_scheme['right_arrow_suggestion']) return (self.current_cursor_line_without_suggestion + suggest(self.current_suggestion)) else: return self.current_cursor_line_without_suggestion @property def current_suggestion(self): if self.current_line: for entry in reversed(self.rl_history.entries): if entry.startswith(self.current_line): return entry[len(self.current_line):] return '' @property def current_output_line(self): """line of output currently being written, and stdin typed""" return self.current_stdouterr_line + self.stdin.current_line @current_output_line.setter def current_output_line(self, value): self.current_stdouterr_line = '' self.stdin.current_line = '\n' def paint(self, about_to_exit=False, user_quit=False, try_preserve_history_height=30, min_infobox_height=5): """Returns an array of min_height or more rows and width columns, plus cursor position Paints the entire screen - ideally the terminal display layer will take a diff and only write to the screen in portions that have changed, but the idea is that we don't need to worry about that here, instead every frame is completely redrawn because less state is cool! try_preserve_history_height is the the number of rows of content that must be visible before the suggestion box scrolls the terminal in order to display more than min_infobox_height rows of suggestions, docs etc. """ # The hairiest function in the curtsies if about_to_exit: # exception to not changing state! self.clean_up_current_line_for_exit() width, min_height = self.width, self.height show_status_bar = ((bool(self.status_bar.should_show_message) or self.status_bar.has_focus) and not self.request_paint_to_pad_bottom) if show_status_bar: # because we're going to tack the status bar on at the end, shoot # for an array one less than the height of the screen min_height -= 1 current_line_start_row = (len(self.lines_for_display) - max(0, self.scroll_offset)) # TODO how is the situation of self.scroll_offset < 0 possible? # or show_status_bar and about_to_exit ? if self.request_paint_to_clear_screen: self.request_paint_to_clear_screen = False arr = FSArray(min_height + current_line_start_row, width) elif self.request_paint_to_pad_bottom: # min_height - 1 for startup banner with python version height = min(self.request_paint_to_pad_bottom, min_height - 1) arr = FSArray(height, width) self.request_paint_to_pad_bottom = 0 else: arr = FSArray(0, width) # TODO test case of current line filling up the whole screen (there # aren't enough rows to show it) current_line = paint.paint_current_line(min_height, width, self.current_cursor_line) # needs to happen before we calculate contents of history because # calculating self.current_cursor_line has the side effect of # unhighlighting parens in buffer def move_screen_up(current_line_start_row): # move screen back up a screen minus a line while current_line_start_row < 0: logger.debug('scroll_offset was %s, current_line_start_row ' 'was %s', self.scroll_offset, current_line_start_row) self.scroll_offset = self.scroll_offset - self.height current_line_start_row = (len(self.lines_for_display) - max(-1, self.scroll_offset)) logger.debug('scroll_offset changed to %s, ' 'current_line_start_row changed to %s', self.scroll_offset, current_line_start_row) return current_line_start_row if self.inconsistent_history and not self.history_already_messed_up: logger.debug(INCONSISTENT_HISTORY_MSG) self.history_already_messed_up = True msg = INCONSISTENT_HISTORY_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] current_line_start_row += 1 # for the message # to make up for the scroll that will be received after the # scrolls are rendered down a line self.scroll_offset -= 1 current_line_start_row = move_screen_up(current_line_start_row) logger.debug('current_line_start_row: %r', current_line_start_row) history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) arr[1:history.height + 1, :history.width] = history if arr.height <= min_height: # force scroll down to hide broken history message arr[min_height, 0] = ' ' elif current_line_start_row < 0: # if current line trying to be drawn off the top of the screen logger.debug(CONTIGUITY_BROKEN_MSG) msg = CONTIGUITY_BROKEN_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] current_line_start_row = move_screen_up(current_line_start_row) history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) arr[1:history.height + 1, :history.width] = history if arr.height <= min_height: # force scroll down to hide broken history message arr[min_height, 0] = ' ' else: assert current_line_start_row >= 0 logger.debug("no history issues. start %i", current_line_start_row) history = paint.paint_history(current_line_start_row, width, self.lines_for_display) arr[:history.height, :history.width] = history self.inconsistent_history = False if user_quit: # quit() or exit() in interp current_line_start_row = (current_line_start_row - current_line.height) logger.debug("---current line row slice %r, %r", current_line_start_row, current_line_start_row + current_line.height) logger.debug("---current line col slice %r, %r", 0, current_line.width) arr[current_line_start_row:(current_line_start_row + current_line.height), 0:current_line.width] = current_line if current_line.height > min_height: return arr, (0, 0) # short circuit, no room for infobox lines = paint.display_linize(self.current_cursor_line + 'X', width) # extra character for space for the cursor current_line_end_row = current_line_start_row + len(lines) - 1 current_line_height = current_line_end_row - current_line_start_row if self.stdin.has_focus: cursor_row, cursor_column = divmod( len(self.current_stdouterr_line) + self.stdin.cursor_offset, width) assert cursor_column >= 0, cursor_column elif self.coderunner.running: # TODO does this ever happen? cursor_row, cursor_column = divmod( (len(self.current_cursor_line_without_suggestion) + self.cursor_offset), width) assert cursor_column >= 0, ( cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset) else: cursor_row, cursor_column = divmod( (len(self.current_cursor_line_without_suggestion) - len(self.current_line) + self.cursor_offset), width) assert cursor_column >= 0, ( cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset) cursor_row += current_line_start_row if self.list_win_visible and not self.coderunner.running: logger.debug('infobox display code running') visible_space_above = history.height potential_space_below = min_height - current_line_end_row - 1 visible_space_below = (potential_space_below - self.get_top_usable_line()) if self.config.curtsies_list_above: info_max_rows = max(visible_space_above, visible_space_below) else: # Logic for determining size of completion box # smallest allowed over-full completion box preferred_height = max( # always make infobox at least this height min_infobox_height, # use this value if there's so much space that we can # preserve this try_preserve_history_height rows history min_height - try_preserve_history_height) info_max_rows = min(max(visible_space_below, preferred_height), min_height - current_line_height - 1) infobox = paint.paint_infobox( info_max_rows, int(width * self.config.cli_suggestion_width), self.matches_iter.matches, self.funcprops, self.arg_pos, self.current_match, self.docstring, self.config, self.matches_iter.completer.format if self.matches_iter.completer else None) if (visible_space_below >= infobox.height or not self.config.curtsies_list_above): arr[current_line_end_row + 1:(current_line_end_row + 1 + infobox.height), 0:infobox.width] = infobox else: arr[current_line_start_row - infobox.height: current_line_start_row, 0:infobox.width] = infobox logger.debug('infobox of shape %r added to arr of shape %r', infobox.shape, arr.shape) logger.debug('about to exit: %r', about_to_exit) if show_status_bar: statusbar_row = (min_height if arr.height == min_height else arr.height) if about_to_exit: arr[statusbar_row, :] = FSArray(1, width) else: arr[statusbar_row, :] = paint.paint_statusbar( 1, width, self.status_bar.current_line, self.config) if self.presentation_mode: rows = arr.height columns = arr.width last_key_box = paint.paint_last_events( rows, columns, [events.pp_event(x) for x in self.last_events if x], self.config) arr[arr.height - last_key_box.height:arr.height, arr.width - last_key_box.width:arr.width] = last_key_box if self.config.color_scheme['background'] not in ('d', 'D'): for r in range(arr.height): bg = color_for_letter(self.config.color_scheme['background']) arr[r] = fmtstr(arr[r], bg=bg) logger.debug('returning arr of size %r', arr.shape) logger.debug('cursor pos: %r', (cursor_row, cursor_column)) return arr, (cursor_row, cursor_column) @contextlib.contextmanager def in_paste_mode(self): orig_value = self.paste_mode self.paste_mode = True yield self.paste_mode = orig_value if not self.paste_mode: self.update_completion() def __repr__(self): s = '' s += '<' + repr(type(self)) + '\n' s += " cursor_offset:" + repr(self.cursor_offset) + '\n' s += " num display lines:" + repr(len(self.display_lines)) + '\n' s += " lines scrolled down:" + repr(self.scroll_offset) + '\n' s += '>' return s def _get_current_line(self): return self._current_line def _set_current_line(self, line, update_completion=True, reset_rl_history=True, clear_special_mode=True): if self._current_line == line: return self._current_line = line if self.paste_mode: return if update_completion: self.update_completion() if reset_rl_history: self.rl_history.reset() if clear_special_mode: self.special_mode = None self.unhighlight_paren() current_line = property(_get_current_line, _set_current_line, None, "The current line") def _get_cursor_offset(self): return self._cursor_offset def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): if self._cursor_offset == offset: return if self.paste_mode: self._cursor_offset = offset self.unhighlight_paren() return if reset_rl_history: self.rl_history.reset() if clear_special_mode: self.incr_search_mode = None self._cursor_offset = offset if update_completion: self.update_completion() self.unhighlight_paren() cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, "The current cursor offset from the front of the " "line") def echo(self, msg, redraw=True): """ Notification that redrawing the current line is necessary (we don't care, since we always redraw the whole screen) Supposed to parse and echo a formatted string with appropriate attributes. It's not supposed to update the screen if it's reevaluating the code (as it does with undo).""" logger.debug("echo called with %r" % msg) @property def cpos(self): "many WATs were had - it's the pos from the end of the line back""" return len(self.current_line) - self.cursor_offset def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: self.display_buffer[lineno] = bpythonparse( format(tokens, self.formatter)) def take_back_buffer_line(self): assert len(self.buffer) > 0 if len(self.buffer) == 1: self._cursor_offset = 0 self.current_line = '' else: line = self.buffer[-1] indent = self.predicted_indent(line) self._current_line = indent * ' ' self.cursor_offset = len(self.current_line) self.display_buffer.pop() self.buffer.pop() self.history.pop() def take_back_empty_line(self): assert self.history and not self.history[-1] self.history.pop() self.display_lines.pop() def prompt_undo(self): if self.buffer: return self.take_back_buffer_line() if self.history and not self.history[-1]: return self.take_back_empty_line() def prompt_for_undo(): n = BpythonRepl.prompt_undo(self) if n > 0: self.request_undo(n=n) greenlet.greenlet(prompt_for_undo).switch() def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" if self.watcher: self.watcher.reset() old_logical_lines = self.history old_display_lines = self.display_lines self.history = [] self.display_lines = [] if not self.weak_rewind: self.interp = self.interp.__class__() self.interp.write = self.send_to_stderr self.coderunner.interp = self.interp self.initialize_interp() self.buffer = [] self.display_buffer = [] self.highlighted_paren = None self.process_event(bpythonevents.RunStartupFileEvent()) self.reevaluating = True sys.stdin = ReevaluateFakeStdin(self.stdin, self) for line in old_logical_lines: self._current_line = line self.on_enter(insert_into_history=insert_into_history) while self.fake_refresh_requested: self.fake_refresh_requested = False self.process_event(bpythonevents.RefreshRequestEvent()) sys.stdin = self.stdin self.reevaluating = False num_lines_onscreen = (len(self.lines_for_display) - max(0, self.scroll_offset)) display_lines_offscreen = self.display_lines[:len(self.display_lines) - num_lines_onscreen] old_display_lines_offscreen = old_display_lines[:( len(self.display_lines) - num_lines_onscreen)] logger.debug('old_display_lines_offscreen %s', '|'.join( str(x) for x in old_display_lines_offscreen)) logger.debug(' display_lines_offscreen %s', '|'.join( str(x) for x in display_lines_offscreen)) if ((old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen) and not self.history_already_messed_up): self.inconsistent_history = True logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history) self._cursor_offset = 0 self.current_line = '' def initialize_interp(self): self.coderunner.interp.locals['_repl'] = self self.coderunner.interp.runsource( 'from bpython.curtsiesfrontend._internal ' 'import _Helper') self.coderunner.interp.runsource('help = _Helper(_repl)\n') del self.coderunner.interp.locals['_repl'] del self.coderunner.interp.locals['_Helper'] def getstdout(self): lines = self.lines_for_display + [self.current_line_formatted] s = '\n'.join(x.s if isinstance(x, FmtStr) else x for x in lines) \ if lines else '' return s def focus_on_subprocess(self, args): prev_sigwinch_handler = signal.getsignal(signal.SIGWINCH) try: signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) with Termmode(self.orig_stdin, self.orig_tcattrs): terminal = blessings.Terminal(stream=sys.__stdout__) with terminal.fullscreen(): sys.__stdout__.write(terminal.save) sys.__stdout__.write(terminal.move(0, 0)) sys.__stdout__.flush() p = subprocess.Popen(args, stdin=self.orig_stdin, stderr=sys.__stderr__, stdout=sys.__stdout__) p.wait() sys.__stdout__.write(terminal.restore) sys.__stdout__.flush() finally: signal.signal(signal.SIGWINCH, prev_sigwinch_handler) def pager(self, text): """Runs an external pager on text text must be a unicode""" command = get_pager_command() with tempfile.NamedTemporaryFile() as tmp: tmp.write(text.encode(getpreferredencoding())) tmp.flush() self.focus_on_subprocess(command + [tmp.name]) def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: self.status_bar.message('%s' % (e, )) else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), TerminalFormatter()) self.pager(source) def help_text(self): return self.version_help_text() + '\n' + self.key_help_text() def version_help_text(self): return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + ('using curtsies version %s' % curtsies.__version__) + '\n' + HELP_MESSAGE.format(config_file_location=default_config_path(), example_config_url=EXAMPLE_CONFIG_URL, config=self.config)) def key_help_text(self): NOT_IMPLEMENTED = ('suspend', 'cut to buffer', 'search', 'last output', 'yank from buffer', 'cut to buffer') pairs = [] pairs.append(['complete history suggestion', 'right arrow at end of line']) pairs.append(['previous match with current line', 'up arrow']) for functionality, key in [(attr[:-4].replace('_', ' '), getattr(self.config, attr)) for attr in self.config.__dict__ if attr.endswith('key')]: if functionality in NOT_IMPLEMENTED: key = 'Not Implemented' if key == '': key = 'Disabled' pairs.append([functionality, key]) max_func = max(len(func) for func, key in pairs) return '\n'.join('%s : %s' % (func.rjust(max_func), key) for func, key in pairs) def is_nop(char): return unicodedata.category(unicode(char)) == 'Cc' def tabs_to_spaces(line): return line.replace('\t', ' ') def _last_word(line): return line.split().pop() if line.split() else '' def compress_paste_event(paste_event): """If all events in a paste event are identical and not simple characters, returns one of them Useful for when the UI is running so slowly that repeated keypresses end up in a paste event. If we value not getting delayed and assume the user is holding down a key to produce such frequent key events, it makes sense to drop some of the events. """ if not all(paste_event.events[0] == e for e in paste_event.events): return None event = paste_event.events[0] # basically "is there a special curtsies names for this key?" if len(event) > 1: return event else: return None def just_simple_events(event_list): simple_events = [] for e in event_list: # '\n' necessary for pastes if e in ("", "", "", "\n", "\r"): simple_events.append('\n') elif isinstance(e, events.Event): pass # ignore events elif e == '': simple_events.append(' ') elif len(e) > 1: pass # get rid of etc. else: simple_events.append(e) return simple_events def is_simple_event(e): if isinstance(e, events.Event): return False if e in ("", "", "", "\n", "\r", ""): return True if len(e) > 1: return False else: return True bpython-0.17.1/bpython/__main__.py0000644000175100017510000000236413240407731017026 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import sys if __name__ == '__main__': from .curtsies import main sys.exit(main()) bpython-0.17.1/bpython/keys.py0000644000175100017510000000505313240407731016257 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2008 Simon de Vlieger # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import string from six.moves import range class KeyMap(object): def __init__(self, default=''): self.map = {} self.default = default def __getitem__(self, key): if not key: # Unbound key return self.default elif key in self.map: return self.map[key] else: raise KeyError('Configured keymap (%s)' % key + ' does not exist in bpython.keys') def __delitem__(self, key): del self.map[key] def __setitem__(self, key, value): self.map[key] = value cli_key_dispatch = KeyMap(tuple()) urwid_key_dispatch = KeyMap('') # fill dispatch with letters for c in string.ascii_lowercase: cli_key_dispatch['C-%s' % c] = (chr(string.ascii_lowercase.index(c) + 1), '^%s' % c.upper()) for c in string.ascii_lowercase: urwid_key_dispatch['C-%s' % c] = 'ctrl %s' % c urwid_key_dispatch['M-%s' % c] = 'meta %s' % c # fill dispatch with cool characters cli_key_dispatch['C-['] = (chr(27), '^[') cli_key_dispatch['C-\\'] = (chr(28), '^\\') cli_key_dispatch['C-]'] = (chr(29), '^]') cli_key_dispatch['C-^'] = (chr(30), '^^') cli_key_dispatch['C-_'] = (chr(31), '^_') # fill dispatch with function keys for x in range(1, 13): cli_key_dispatch['F%d' % x] = ('KEY_F(%d)' % x,) for x in range(1, 13): urwid_key_dispatch['F%d' % x] = 'f%d' % x bpython-0.17.1/bpython/pager.py0000644000175100017510000000504713240407731016405 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import curses import errno import os import pydoc import subprocess import sys import shlex from bpython._py3compat import py3 def get_pager_command(default='less -rf'): command = shlex.split(os.environ.get('PAGER', default)) return command def page_internal(data): """A more than dumb pager function.""" if hasattr(pydoc, 'ttypager'): pydoc.ttypager(data) else: sys.stdout.write(data) def page(data, use_internal=False): command = get_pager_command() if not command or use_internal: page_internal(data) else: curses.endwin() try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) if py3 or isinstance(data, unicode): data = data.encode(sys.__stdout__.encoding, 'replace') popen.stdin.write(data) popen.stdin.close() except OSError as e: if e.errno == errno.ENOENT: # pager command not found, fall back to internal pager page_internal(data) return except IOError as e: if e.errno != errno.EPIPE: raise while True: try: popen.wait() except OSError as e: if e.errno != errno.EINTR: raise else: break curses.doupdate() # vim: sw=4 ts=4 sts=4 ai et bpython-0.17.1/bpython/history.py0000644000175100017510000002022713240407731017005 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2009 the bpython authors. # Copyright (c) 2012,2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import unicode_literals, absolute_import import io import os import stat from itertools import islice from six.moves import range from .translations import _ from .filelock import FileLock class History(object): """Stores readline-style history and current place in it""" def __init__(self, entries=None, duplicates=True, hist_size=100): if entries is None: self.entries = [''] else: self.entries = list(entries) # how many lines back in history is currently selected where 0 is the # saved typed line, 1 the prev entered line self.index = 0 # what was on the prompt before using history self.saved_line = '' self.duplicates = duplicates self.hist_size = hist_size def append(self, line): self.append_to(self.entries, line) def append_to(self, entries, line): line = line.rstrip('\n') if line: if not self.duplicates: # remove duplicates try: while True: entries.remove(line) except ValueError: pass entries.append(line) def first(self): """Move back to the beginning of the history.""" if not self.is_at_end: self.index = len(self.entries) return self.entries[-self.index] def back(self, start=True, search=False, target=None, include_current=False): """Move one step back in the history.""" if target is None: target = self.saved_line if not self.is_at_end: if search: self.index += self.find_partial_match_backward(target, include_current) elif start: self.index += self.find_match_backward(target, include_current) else: self.index += 1 return self.entry @property def entry(self): """The current entry, which may be the saved line""" return self.entries[-self.index] if self.index else self.saved_line @property def entries_by_index(self): return list(reversed(self.entries + [self.saved_line])) def find_match_backward(self, search_term, include_current=False): add = 0 if include_current else 1 start = self.index + add for idx, val in enumerate(islice(self.entries_by_index, start, None)): if val.startswith(search_term): return idx + add return 0 def find_partial_match_backward(self, search_term, include_current=False): add = 0 if include_current else 1 start = self.index + add for idx, val in enumerate(islice(self.entries_by_index, start, None)): if search_term in val: return idx + add return 0 def forward(self, start=True, search=False, target=None, include_current=False): """Move one step forward in the history.""" if target is None: target = self.saved_line if self.index > 1: if search: self.index -= self.find_partial_match_forward(target, include_current) elif start: self.index -= self.find_match_forward(target, include_current) else: self.index -= 1 return self.entry else: self.index = 0 return self.saved_line def find_match_forward(self, search_term, include_current=False): add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) for idx in range(end): val = self.entries_by_index[end - 1 - idx] if val.startswith(search_term): return idx + (0 if include_current else 1) return self.index def find_partial_match_forward(self, search_term, include_current=False): add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) for idx in range(end): val = self.entries_by_index[end - 1 - idx] if search_term in val: return idx + add return self.index def last(self): """Move forward to the end of the history.""" if not self.is_at_start: self.index = 0 return self.entries[0] @property def is_at_end(self): return self.index >= len(self.entries) or self.index == -1 @property def is_at_start(self): return self.index == 0 def enter(self, line): if self.index == 0: self.saved_line = line @classmethod def from_filename(cls, filename): history = cls() history.load(filename) return history def reset(self): self.index = 0 self.saved_line = '' def load(self, filename, encoding): with io.open(filename, 'r', encoding=encoding, errors='ignore') as hfile: with FileLock(hfile): self.entries = self.load_from(hfile) def load_from(self, fd): entries = [] for line in fd: self.append_to(entries, line) return entries if len(entries) else [''] def save(self, filename, encoding, lines=0): fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.TRUNC, stat.S_IRUSR | stat.S_IWUSR) with io.open(fd, 'w', encoding=encoding, errors='ignore') as hfile: with FileLock(hfile): self.save_to(hfile, self.entries, lines) def save_to(self, fd, entries=None, lines=0): if entries is None: entries = self.entries for line in entries[-lines:]: fd.write(line) fd.write('\n') def append_reload_and_write(self, s, filename, encoding): if not self.hist_size: return self.append(s) try: fd = os.open(filename, os.O_APPEND | os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR) with io.open(fd, 'a+', encoding=encoding, errors='ignore') as hfile: with FileLock(hfile): # read entries hfile.seek(0, os.SEEK_SET) entries = self.load_from(hfile) self.append_to(entries, s) # write new entries hfile.seek(0, os.SEEK_SET) hfile.truncate() self.save_to(hfile, entries, self.hist_size) self.entries = entries except EnvironmentError as err: raise RuntimeError( _('Error occurred while writing to file %s (%s)') % (filename, err.strerror)) else: if len(self.entries) == 0: # Make sure that entries contains at least one element. If the # file and s are empty, this can occur. self.entries = [''] bpython-0.17.1/bpython/config.py0000644000175100017510000002766513240407731016566 0ustar useruser00000000000000# encoding: utf-8 from __future__ import unicode_literals, absolute_import import os import sys import locale from itertools import chain from six import iterkeys, iteritems from six.moves.configparser import ConfigParser from .autocomplete import SIMPLE as default_completion, ALL_MODES from .keys import cli_key_dispatch as cli_key_dispatch class Struct(object): """Simple class for instantiating objects we can add arbitrary attributes to and use for various arbitrary things.""" def getpreferredencoding(): """Get the user's preferred encoding.""" return locale.getpreferredencoding() or sys.getdefaultencoding() def can_encode(c): try: c.encode(getpreferredencoding()) return True except UnicodeEncodeError: return False def supports_box_chars(): """Check if the encoding supports Unicode box characters.""" return all(map(can_encode, u'│─└┘┌┐')) def get_config_home(): """Returns the base directory for bpython's configuration files.""" xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') return os.path.join(xdg_config_home, 'bpython') def default_config_path(): """Returns bpython's default configuration file path.""" return os.path.join(get_config_home(), 'config') def fill_config_with_default_values(config, default_values): for section in iterkeys(default_values): if not config.has_section(section): config.add_section(section) for (opt, val) in iteritems(default_values[section]): if not config.has_option(section, opt): config.set(section, opt, '%s' % (val, )) def loadini(struct, configfile): """Loads .ini configuration file and stores its values in struct""" config_path = os.path.expanduser(configfile) config = ConfigParser() defaults = { 'general': { 'arg_spec': True, 'auto_display_list': True, 'autocomplete_mode': default_completion, 'color_scheme': 'default', 'complete_magic_methods': True, 'dedent_after': 1, 'default_autoreload': False, 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')), 'flush_output': True, 'highlight_show_source': True, 'hist_duplicates': True, 'hist_file': '~/.pythonhist', 'hist_length': 100, 'paste_time': 0.02, 'pastebin_confirm': True, 'pastebin_expiry': '1week', 'pastebin_helper': '', 'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id', 'pastebin_show_url': 'https://bpaste.net/show/$paste_id', 'pastebin_url': 'https://bpaste.net/json/new', 'save_append_py': False, 'single_undo_time': 1.0, 'syntax': True, 'tab_length': 4, 'unicode_box': True }, 'keyboard': { 'backspace': 'C-h', 'beginning_of_line': 'C-a', 'clear_line': 'C-u', 'clear_screen': 'C-l', 'clear_word': 'C-w', 'copy_clipboard': 'F10', 'cut_to_buffer': 'C-k', 'delete': 'C-d', 'down_one_line': 'C-n', 'edit_config': 'F3', 'edit_current_block': 'C-x', 'end_of_line': 'C-e', 'exit': '', 'external_editor': 'F7', 'help': 'F1', 'incremental_search': 'M-s', 'last_output': 'F9', 'left': 'C-b', 'pastebin': 'F8', 'reimport': 'F6', 'reverse_incremental_search': 'M-r', 'right': 'C-f', 'save': 'C-s', 'search': 'C-o', 'show_source': 'F2', 'suspend': 'C-z', 'toggle_file_watch': 'F5', 'transpose_chars': 'C-t', 'undo': 'C-r', 'up_one_line': 'C-p', 'yank_from_buffer': 'C-y' }, 'cli': { 'suggestion_width': 0.8, 'trim_prompts': False, }, 'curtsies': { 'list_above': False, 'right_arrow_completion': True, }} default_keys_to_commands = dict((value, key) for (key, value) in iteritems(defaults['keyboard'])) fill_config_with_default_values(config, defaults) if not config.read(config_path): # No config file. If the user has it in the old place then complain if os.path.isfile(os.path.expanduser('~/.bpython.ini')): sys.stderr.write("Error: It seems that you have a config file at " "~/.bpython.ini. Please move your config file to " "%s\n" % default_config_path()) sys.exit(1) def get_key_no_doublebind(command): default_commands_to_keys = defaults['keyboard'] requested_key = config.get('keyboard', command) try: default_command = default_keys_to_commands[requested_key] if (default_commands_to_keys[default_command] == config.get('keyboard', default_command)): setattr(struct, '%s_key' % default_command, '') except KeyError: pass return requested_key struct.config_path = config_path struct.dedent_after = config.getint('general', 'dedent_after') struct.tab_length = config.getint('general', 'tab_length') struct.auto_display_list = config.getboolean('general', 'auto_display_list') struct.syntax = config.getboolean('general', 'syntax') struct.arg_spec = config.getboolean('general', 'arg_spec') struct.paste_time = config.getfloat('general', 'paste_time') struct.single_undo_time = config.getfloat('general', 'single_undo_time') struct.highlight_show_source = config.getboolean('general', 'highlight_show_source') struct.hist_file = config.get('general', 'hist_file') struct.editor = config.get('general', 'editor') struct.hist_length = config.getint('general', 'hist_length') struct.hist_duplicates = config.getboolean('general', 'hist_duplicates') struct.flush_output = config.getboolean('general', 'flush_output') struct.default_autoreload = config.getboolean('general', 'default_autoreload') struct.pastebin_key = get_key_no_doublebind('pastebin') struct.copy_clipboard_key = get_key_no_doublebind('copy_clipboard') struct.save_key = get_key_no_doublebind('save') struct.search_key = get_key_no_doublebind('search') struct.show_source_key = get_key_no_doublebind('show_source') struct.suspend_key = get_key_no_doublebind('suspend') struct.toggle_file_watch_key = get_key_no_doublebind('toggle_file_watch') struct.undo_key = get_key_no_doublebind('undo') struct.reimport_key = get_key_no_doublebind('reimport') struct.reverse_incremental_search_key = get_key_no_doublebind( 'reverse_incremental_search') struct.incremental_search_key = get_key_no_doublebind('incremental_search') struct.up_one_line_key = get_key_no_doublebind('up_one_line') struct.down_one_line_key = get_key_no_doublebind('down_one_line') struct.cut_to_buffer_key = get_key_no_doublebind('cut_to_buffer') struct.yank_from_buffer_key = get_key_no_doublebind('yank_from_buffer') struct.clear_word_key = get_key_no_doublebind('clear_word') struct.backspace_key = get_key_no_doublebind('backspace') struct.clear_line_key = get_key_no_doublebind('clear_line') struct.clear_screen_key = get_key_no_doublebind('clear_screen') struct.delete_key = get_key_no_doublebind('delete') struct.left_key = get_key_no_doublebind('left') struct.right_key = get_key_no_doublebind('right') struct.end_of_line_key = get_key_no_doublebind('end_of_line') struct.beginning_of_line_key = get_key_no_doublebind('beginning_of_line') struct.transpose_chars_key = get_key_no_doublebind('transpose_chars') struct.exit_key = get_key_no_doublebind('exit') struct.last_output_key = get_key_no_doublebind('last_output') struct.edit_config_key = get_key_no_doublebind('edit_config') struct.edit_current_block_key = get_key_no_doublebind('edit_current_block') struct.external_editor_key = get_key_no_doublebind('external_editor') struct.help_key = get_key_no_doublebind('help') struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm') struct.pastebin_url = config.get('general', 'pastebin_url') struct.pastebin_show_url = config.get('general', 'pastebin_show_url') struct.pastebin_removal_url = config.get('general', 'pastebin_removal_url') struct.pastebin_expiry = config.get('general', 'pastebin_expiry') struct.pastebin_helper = config.get('general', 'pastebin_helper') struct.cli_suggestion_width = config.getfloat('cli', 'suggestion_width') struct.cli_trim_prompts = config.getboolean('cli', 'trim_prompts') struct.complete_magic_methods = config.getboolean('general', 'complete_magic_methods') struct.autocomplete_mode = config.get('general', 'autocomplete_mode') struct.save_append_py = config.getboolean('general', 'save_append_py') struct.curtsies_list_above = config.getboolean('curtsies', 'list_above') struct.curtsies_right_arrow_completion = \ config.getboolean('curtsies', 'right_arrow_completion') color_scheme_name = config.get('general', 'color_scheme') default_colors = { 'keyword': 'y', 'name': 'c', 'comment': 'b', 'string': 'm', 'error': 'r', 'number': 'G', 'operator': 'Y', 'punctuation': 'y', 'token': 'C', 'background': 'd', 'output': 'w', 'main': 'c', 'paren': 'R', 'prompt': 'c', 'prompt_more': 'g', 'right_arrow_suggestion': 'K', } if color_scheme_name == 'default': struct.color_scheme = default_colors else: struct.color_scheme = dict() theme_filename = color_scheme_name + '.theme' path = os.path.expanduser(os.path.join(get_config_home(), theme_filename)) try: load_theme(struct, path, struct.color_scheme, default_colors) except EnvironmentError: sys.stderr.write("Could not load theme '%s'.\n" % (color_scheme_name, )) sys.exit(1) # expand path of history file struct.hist_file = os.path.expanduser(struct.hist_file) # verify completion mode if struct.autocomplete_mode not in ALL_MODES: struct.autocomplete_mode = default_completion # set box drawing characters if config.getboolean('general', 'unicode_box') and supports_box_chars(): struct.left_border = '│' struct.right_border = '│' struct.top_border = '─' struct.bottom_border = '─' struct.left_bottom_corner = '└' struct.right_bottom_corner = '┘' struct.left_top_corner = '┌' struct.right_top_corner = '┐' else: struct.left_border = '|' struct.right_border = '|' struct.top_border = '-' struct.bottom_border = '-' struct.left_bottom_corner = '+' struct.right_bottom_corner = '+' struct.left_top_corner = '+' struct.right_top_corner = '+' def load_theme(struct, path, colors, default_colors): theme = ConfigParser() with open(path, 'r') as f: theme.readfp(f) for k, v in chain(theme.items('syntax'), theme.items('interface')): if theme.has_option('syntax', k): colors[k] = theme.get('syntax', k) else: colors[k] = theme.get('interface', k) # Check against default theme to see if all values are defined for k, v in iteritems(default_colors): if k not in colors: colors[k] = v bpython-0.17.1/bpython/inspection.py0000644000175100017510000002450413240407731017461 0ustar useruser00000000000000# encoding: utf-8 # The MIT License # # Copyright (c) 2009-2011 the bpython authors. # Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import absolute_import import inspect import io import keyword import pydoc from collections import namedtuple from six.moves import range from pygments.token import Token from ._py3compat import PythonLexer, py3 from .lazyre import LazyReCompile if not py3: import types _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 on attribute lookup.""" def __init__(self, obj): self.obj = obj def __enter__(self): """Try to make an object not exhibit side-effects on attribute lookup.""" type_ = type(self.obj) __getattribute__ = None __getattr__ = None # Dark magic: # If __getattribute__ doesn't exist on the class and __getattr__ does # then __getattr__ will be called when doing # getattr(type_, '__getattribute__', None) # so we need to first remove the __getattr__, then the # __getattribute__, then look up the attributes and then restore the # original methods. :-( # The upshot being that introspecting on an object to display its # attributes will avoid unwanted side-effects. if is_new_style(self.obj): __getattr__ = getattr(type_, '__getattr__', None) if __getattr__ is not None: try: setattr(type_, '__getattr__', (lambda *_, **__: None)) except TypeError: __getattr__ = None __getattribute__ = getattr(type_, '__getattribute__', None) if __getattribute__ is not None: try: setattr(type_, '__getattribute__', object.__getattribute__) except TypeError: # XXX: This happens for e.g. built-in types __getattribute__ = None self.attribs = (__getattribute__, __getattr__) # /Dark magic def __exit__(self, exc_type, exc_val, exc_tb): """Restore an object's magic methods.""" type_ = type(self.obj) __getattribute__, __getattr__ = self.attribs # Dark magic: if __getattribute__ is not None: setattr(type_, '__getattribute__', __getattribute__) if __getattr__ is not None: setattr(type_, '__getattr__', __getattr__) # /Dark magic if py3: def is_new_style(obj): return True else: def is_new_style(obj): """Returns True if obj is a new-style class or object""" return type(obj) not in [types.InstanceType, types.ClassType] class _Repr(object): """ Helper for `fixlongargs()`: Returns the given value in `__repr__()`. """ def __init__(self, value): self.value = value def __repr__(self): return self.value __str__ = __repr__ def parsekeywordpairs(signature): tokens = PythonLexer().get_tokens(signature) preamble = True stack = [] substack = [] parendepth = 0 for token, value in tokens: if preamble: if token is Token.Punctuation and value == u"(": preamble = False continue if token is Token.Punctuation: if value in [u'(', u'{', u'[']: parendepth += 1 elif value in [u')', u'}', u']']: parendepth -= 1 elif value == ':' and parendepth == -1: # End of signature reached break if ((value == ',' and parendepth == 0) or (value == ')' and parendepth == -1)): stack.append(substack) substack = [] continue if value and (parendepth > 0 or value.strip()): substack.append(value) d = {} for item in stack: if len(item) >= 3: d[item[0]] = ''.join(item[2:]) return d def fixlongargs(f, argspec): """Functions taking default arguments that are references to other objects whose str() is too big will cause breakage, so we swap out the object itself with the name it was referenced with in the source by parsing the source itself !""" if argspec[3] is None: # No keyword args, no need to do anything return values = list(argspec[3]) if not values: return keys = argspec[0][-len(values):] try: src = inspect.getsourcelines(f) except (IOError, IndexError): # IndexError is raised in inspect.findsource(), can happen in # some situations. See issue #94. return signature = ''.join(src[0]) kwparsed = parsekeywordpairs(signature) for i, (key, value) in enumerate(zip(keys, values)): if len(repr(value)) != len(kwparsed[key]): values[i] = _Repr(kwparsed[key]) argspec[3] = values getpydocspec_re = LazyReCompile(r'([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)') def getpydocspec(f, func): try: argspec = pydoc.getdoc(f) except NameError: return None s = getpydocspec_re.search(argspec) 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() for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): varkwargs = arg[2:] elif arg.startswith('*'): varargs = arg[1:] else: arg, _, default = arg.partition('=') if varargs is not None: kwonly_args.append(arg) if default: kwonly_defaults[arg] = default else: args.append(arg) if default: defaults.append(default) return ArgSpec(args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None) 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: try: func_name = getattr(f, '__name__', None) except: # if calling foo.__name__ would result in an error func_name = None try: is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or (func_name == '__init__' and not 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) return None try: if py3: argspec = inspect.getfullargspec(f) else: argspec = inspect.getargspec(f) argspec = list(argspec) fixlongargs(f, argspec) 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.args.insert(0, 'obj') fprops = FuncProps(func, argspec, is_bound_method) return fprops def is_eval_safe_name(string): if py3: return all(part.isidentifier() and not keyword.iskeyword(part) for part in string.split('.')) else: return all(_name.match(part) and not keyword.iskeyword(part) for part in string.split('.')) def is_callable(obj): return callable(obj) get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*([-\w.]+).*$') def get_encoding(obj): """Try to obtain encoding information of the source of an object.""" for line in inspect.findsource(obj)[0][:2]: m = get_encoding_line_re.search(line) if m: return m.group(1) return 'ascii' def get_encoding_comment(source): """Returns encoding line without the newline, or None is not found""" for line in source.splitlines()[:2]: m = get_encoding_line_re.search(line) if m: return m.group(0) return None def get_encoding_file(fname): """Try to obtain encoding information from a Python source file.""" with io.open(fname, 'rt', encoding='ascii', errors='ignore') as f: for unused in range(2): line = f.readline() match = get_encoding_line_re.search(line) if match: return match.group(1) return 'ascii' if py3: def get_source_unicode(obj): """Returns a decoded source of object""" return inspect.getsource(obj) else: def get_source_unicode(obj): """Returns a decoded source of object""" return inspect.getsource(obj).decode(get_encoding(obj)) bpython-0.17.1/bpython/autocomplete.py0000644000175100017510000005113213240410473020001 0ustar useruser00000000000000# coding: utf-8 # 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 # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # from __future__ import unicode_literals, absolute_import import __main__ import abc import glob import keyword import logging import os import re import rlcompleter import sys from six.moves import range, builtins from six import string_types, iteritems from . import inspection from . import importcompletion from . import line as lineparts from .line import LinePart from ._py3compat import py3, try_decode from .lazyre import LazyReCompile from .simpleeval import (safe_eval, evaluate_current_expression, EvaluationError) if not py3: from types import InstanceType, ClassType # Autocomplete modes SIMPLE = 'simple' SUBSTRING = 'substring' FUZZY = 'fuzzy' ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) MAGIC_METHODS = tuple("__%s__" % s for s in ( "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", "oct", "hex", "index", "coerce", "enter", "exit")) if py3: KEYWORDS = frozenset(keyword.kwlist) else: KEYWORDS = frozenset(name.decode('ascii') for name in keyword.kwlist) def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] def few_enough_underscores(current, match): """Returns whether match should be shown based on current if current is _, True if match starts with 0 or 1 underscore if current is __, True regardless of match otherwise True if match does not start with any underscore """ if current.startswith('__'): return True elif current.startswith('_') and not match.startswith('__'): return True elif match.startswith('_'): return False else: return True def method_match_simple(word, size, text): return word[:size] == text def method_match_substring(word, size, text): return text in word def method_match_fuzzy(word, size, text): s = r'.*%s.*' % '.*'.join(list(text)) return re.search(s, word) MODES_MAP = { SIMPLE: method_match_simple, SUBSTRING: method_match_substring, FUZZY: method_match_fuzzy } class BaseCompletionType(object): """Describes different completion types""" def __init__(self, shown_before_tab=True, mode=SIMPLE): self._shown_before_tab = shown_before_tab self.method_match = MODES_MAP[mode] def matches(self, cursor_offset, line, **kwargs): """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. ie, import completion doesn't make sense if there cursor isn't after an import or from statement, so it ought to return None. Completion types are used to: * `locate(cur, line)` their initial target word to replace given a line and cursor * find `matches(cur, line)` that might replace that word * `format(match)` matches to be displayed to the user * determine whether suggestions should be `shown_before_tab` * `substitute(cur, line, match)` in a match for what's found with `target` """ raise NotImplementedError def locate(self, cursor_offset, line): """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): return word def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" 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): """Whether suggestions should be shown before the user hits tab, or only once that has happened.""" return self._shown_before_tab class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" def __init__(self, completers, mode=SIMPLE): if not completers: raise ValueError( "CumulativeCompleter requires at least one completer") self._completers = completers super(CumulativeCompleter, self).__init__(True, mode) def locate(self, current_offset, line): return self._completers[0].locate(current_offset, line) 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: matches = completer.matches(cursor_offset=cursor_offset, line=line, **kwargs) if matches is not None: all_matches.update(matches) return_value = all_matches return return_value class ImportCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): return importcompletion.complete(cursor_offset, line) def locate(self, current_offset, line): return lineparts.current_word(current_offset, line) def format(self, word): return after_last_dot(word) class FilenameCompletion(BaseCompletionType): def __init__(self, mode=SIMPLE): super(FilenameCompletion, self).__init__(False, mode) if sys.version_info[:2] >= (3, 4): def safe_glob(self, pathname): return glob.iglob(glob.escape(pathname) + '*') else: def safe_glob(self, pathname): try: return glob.glob(pathname + '*') except re.error: # see #491 return tuple() def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) if cs is None: return None matches = set() username = cs.word.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) for filename in self.safe_glob(os.path.expanduser(cs.word)): if os.path.isdir(filename): filename += os.path.sep if cs.word.startswith('~'): filename = username + filename[len(user_dir):] matches.add(filename) return matches def locate(self, current_offset, line): return lineparts.current_string(current_offset, line) def format(self, filename): filename.rstrip(os.sep).rsplit(os.sep)[-1] if os.sep in filename[:-1]: return filename[filename.rindex(os.sep, 0, -1) + 1:] else: return filename 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 locals_ = kwargs['locals_'] r = self.locate(cursor_offset, line) if r is None: return None if locals_ is None: # TODO add a note about why locals_ = __main__.__dict__ assert '.' in r.word for i in range(1, len(r.word) + 1): if r.word[-i] == '[': i -= 1 break methodtext = r.word[-i:] matches = set(''.join([r.word[:-i], m]) for m in self.attr_matches(methodtext, locals_)) return set(m for m in matches if few_enough_underscores(r.word.split('.')[-1], m.split('.')[-1])) def locate(self, current_offset, line): return lineparts.current_dotted_attribute(current_offset, line) def format(self, word): return after_last_dot(word) def attr_matches(self, text, namespace): """Taken from rlcompleter.py and bent to my will. """ # 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 = self.attr_matches_re.match(text) if not m: return [] expr, attr = m.group(1, 3) if expr.isdigit(): # Special case: float literal, using attrs here will result in # a SyntaxError return [] try: obj = safe_eval(expr, namespace) except EvaluationError: return [] with inspection.AttrCleaner(obj): matches = self.attr_lookup(obj, expr, attr) return matches 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 = self.list_attributes(obj) if hasattr(obj, '__class__'): words.append('__class__') words = words + rlcompleter.get_class_members(obj.__class__) if not isinstance(obj.__class__, abc.ABCMeta): try: words.remove('__abstractmethods__') except ValueError: pass if not py3 and isinstance(obj, (InstanceType, ClassType)): # Account for the __dict__ in an old-style class. words.append('__dict__') matches = [] n = len(attr) for word in words: if self.method_match(word, n, attr) and word != "__builtins__": 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): 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 _, _, dexpr = lineparts.current_dict(cursor_offset, line) try: obj = safe_eval(dexpr, locals_) except EvaluationError: return None if isinstance(obj, dict) and obj.keys(): 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 None def locate(self, current_offset, line): return lineparts.current_dict_key(current_offset, line) def format(self, match): return match[:-1] class MagicMethodCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): if 'current_block' not in kwargs: return None current_block = kwargs['current_block'] r = self.locate(cursor_offset, line) if r is None: return None if 'class' not in current_block: return None 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) class GlobalCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. """ if 'locals_' not in kwargs: return None locals_ = kwargs['locals_'] r = self.locate(cursor_offset, line) if r is None: return None matches = set() n = len(r.word) for word in KEYWORDS: if self.method_match(word, n, r.word): 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, r.word) and word != "__builtins__"): matches.add(_callable_postfix(val, word)) return matches if matches else None def locate(self, current_offset, line): return lineparts.current_single_word(current_offset, line) class ParameterNameCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): if 'argspec' not in kwargs: return None argspec = kwargs['argspec'] if not argspec: return None r = self.locate(cursor_offset, line) if r is None: return None if argspec: matches = set(name + '=' for name in argspec[1][0] if isinstance(name, string_types) and name.startswith(r.word)) if py3: matches.update(name + '=' for name in argspec[1][4] if name.startswith(r.word)) return matches if matches else None def locate(self, current_offset, line): return lineparts.current_word(current_offset, line) class ExpressionAttributeCompletion(AttrCompletion): # could replace attr completion as a more general case with some work def locate(self, current_offset, line): return lineparts.current_expression_attribute(current_offset, line) def matches(self, cursor_offset, line, **kwargs): if 'locals_' not in kwargs: return None locals_ = kwargs['locals_'] if locals_ is None: locals_ = __main__.__dict__ attr = self.locate(cursor_offset, line) try: obj = evaluate_current_expression(cursor_offset, line, locals_) except EvaluationError: return set() with inspection.AttrCleaner(obj): # strips leading dot matches = [m[1:] for m in self.attr_lookup(obj, '', attr.word)] return set(m for m in matches if few_enough_underscores(attr.word, m)) try: import jedi except ImportError: class MultilineJediCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): return None else: class JediCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): if 'history' not in kwargs: return None history = kwargs['history'] if not lineparts.current_word(cursor_offset, line): return None history = '\n'.join(history) + '\n' + line try: script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') completions = script.completions() except (jedi.NotFoundError, IndexError, KeyError): # IndexError for #483 # KeyError for #544 self._orig_start = None return None if completions: diff = len(completions[0].name) - len(completions[0].complete) self._orig_start = cursor_offset - diff else: self._orig_start = None return None first_letter = line[self._orig_start:self._orig_start + 1] matches = [try_decode(c.name, 'ascii') for c in completions] if any(not m.lower().startswith(matches[0][0].lower()) for m in matches): # Too general - giving completions starting with multiple # letters return None else: # case-sensitive matches only return set(m for m in matches if m.startswith(first_letter)) def locate(self, cursor_offset, line): start = self._orig_start end = cursor_offset return LinePart(start, end, line[start:end]) class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): if 'current_block' not in kwargs or 'history' not in kwargs: return None current_block = kwargs['current_block'] history = kwargs['history'] if '\n' in current_block: assert cursor_offset <= len(line), "%r %r" % (cursor_offset, line) results = super(MultilineJediCompletion, self).matches(cursor_offset, line, history=history) return results else: return None def get_completer(completers, cursor_offset, line, **kwargs): """Returns a list of matches and an applicable completer If no matches available, returns a tuple of an empty list and None cursor_offset is the current cursor column line is a string of the current line kwargs (all optional): locals_ is a dictionary of the environment argspec is an inspect.ArgSpec instance for the current function where the cursor is current_block is the possibly multiline not-yet-evaluated block of code which the current line is part of complete_magic_methods is a bool of whether we ought to complete double underscore methods like __len__ in method signatures """ for completer in completers: try: matches = completer.matches(cursor_offset, line, **kwargs) 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 def get_default_completer(mode=SIMPLE): return ( DictKeyCompletion(mode=mode), ImportCompletion(mode=mode), FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), CumulativeCompleter((GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode), AttrCompletion(mode=mode), ExpressionAttributeCompletion(mode=mode), ) def get_completer_bpython(cursor_offset, line, **kwargs): """""" return get_completer(get_default_completer(), cursor_offset, line, **kwargs) def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" with inspection.AttrCleaner(value): if inspection.is_callable(value): word += '(' return word