prefilterfrontend.py
280 lines
| 10.3 KiB
| text/x-python
|
PythonLexer
Gael Varoquaux
|
r1385 | """ | ||
Frontend class that uses IPython0 to prefilter the inputs. | ||||
Using the IPython0 mechanism gives us access to the magics. | ||||
gvaroquaux
|
r1455 | |||
This is a transitory class, used here to do the transition between | ||||
ipython0 and ipython1. This class is meant to be short-lived as more | ||||
functionnality is abstracted out of ipython0 in reusable functions and | ||||
is added on the interpreter. This class can be a used to guide this | ||||
refactoring. | ||||
Gael Varoquaux
|
r1385 | """ | ||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
# Copyright (C) 2008 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
import sys | ||||
Gael Varoquaux
|
r1840 | import pydoc | ||
import os | ||||
Gael Varoquaux
|
r1884 | import re | ||
Gael Varoquaux
|
r1840 | import __builtin__ | ||
Gael Varoquaux
|
r1385 | |||
from IPython.ipmaker import make_IPython | ||||
from IPython.ipapi import IPApi | ||||
Gael Varoquaux
|
r1423 | from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap | ||
Gael Varoquaux
|
r1385 | |||
Gael Varoquaux
|
r1437 | from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap | ||
Gael Varoquaux
|
r1385 | from IPython.genutils import Term | ||
Gael Varoquaux
|
r1840 | |||
from linefrontendbase import LineFrontEndBase, common_prefix | ||||
gvaroquaux
|
r1455 | |||
Gael Varoquaux
|
r1385 | |||
Gael Varoquaux
|
r1437 | def mk_system_call(system_call_function, command): | ||
""" given a os.system replacement, and a leading string command, | ||||
returns a function that will execute the command with the given | ||||
argument string. | ||||
Gael Varoquaux
|
r1424 | """ | ||
def my_system_call(args): | ||||
Gael Varoquaux
|
r1437 | system_call_function("%s %s" % (command, args)) | ||
gvaroquaux
|
r1730 | |||
my_system_call.__doc__ = "Calls %s" % command | ||||
Gael Varoquaux
|
r1424 | return my_system_call | ||
Gael Varoquaux
|
r1385 | #------------------------------------------------------------------------------- | ||
# Frontend class using ipython0 to do the prefiltering. | ||||
#------------------------------------------------------------------------------- | ||||
class PrefilterFrontEnd(LineFrontEndBase): | ||||
gvaroquaux
|
r1455 | """ Class that uses ipython0 to do prefilter the input, do the | ||
completion and the magics. | ||||
The core trick is to use an ipython0 instance to prefilter the | ||||
input, and share the namespace between the interpreter instance used | ||||
to execute the statements and the ipython0 used for code | ||||
completion... | ||||
""" | ||||
Gael Varoquaux
|
r1624 | |||
debug = False | ||||
Gael Varoquaux
|
r1385 | |||
Gael Varoquaux
|
r1504 | def __init__(self, ipython0=None, *args, **kwargs): | ||
""" Parameters: | ||||
----------- | ||||
ipython0: an optional ipython0 instance to use for command | ||||
prefiltering and completion. | ||||
""" | ||||
Gael Varoquaux
|
r1887 | # This is a hack to avoid the IPython exception hook to trigger | ||
# on exceptions (https://bugs.launchpad.net/bugs/337105) | ||||
# XXX: This is horrible: module-leve monkey patching -> side | ||||
# effects. | ||||
from IPython import iplib | ||||
iplib.InteractiveShell.isthreaded = True | ||||
Gael Varoquaux
|
r1624 | LineFrontEndBase.__init__(self, *args, **kwargs) | ||
self.shell.output_trap = RedirectorOutputTrap( | ||||
out_callback=self.write, | ||||
err_callback=self.write, | ||||
) | ||||
self.shell.traceback_trap = SyncTracebackTrap( | ||||
formatters=self.shell.traceback_trap.formatters, | ||||
) | ||||
# Start the ipython0 instance: | ||||
Gael Varoquaux
|
r1472 | self.save_output_hooks() | ||
Gael Varoquaux
|
r1504 | if ipython0 is None: | ||
# Instanciate an IPython0 interpreter to be able to use the | ||||
# prefiltering. | ||||
Gael Varoquaux
|
r1840 | # Suppress all key input, to avoid waiting | ||
def my_rawinput(x=None): | ||||
return '\n' | ||||
old_rawinput = __builtin__.raw_input | ||||
__builtin__.raw_input = my_rawinput | ||||
Gael Varoquaux
|
r1505 | # XXX: argv=[] is a bit bold. | ||
Gael Varoquaux
|
r1624 | ipython0 = make_IPython(argv=[], | ||
user_ns=self.shell.user_ns, | ||||
user_global_ns=self.shell.user_global_ns) | ||||
Gael Varoquaux
|
r1840 | __builtin__.raw_input = old_rawinput | ||
Gael Varoquaux
|
r1504 | self.ipython0 = ipython0 | ||
Gael Varoquaux
|
r1385 | # Set the pager: | ||
self.ipython0.set_hook('show_in_pager', | ||||
Gael Varoquaux
|
r1504 | lambda s, string: self.write("\n" + string)) | ||
Gael Varoquaux
|
r1385 | self.ipython0.write = self.write | ||
self._ip = _ip = IPApi(self.ipython0) | ||||
# Make sure the raw system call doesn't get called, as we don't | ||||
# have a stdin accessible. | ||||
Gael Varoquaux
|
r1437 | self._ip.system = self.system_call | ||
Gael Varoquaux
|
r1424 | # XXX: Muck around with magics so that they work better | ||
# in our environment | ||||
Gael Varoquaux
|
r1840 | if not sys.platform.startswith('win'): | ||
self.ipython0.magic_ls = mk_system_call(self.system_call, | ||||
'ls -CF') | ||||
Gael Varoquaux
|
r1472 | # And now clean up the mess created by ipython0 | ||
self.release_output() | ||||
Gael Varoquaux
|
r1624 | |||
Gael Varoquaux
|
r1505 | if not 'banner' in kwargs and self.banner is None: | ||
Gael Varoquaux
|
r1884 | self.banner = self.ipython0.BANNER | ||
Gael Varoquaux
|
r1495 | |||
Gael Varoquaux
|
r1884 | # FIXME: __init__ and start should be two different steps | ||
Gael Varoquaux
|
r1624 | self.start() | ||
Gael Varoquaux
|
r1385 | |||
gvaroquaux
|
r1455 | #-------------------------------------------------------------------------- | ||
# FrontEndBase interface | ||||
#-------------------------------------------------------------------------- | ||||
Gael Varoquaux
|
r1385 | |||
def show_traceback(self): | ||||
gvaroquaux
|
r1455 | """ Use ipython0 to capture the last traceback and display it. | ||
""" | ||||
Gael Varoquaux
|
r1825 | # Don't do the capture; the except_hook has already done some | ||
# modifications to the IO streams, if we store them, we'll be | ||||
# storing the wrong ones. | ||||
#self.capture_output() | ||||
gvaroquaux
|
r1640 | self.ipython0.showtraceback(tb_offset=-1) | ||
Gael Varoquaux
|
r1385 | self.release_output() | ||
Gael Varoquaux
|
r1391 | def execute(self, python_string, raw_string=None): | ||
gvaroquaux
|
r1502 | if self.debug: | ||
print 'Executing Python code:', repr(python_string) | ||||
Gael Varoquaux
|
r1391 | self.capture_output() | ||
LineFrontEndBase.execute(self, python_string, | ||||
raw_string=raw_string) | ||||
self.release_output() | ||||
Gael Varoquaux
|
r1472 | def save_output_hooks(self): | ||
""" Store all the output hooks we can think of, to be able to | ||||
restore them. | ||||
We need to do this early, as starting the ipython0 instance will | ||||
screw ouput hooks. | ||||
""" | ||||
self.__old_cout_write = Term.cout.write | ||||
self.__old_cerr_write = Term.cerr.write | ||||
self.__old_stdout = sys.stdout | ||||
self.__old_stderr= sys.stderr | ||||
self.__old_help_output = pydoc.help.output | ||||
self.__old_display_hook = sys.displayhook | ||||
Gael Varoquaux
|
r1385 | def capture_output(self): | ||
Gael Varoquaux
|
r1391 | """ Capture all the output mechanisms we can think of. | ||
Gael Varoquaux
|
r1385 | """ | ||
Gael Varoquaux
|
r1472 | self.save_output_hooks() | ||
Gael Varoquaux
|
r1385 | Term.cout.write = self.write | ||
Term.cerr.write = self.write | ||||
sys.stdout = Term.cout | ||||
sys.stderr = Term.cerr | ||||
Gael Varoquaux
|
r1391 | pydoc.help.output = self.shell.output_trap.out | ||
Gael Varoquaux
|
r1390 | |||
Gael Varoquaux
|
r1385 | |||
def release_output(self): | ||||
Gael Varoquaux
|
r1391 | """ Release all the different captures we have made. | ||
Gael Varoquaux
|
r1385 | """ | ||
Term.cout.write = self.__old_cout_write | ||||
Gael Varoquaux
|
r1472 | Term.cerr.write = self.__old_cerr_write | ||
Gael Varoquaux
|
r1385 | sys.stdout = self.__old_stdout | ||
sys.stderr = self.__old_stderr | ||||
Gael Varoquaux
|
r1391 | pydoc.help.output = self.__old_help_output | ||
Gael Varoquaux
|
r1472 | sys.displayhook = self.__old_display_hook | ||
Gael Varoquaux
|
r1385 | |||
def complete(self, line): | ||||
Gael Varoquaux
|
r1624 | # FIXME: This should be factored out in the linefrontendbase | ||
# method. | ||||
Gael Varoquaux
|
r1884 | word = self._get_completion_text(line) | ||
Gael Varoquaux
|
r1385 | completions = self.ipython0.complete(word) | ||
Gael Varoquaux
|
r1407 | # FIXME: The proper sort should be done in the complete method. | ||
Gael Varoquaux
|
r1385 | key = lambda x: x.replace('_', '') | ||
completions.sort(key=key) | ||||
if completions: | ||||
prefix = common_prefix(completions) | ||||
line = line[:-len(word)] + prefix | ||||
return line, completions | ||||
gvaroquaux
|
r1455 | |||
#-------------------------------------------------------------------------- | ||||
# LineFrontEndBase interface | ||||
#-------------------------------------------------------------------------- | ||||
def prefilter_input(self, input_string): | ||||
""" Using IPython0 to prefilter the commands to turn them | ||||
in executable statements that are valid Python strings. | ||||
""" | ||||
input_string = LineFrontEndBase.prefilter_input(self, input_string) | ||||
filtered_lines = [] | ||||
# The IPython0 prefilters sometime produce output. We need to | ||||
# capture it. | ||||
self.capture_output() | ||||
self.last_result = dict(number=self.prompt_number) | ||||
Fernando Perez
|
r1706 | |||
## try: | ||||
## for line in input_string.split('\n'): | ||||
## filtered_lines.append( | ||||
## self.ipython0.prefilter(line, False).rstrip()) | ||||
## except: | ||||
## # XXX: probably not the right thing to do. | ||||
## self.ipython0.showsyntaxerror() | ||||
## self.after_execute() | ||||
## finally: | ||||
## self.release_output() | ||||
gvaroquaux
|
r1455 | try: | ||
Fernando Perez
|
r1706 | try: | ||
for line in input_string.split('\n'): | ||||
filtered_lines.append( | ||||
self.ipython0.prefilter(line, False).rstrip()) | ||||
except: | ||||
# XXX: probably not the right thing to do. | ||||
self.ipython0.showsyntaxerror() | ||||
self.after_execute() | ||||
gvaroquaux
|
r1455 | finally: | ||
self.release_output() | ||||
Fernando Perez
|
r1706 | |||
gvaroquaux
|
r1502 | # Clean up the trailing whitespace, to avoid indentation errors | ||
gvaroquaux
|
r1455 | filtered_string = '\n'.join(filtered_lines) | ||
return filtered_string | ||||
#-------------------------------------------------------------------------- | ||||
Gael Varoquaux
|
r1458 | # PrefilterFrontEnd interface | ||
gvaroquaux
|
r1455 | #-------------------------------------------------------------------------- | ||
def system_call(self, command_string): | ||||
""" Allows for frontend to define their own system call, to be | ||||
able capture output and redirect input. | ||||
""" | ||||
return os.system(command_string) | ||||
Gael Varoquaux
|
r1385 | |||
Gael Varoquaux
|
r1391 | def do_exit(self): | ||
""" Exit the shell, cleanup and save the history. | ||||
""" | ||||
self.ipython0.atexit_operations() | ||||
Gael Varoquaux
|
r1884 | |||
def _get_completion_text(self, line): | ||||
""" Returns the text to be completed by breaking the line at specified | ||||
delimiters. | ||||
""" | ||||
# Break at: spaces, '=', all parentheses (except if balanced). | ||||
# FIXME2: In the future, we need to make the implementation similar to | ||||
# that in the 'pyreadline' module (modes/basemode.py) where we break at | ||||
# each delimiter and try to complete the residual line, until we get a | ||||
# successful list of completions. | ||||
expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})' | ||||
complete_sep = re.compile(expression) | ||||
text = complete_sep.split(line)[-1] | ||||
return text | ||||