bpython-0.18/0000775000175100017510000000000013451167435013262 5ustar user1user100000000000000bpython-0.18/CHANGELOG0000664000175100017510000006723113451167327014505 0ustar user1user100000000000000Changelog
=========
0.18
----
New features:
* #713 expose globals in bpdb debugging.
Thanks to toejough.
Fixes:
* Fix file locking on Windows.
* Exit gracefully if config file fails to be loaded due to encoding errors.
* #744: Fix newline handling.
Thanks to Attila Szöllősi.
* #731: Fix exit code.
Thanks to benkrig.
* #767: Fix crash when matching certain lines in history.
Support for Python 3.3 has been dropped.
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 handling 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 running scripts with non-ASCII characters.
* #639: Fix compatibility 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.18/bpdb/0000775000175100017510000000000013451167434014170 5ustar user1user100000000000000bpython-0.18/bpdb/__main__.py0000664000175100017510000000235413451167126016264 0ustar user1user100000000000000# 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.18/bpdb/__init__.py0000664000175100017510000000776613451167126016317 0ustar user1user100000000000000# 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.18/bpdb/debugger.py0000664000175100017510000000401313451167126016322 0ustar user1user100000000000000# 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):
locals_ = self.curframe.f_globals.copy()
locals_.update(self.curframe.f_locals)
bpython.embed(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.18/PKG-INFO0000664000175100017510000000104013451167435014352 0ustar user1user100000000000000Metadata-Version: 2.1
Name: bpython
Version: 0.18
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: bpython is a fancy interface to the Python
interpreter for Unix-like operating systems.
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Provides-Extra: jedi
Provides-Extra: urwid
Provides-Extra: watch
bpython-0.18/bpython/0000775000175100017510000000000013451167434014744 5ustar user1user100000000000000bpython-0.18/bpython/urwid.py0000664000175100017510000014402013451167126016447 0ustar user1user100000000000000# 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.
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 = '' # native str (bytes in Py2, unicode in Py3)
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:
caption = ('prompt', self.ps1)
if py3:
self.stdout_hist += self.ps1
else:
self.stdout_hist += self.ps1.encode(getpreferredencoding())
else:
caption = ('prompt_more', self.ps2)
if py3:
self.stdout_hist += self.ps2
else:
self.stdout_hist += self.ps2.encode(getpreferredencoding())
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.18/bpython/keys.py0000664000175100017510000000505313451167126016272 0ustar user1user100000000000000# 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.18/bpython/paste.py0000664000175100017510000000775113451167126016442 0ustar user1user100000000000000# 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.18/bpython/args.py0000664000175100017510000001062413451167126016253 0ustar user1user100000000000000# 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.18/bpython/cli.py0000664000175100017510000017401713451167126016075 0ustar user1user100000000000000# 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 . import args as bpargs
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 = '' # native str (bytes in Py2, unicode in Py3)
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))
if py3:
self.stdout_hist += self.ps1
else:
self.stdout_hist += self.ps1.encode(getpreferredencoding())
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))
if py3:
self.stdout_hist += self.ps2
else:
self.stdout_hist += self.ps2.encode(getpreferredencoding())
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:
# can't determine actual size - return default values
sizex, sizey = stdscr.getmaxyx()
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:
bpargs.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.18/bpython/_version.py0000664000175100017510000000007113451167434017140 0ustar user1user100000000000000# Auto-generated file, do not edit!
__version__ = '0.18'
bpython-0.18/bpython/inspection.py0000664000175100017510000002450413451167327017477 0ustar user1user100000000000000# 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.18/bpython/filelock.py0000664000175100017510000000566613451167126017121 0ustar user1user100000000000000# 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, fileobj):
self.fileobj = fileobj
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, fileobj, mode=None):
super(UnixFileLock, self).__init__(fileobj)
if mode is None:
mode = fcntl.LOCK_EX
self.mode = mode
def acquire(self):
try:
fcntl.flock(self.fileobj, self.mode)
self.locked = True
except IOError as e:
if e.errno != errno.ENOLCK:
raise e
def release(self):
fcntl.flock(self.fileobj, fcntl.LOCK_UN)
self.locked = False
class WindowsFileLock(BaseLock):
"""Simple file locking for Windows using msvcrt
"""
def __init__(self, fileobj, mode=None):
super(WindowsFileLock, self).__init__(fileobj)
def acquire(self):
msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_NBLCK, 1)
self.locked = True
def release(self):
msvcrt.locking(self.fileobj.fileno(), 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.18/bpython/config.py0000664000175100017510000003043413451167126016565 0ustar user1user100000000000000# 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)
try:
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)
except UnicodeDecodeError as e:
sys.stderr.write("Error: Unable to parse config file at '{}' due to an "
"encoding issue. Please make sure to fix the encoding "
"of the file or remove it and then try again.\n".format(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.18/bpython/__main__.py0000664000175100017510000000236413451167126017041 0ustar user1user100000000000000# 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.18/bpython/history.py0000664000175100017510000002002113451167126017010 0ustar user1user100000000000000# 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
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.18/bpython/translations/0000775000175100017510000000000013451167435017466 5ustar user1user100000000000000bpython-0.18/bpython/translations/fr_FR/0000775000175100017510000000000013451167434020463 5ustar user1user100000000000000bpython-0.18/bpython/translations/fr_FR/LC_MESSAGES/0000775000175100017510000000000013451167435022251 5ustar user1user100000000000000bpython-0.18/bpython/translations/fr_FR/LC_MESSAGES/bpython.po0000664000175100017510000001740713451167126024302 0ustar user1user100000000000000# 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.18/bpython/translations/it_IT/0000775000175100017510000000000013451167434020475 5ustar user1user100000000000000bpython-0.18/bpython/translations/it_IT/LC_MESSAGES/0000775000175100017510000000000013451167435022263 5ustar user1user100000000000000bpython-0.18/bpython/translations/it_IT/LC_MESSAGES/bpython.po0000664000175100017510000001376613451167126024320 0ustar user1user100000000000000# 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.18/bpython/translations/__init__.py0000664000175100017510000000230513451167126021574 0ustar user1user100000000000000# 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.18/bpython/translations/nl_NL/0000775000175100017510000000000013451167434020467 5ustar user1user100000000000000bpython-0.18/bpython/translations/nl_NL/LC_MESSAGES/0000775000175100017510000000000013451167435022255 5ustar user1user100000000000000bpython-0.18/bpython/translations/nl_NL/LC_MESSAGES/bpython.po0000664000175100017510000001376013451167126024304 0ustar user1user100000000000000# 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.18/bpython/translations/es_ES/0000775000175100017510000000000013451167434020463 5ustar user1user100000000000000bpython-0.18/bpython/translations/es_ES/LC_MESSAGES/0000775000175100017510000000000013451167435022251 5ustar user1user100000000000000bpython-0.18/bpython/translations/es_ES/LC_MESSAGES/bpython.po0000664000175100017510000001376513451167126024305 0ustar user1user100000000000000# 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.18/bpython/translations/de/0000775000175100017510000000000013451167434020055 5ustar user1user100000000000000bpython-0.18/bpython/translations/de/LC_MESSAGES/0000775000175100017510000000000013451167435021643 5ustar user1user100000000000000bpython-0.18/bpython/translations/de/LC_MESSAGES/bpython.po0000664000175100017510000001650513451167126023672 0ustar user1user100000000000000# 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.18/bpython/importcompletion.py0000664000175100017510000001537313451167126020731 0ustar user1user100000000000000# 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 py3:
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.18/bpython/pager.py0000664000175100017510000000504713451167126016420 0ustar user1user100000000000000# 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.18/bpython/simplerepl.py0000664000175100017510000001064613451167126017477 0ustar user1user100000000000000# 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.18/bpython/__init__.py0000664000175100017510000000275413451167126017063 0ustar user1user100000000000000# 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=None, banner=None):
if args is None:
args = ['-i', '-q']
from .curtsies import main
return main(args, locals_, banner)
bpython-0.18/bpython/formatter.py0000664000175100017510000000736713451167126017334 0ustar user1user100000000000000# 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.18/bpython/curtsies.py0000664000175100017510000001755613451167126017173 0ustar user1user100000000000000# 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.18/bpython/sample-config0000664000175100017510000000546213451167126017420 0ustar user1user100000000000000# 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.18/bpython/lazyre.py0000664000175100017510000000420213451167126016620 0ustar user1user100000000000000# 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.18/bpython/repl.py0000664000175100017510000013406113451167126016263 0ustar user1user100000000000000# 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 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
stdout_text = 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.18/bpython/test/0000775000175100017510000000000013451167435015724 5ustar user1user100000000000000bpython-0.18/bpython/test/test_line_properties.py0000664000175100017510000003027613451167126022545 0ustar user1user100000000000000# 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('