ipshell_nonblocking.py
527 lines
| 17.1 KiB
| text/x-python
|
PythonLexer
Ville M. Vainio
|
r1112 | #!/usr/bin/python | ||
# -*- coding: iso-8859-15 -*- | ||||
''' | ||||
Provides IPython remote instance. | ||||
@author: Laurent Dufrechou | ||||
laurent.dufrechou _at_ gmail.com | ||||
@license: BSD | ||||
All rights reserved. This program and the accompanying materials are made | ||||
available under the terms of the BSD which accompanies this distribution, and | ||||
is available at U{http://www.opensource.org/licenses/bsd-license.php} | ||||
''' | ||||
__version__ = 0.9 | ||||
__author__ = "Laurent Dufrechou" | ||||
__email__ = "laurent.dufrechou _at_ gmail.com" | ||||
__license__ = "BSD" | ||||
import re | ||||
import sys | ||||
import os | ||||
import locale | ||||
from thread_ex import ThreadEx | ||||
try: | ||||
Laurent Dufréchou
|
r1221 | import IPython | ||
Brian Granger
|
r2023 | from IPython.utils import genutils | ||
Brian Granger
|
r2028 | from IPython.core import iplib | ||
Ville M. Vainio
|
r1112 | except Exception,e: | ||
Laurent Dufréchou
|
r1221 | print "Error importing IPython (%s)" % str(e) | ||
raise Exception, e | ||||
Ville M. Vainio
|
r1112 | |||
############################################################################## | ||||
class _Helper(object): | ||||
"""Redefine the built-in 'help'. | ||||
This is a wrapper around pydoc.help (with a twist). | ||||
""" | ||||
Laurent Dufréchou
|
r1221 | def __init__(self, pager): | ||
Ville M. Vainio
|
r1112 | self._pager = pager | ||
def __repr__(self): | ||||
return "Type help() for interactive help, " \ | ||||
"or help(object) for help about object." | ||||
def __call__(self, *args, **kwds): | ||||
class DummyWriter(object): | ||||
Fernando Perez
|
r2116 | '''Dumy class to handle help output''' | ||
Laurent Dufréchou
|
r1221 | def __init__(self, pager): | ||
Ville M. Vainio
|
r1112 | self._pager = pager | ||
Laurent Dufréchou
|
r1221 | def write(self, data): | ||
'''hook to fill self._pager''' | ||||
Ville M. Vainio
|
r1112 | self._pager(data) | ||
import pydoc | ||||
pydoc.help.output = DummyWriter(self._pager) | ||||
pydoc.help.interact = lambda :1 | ||||
return pydoc.help(*args, **kwds) | ||||
############################################################################## | ||||
class _CodeExecutor(ThreadEx): | ||||
Laurent Dufréchou
|
r1221 | ''' Thread that execute ipython code ''' | ||
ldufrechou
|
r1621 | def __init__(self, instance): | ||
Ville M. Vainio
|
r1112 | ThreadEx.__init__(self) | ||
self.instance = instance | ||||
ldufrechou
|
r1621 | |||
Ville M. Vainio
|
r1112 | def run(self): | ||
Laurent Dufréchou
|
r1221 | '''Thread main loop''' | ||
Ville M. Vainio
|
r1112 | try: | ||
self.instance._doc_text = None | ||||
self.instance._help_text = None | ||||
self.instance._execute() | ||||
# used for uper class to generate event after execution | ||||
laurent.dufrechou@gmail.com
|
r1816 | self.instance._after_execute() | ||
Ville M. Vainio
|
r1112 | |||
except KeyboardInterrupt: | ||||
pass | ||||
############################################################################## | ||||
class NonBlockingIPShell(object): | ||||
''' | ||||
Create an IPython instance, running the commands in a separate, | ||||
non-blocking thread. | ||||
This allows embedding in any GUI without blockage. | ||||
Note: The ThreadEx class supports asynchroneous function call | ||||
via raise_exc() | ||||
''' | ||||
Laurent Dufréchou
|
r1221 | def __init__(self, argv=[], user_ns={}, user_global_ns=None, | ||
Ville M. Vainio
|
r1112 | cin=None, cout=None, cerr=None, | ||
ldufrechou
|
r1153 | ask_exit_handler=None): | ||
Ville M. Vainio
|
r1112 | ''' | ||
@param argv: Command line options for IPython | ||||
@type argv: list | ||||
@param user_ns: User namespace. | ||||
@type user_ns: dictionary | ||||
@param user_global_ns: User global namespace. | ||||
@type user_global_ns: dictionary. | ||||
@param cin: Console standard input. | ||||
@type cin: IO stream | ||||
@param cout: Console standard output. | ||||
@type cout: IO stream | ||||
@param cerr: Console standard error. | ||||
@type cerr: IO stream | ||||
@param exit_handler: Replacement for builtin exit() function | ||||
@type exit_handler: function | ||||
@param time_loop: Define the sleep time between two thread's loop | ||||
@type int | ||||
''' | ||||
#ipython0 initialisation | ||||
Laurent Dufréchou
|
r1221 | self._IP = None | ||
laurent.dufrechou@gmail.com
|
r1816 | self.init_ipython0(argv, user_ns, user_global_ns, | ||
Ville M. Vainio
|
r1112 | cin, cout, cerr, | ||
ldufrechou
|
r1153 | ask_exit_handler) | ||
Ville M. Vainio
|
r1112 | |||
#vars used by _execute | ||||
self._iter_more = 0 | ||||
self._history_level = 0 | ||||
ldufrechou
|
r1177 | self._complete_sep = re.compile('[\s\{\}\[\]\(\)\=]') | ||
Ville M. Vainio
|
r1112 | self._prompt = str(self._IP.outputcache.prompt1).strip() | ||
#thread working vars | ||||
self._line_to_execute = '' | ||||
ldufrechou
|
r1621 | self._threading = True | ||
Ville M. Vainio
|
r1112 | #vars that will be checked by GUI loop to handle thread states... | ||
#will be replaced later by PostEvent GUI funtions... | ||||
self._doc_text = None | ||||
self._help_text = None | ||||
self._add_button = None | ||||
laurent.dufrechou@gmail.com
|
r1816 | def init_ipython0(self, argv=[], user_ns={}, user_global_ns=None, | ||
Ville M. Vainio
|
r1112 | cin=None, cout=None, cerr=None, | ||
ldufrechou
|
r1153 | ask_exit_handler=None): | ||
ldufrechou
|
r1621 | ''' Initialize an ipython0 instance ''' | ||
Laurent Dufréchou
|
r1221 | |||
ldufrechou
|
r1623 | #first we redefine in/out/error functions of IPython | ||
#BUG: we've got a limitation form ipython0 there | ||||
#only one instance can be instanciated else tehre will be | ||||
#cin/cout/cerr clash... | ||||
Ville M. Vainio
|
r1112 | if cin: | ||
Brian Granger
|
r2023 | genutils.Term.cin = cin | ||
Ville M. Vainio
|
r1112 | if cout: | ||
Brian Granger
|
r2023 | genutils.Term.cout = cout | ||
Ville M. Vainio
|
r1112 | if cerr: | ||
Brian Granger
|
r2023 | genutils.Term.cerr = cerr | ||
Ville M. Vainio
|
r1112 | |||
excepthook = sys.excepthook | ||||
ldufrechou
|
r1623 | |||
ldufrechou
|
r1587 | #Hack to save sys.displayhook, because ipython seems to overwrite it... | ||
self.sys_displayhook_ori = sys.displayhook | ||||
Brian Granger
|
r2046 | self._IP = IPython.shell.make_IPython( | ||
Laurent Dufréchou
|
r1221 | argv,user_ns=user_ns, | ||
user_global_ns=user_global_ns, | ||||
embedded=True, | ||||
Brian Granger
|
r2046 | shell_class=IPython.shell.InteractiveShell) | ||
Ville M. Vainio
|
r1112 | |||
ldufrechou
|
r1623 | #we save ipython0 displayhook and we restore sys.displayhook | ||
self.displayhook = sys.displayhook | ||||
ldufrechou
|
r1587 | sys.displayhook = self.sys_displayhook_ori | ||
Ville M. Vainio
|
r1112 | #we replace IPython default encoding by wx locale encoding | ||
loc = locale.getpreferredencoding() | ||||
if loc: | ||||
Laurent Dufréchou
|
r1221 | self._IP.stdin_encoding = loc | ||
Ville M. Vainio
|
r1112 | #we replace the ipython default pager by our pager | ||
Laurent Dufréchou
|
r1221 | self._IP.set_hook('show_in_pager', self._pager) | ||
Ville M. Vainio
|
r1112 | |||
laurent.dufrechou@gmail.com
|
r1817 | #we replace the ipython default shell command caller | ||
#by our shell handler | ||||
Laurent Dufréchou
|
r1221 | self._IP.set_hook('shell_hook', self._shell) | ||
Ville M. Vainio
|
r1112 | |||
#we replace the ipython default input command caller by our method | ||||
Brian Granger
|
r2028 | iplib.raw_input_original = self._raw_input_original | ||
Ville M. Vainio
|
r1112 | #we replace the ipython default exit command by our method | ||
self._IP.exit = ask_exit_handler | ||||
#we replace the help command | ||||
self._IP.user_ns['help'] = _Helper(self._pager_help) | ||||
ldufrechou
|
r1153 | |||
#we disable cpase magic... until we found a way to use it properly. | ||||
Brian Granger
|
r2027 | from IPython.core import ipapi | ||
ip = ipapi.get() | ||||
laurent.dufrechou@gmail.com
|
r1816 | def bypass_magic(self, arg): | ||
ldufrechou
|
r1153 | print '%this magic is currently disabled.' | ||
laurent.dufrechou@gmail.com
|
r1816 | ip.expose_magic('cpaste', bypass_magic) | ||
laurent dufrechou
|
r1808 | |||
laurent.dufrechou@gmail.com
|
r1818 | import __builtin__ | ||
__builtin__.raw_input = self._raw_input | ||||
Ville M. Vainio
|
r1112 | sys.excepthook = excepthook | ||
laurent.dufrechou@gmail.com
|
r1817 | #----------------------- Thread management section ---------------------- | ||
laurent.dufrechou@gmail.com
|
r1816 | def do_execute(self, line): | ||
Ville M. Vainio
|
r1112 | """ | ||
Tell the thread to process the 'line' command | ||||
""" | ||||
self._line_to_execute = line | ||||
ldufrechou
|
r1623 | |||
ldufrechou
|
r1621 | if self._threading: | ||
laurent.dufrechou@gmail.com
|
r1817 | #we launch the ipython line execution in a thread to make it | ||
#interruptible with include it in self namespace to be able | ||||
#to call ce.raise_exc(KeyboardInterrupt) | ||||
ldufrechou
|
r1621 | self.ce = _CodeExecutor(self) | ||
self.ce.start() | ||||
else: | ||||
try: | ||||
self._doc_text = None | ||||
self._help_text = None | ||||
self._execute() | ||||
# used for uper class to generate event after execution | ||||
laurent.dufrechou@gmail.com
|
r1816 | self._after_execute() | ||
ldufrechou
|
r1621 | |||
except KeyboardInterrupt: | ||||
pass | ||||
ldufrechou
|
r1623 | |||
laurent.dufrechou@gmail.com
|
r1817 | #----------------------- IPython management section ---------------------- | ||
laurent.dufrechou@gmail.com
|
r1816 | def get_threading(self): | ||
ldufrechou
|
r1621 | """ | ||
Returns threading status, is set to True, then each command sent to | ||||
the interpreter will be executed in a separated thread allowing, | ||||
for example, breaking a long running commands. | ||||
Disallowing it, permits better compatibilty with instance that is embedding | ||||
IPython instance. | ||||
@return: Execution method | ||||
@rtype: bool | ||||
""" | ||||
return self._threading | ||||
laurent.dufrechou@gmail.com
|
r1816 | def set_threading(self, state): | ||
ldufrechou
|
r1621 | """ | ||
Sets threading state, if set to True, then each command sent to | ||||
the interpreter will be executed in a separated thread allowing, | ||||
for example, breaking a long running commands. | ||||
Disallowing it, permits better compatibilty with instance that is embedding | ||||
IPython instance. | ||||
@param state: Sets threading state | ||||
@type bool | ||||
""" | ||||
self._threading = state | ||||
laurent.dufrechou@gmail.com
|
r1816 | def get_doc_text(self): | ||
Ville M. Vainio
|
r1112 | """ | ||
Returns the output of the processing that need to be paged (if any) | ||||
@return: The std output string. | ||||
@rtype: string | ||||
""" | ||||
return self._doc_text | ||||
laurent.dufrechou@gmail.com
|
r1816 | def get_help_text(self): | ||
Ville M. Vainio
|
r1112 | """ | ||
Returns the output of the processing that need to be paged via help pager(if any) | ||||
@return: The std output string. | ||||
@rtype: string | ||||
""" | ||||
return self._help_text | ||||
laurent.dufrechou@gmail.com
|
r1816 | def get_banner(self): | ||
Ville M. Vainio
|
r1112 | """ | ||
Returns the IPython banner for useful info on IPython instance | ||||
@return: The banner string. | ||||
@rtype: string | ||||
""" | ||||
return self._IP.BANNER | ||||
laurent.dufrechou@gmail.com
|
r1816 | def get_prompt_count(self): | ||
Ville M. Vainio
|
r1112 | """ | ||
Returns the prompt number. | ||||
Each time a user execute a line in the IPython shell the prompt count is increased | ||||
@return: The prompt number | ||||
@rtype: int | ||||
""" | ||||
return self._IP.outputcache.prompt_count | ||||
laurent.dufrechou@gmail.com
|
r1816 | def get_prompt(self): | ||
Ville M. Vainio
|
r1112 | """ | ||
Returns current prompt inside IPython instance | ||||
(Can be In [...]: ot ...:) | ||||
@return: The current prompt. | ||||
@rtype: string | ||||
""" | ||||
return self._prompt | ||||
laurent.dufrechou@gmail.com
|
r1816 | def get_indentation(self): | ||
Ville M. Vainio
|
r1112 | """ | ||
Returns the current indentation level | ||||
Usefull to put the caret at the good start position if we want to do autoindentation. | ||||
@return: The indentation level. | ||||
@rtype: int | ||||
""" | ||||
return self._IP.indent_current_nsp | ||||
laurent.dufrechou@gmail.com
|
r1816 | def update_namespace(self, ns_dict): | ||
Ville M. Vainio
|
r1112 | ''' | ||
Add the current dictionary to the shell namespace. | ||||
@param ns_dict: A dictionary of symbol-values. | ||||
@type ns_dict: dictionary | ||||
''' | ||||
self._IP.user_ns.update(ns_dict) | ||||
def complete(self, line): | ||||
''' | ||||
Returns an auto completed line and/or posibilities for completion. | ||||
@param line: Given line so far. | ||||
@type line: string | ||||
@return: Line completed as for as possible, | ||||
and possible further completions. | ||||
@rtype: tuple | ||||
''' | ||||
split_line = self._complete_sep.split(line) | ||||
possibilities = self._IP.complete(split_line[-1]) | ||||
if possibilities: | ||||
laurent.dufrechou@gmail.com
|
r1816 | def _common_prefix(str1, str2): | ||
Ville M. Vainio
|
r1112 | ''' | ||
Reduction function. returns common prefix of two given strings. | ||||
@param str1: First string. | ||||
@type str1: string | ||||
@param str2: Second string | ||||
@type str2: string | ||||
@return: Common prefix to both strings. | ||||
@rtype: string | ||||
''' | ||||
for i in range(len(str1)): | ||||
if not str2.startswith(str1[:i+1]): | ||||
return str1[:i] | ||||
return str1 | ||||
laurent.dufrechou@gmail.com
|
r1816 | common_prefix = reduce(_common_prefix, possibilities) | ||
Ville M. Vainio
|
r1112 | completed = line[:-len(split_line[-1])]+common_prefix | ||
else: | ||||
completed = line | ||||
return completed, possibilities | ||||
laurent.dufrechou@gmail.com
|
r1816 | def history_back(self): | ||
Ville M. Vainio
|
r1112 | ''' | ||
Provides one history command back. | ||||
@return: The command string. | ||||
@rtype: string | ||||
''' | ||||
history = '' | ||||
#the below while loop is used to suppress empty history lines | ||||
while((history == '' or history == '\n') and self._history_level >0): | ||||
Laurent Dufréchou
|
r1221 | if self._history_level >= 1: | ||
self._history_level -= 1 | ||||
laurent.dufrechou@gmail.com
|
r1816 | history = self._get_history() | ||
Ville M. Vainio
|
r1112 | return history | ||
laurent.dufrechou@gmail.com
|
r1816 | def history_forward(self): | ||
Ville M. Vainio
|
r1112 | ''' | ||
Provides one history command forward. | ||||
@return: The command string. | ||||
@rtype: string | ||||
''' | ||||
history = '' | ||||
#the below while loop is used to suppress empty history lines | ||||
Laurent Dufréchou
|
r1221 | while((history == '' or history == '\n') \ | ||
laurent.dufrechou@gmail.com
|
r1816 | and self._history_level <= self._get_history_max_index()): | ||
if self._history_level < self._get_history_max_index(): | ||||
Laurent Dufréchou
|
r1221 | self._history_level += 1 | ||
laurent.dufrechou@gmail.com
|
r1816 | history = self._get_history() | ||
Laurent Dufréchou
|
r1221 | else: | ||
laurent.dufrechou@gmail.com
|
r1816 | if self._history_level == self._get_history_max_index(): | ||
history = self._get_history() | ||||
Laurent Dufréchou
|
r1221 | self._history_level += 1 | ||
Ville M. Vainio
|
r1112 | else: | ||
Laurent Dufréchou
|
r1221 | history = '' | ||
Ville M. Vainio
|
r1112 | return history | ||
laurent.dufrechou@gmail.com
|
r1816 | def init_history_index(self): | ||
Ville M. Vainio
|
r1112 | ''' | ||
set history to last command entered | ||||
''' | ||||
laurent.dufrechou@gmail.com
|
r1816 | self._history_level = self._get_history_max_index()+1 | ||
Ville M. Vainio
|
r1112 | |||
#----------------------- IPython PRIVATE management section -------------- | ||||
laurent.dufrechou@gmail.com
|
r1816 | def _after_execute(self): | ||
Ville M. Vainio
|
r1112 | ''' | ||
Can be redefined to generate post event after excution is done | ||||
''' | ||||
pass | ||||
laurent.dufrechou@gmail.com
|
r1818 | def _ask_exit(self): | ||
''' | ||||
Can be redefined to generate post event to exit the Ipython shell | ||||
''' | ||||
pass | ||||
ldufrechou
|
r1153 | |||
laurent.dufrechou@gmail.com
|
r1816 | def _get_history_max_index(self): | ||
Ville M. Vainio
|
r1112 | ''' | ||
returns the max length of the history buffer | ||||
@return: history length | ||||
@rtype: int | ||||
''' | ||||
return len(self._IP.input_hist_raw)-1 | ||||
laurent.dufrechou@gmail.com
|
r1816 | def _get_history(self): | ||
Ville M. Vainio
|
r1112 | ''' | ||
Get's the command string of the current history level. | ||||
@return: Historic command stri | ||||
@rtype: string | ||||
''' | ||||
rv = self._IP.input_hist_raw[self._history_level].strip('\n') | ||||
return rv | ||||
Laurent Dufréchou
|
r1221 | def _pager_help(self, text): | ||
Ville M. Vainio
|
r1112 | ''' | ||
This function is used as a callback replacment to IPython help pager function | ||||
Laurent Dufréchou
|
r1221 | It puts the 'text' value inside the self._help_text string that can be retrived via | ||
laurent.dufrechou@gmail.com
|
r1816 | get_help_text function. | ||
Ville M. Vainio
|
r1112 | ''' | ||
if self._help_text == None: | ||||
self._help_text = text | ||||
else: | ||||
self._help_text += text | ||||
Laurent Dufréchou
|
r1221 | def _pager(self, IP, text): | ||
Ville M. Vainio
|
r1112 | ''' | ||
This function is used as a callback replacment to IPython pager function | ||||
Laurent Dufréchou
|
r1221 | It puts the 'text' value inside the self._doc_text string that can be retrived via | ||
laurent.dufrechou@gmail.com
|
r1816 | get_doc_text function. | ||
Ville M. Vainio
|
r1112 | ''' | ||
self._doc_text = text | ||||
laurent.dufrechou@gmail.com
|
r1818 | def _raw_input_original(self, prompt=''): | ||
ldufrechou
|
r1153 | ''' | ||
Custom raw_input() replacement. Get's current line from console buffer. | ||||
@param prompt: Prompt to print. Here for compatability as replacement. | ||||
@type prompt: string | ||||
@return: The current command line text. | ||||
@rtype: string | ||||
''' | ||||
return self._line_to_execute | ||||
laurent.dufrechou@gmail.com
|
r1818 | def _raw_input(self, prompt=''): | ||
""" A replacement from python's raw_input. | ||||
""" | ||||
raise NotImplementedError | ||||
Ville M. Vainio
|
r1112 | def _execute(self): | ||
''' | ||||
Executes the current line provided by the shell object. | ||||
''' | ||||
ldufrechou
|
r1623 | |||
Ville M. Vainio
|
r1112 | orig_stdout = sys.stdout | ||
Brian Granger
|
r2046 | sys.stdout = IPython.shell.Term.cout | ||
ldufrechou
|
r1623 | #self.sys_displayhook_ori = sys.displayhook | ||
#sys.displayhook = self.displayhook | ||||
Ville M. Vainio
|
r1112 | try: | ||
line = self._IP.raw_input(None, self._iter_more) | ||||
if self._IP.autoindent: | ||||
self._IP.readline_startup_hook(None) | ||||
except KeyboardInterrupt: | ||||
self._IP.write('\nKeyboardInterrupt\n') | ||||
self._IP.resetbuffer() | ||||
# keep cache in sync with the prompt counter: | ||||
self._IP.outputcache.prompt_count -= 1 | ||||
if self._IP.autoindent: | ||||
self._IP.indent_current_nsp = 0 | ||||
self._iter_more = 0 | ||||
except: | ||||
self._IP.showtraceback() | ||||
else: | ||||
laurent dufrechou
|
r1813 | self._IP.write(str(self._IP.outputcache.prompt_out).strip()) | ||
Ville M. Vainio
|
r1112 | self._iter_more = self._IP.push(line) | ||
laurent.dufrechou@gmail.com
|
r1817 | if (self._IP.SyntaxTB.last_syntax_error and \ | ||
self._IP.rc.autoedit_syntax): | ||||
Ville M. Vainio
|
r1112 | self._IP.edit_syntax_error() | ||
if self._iter_more: | ||||
self._prompt = str(self._IP.outputcache.prompt2).strip() | ||||
if self._IP.autoindent: | ||||
self._IP.readline_startup_hook(self._IP.pre_readline) | ||||
else: | ||||
self._prompt = str(self._IP.outputcache.prompt1).strip() | ||||
self._IP.indent_current_nsp = 0 #we set indentation to 0 | ||||
ldufrechou
|
r1623 | |||
Ville M. Vainio
|
r1112 | sys.stdout = orig_stdout | ||
ldufrechou
|
r1623 | #sys.displayhook = self.sys_displayhook_ori | ||
Ville M. Vainio
|
r1112 | def _shell(self, ip, cmd): | ||
''' | ||||
Replacement method to allow shell commands without them blocking. | ||||
@param ip: Ipython instance, same as self._IP | ||||
@type cmd: Ipython instance | ||||
@param cmd: Shell command to execute. | ||||
@type cmd: string | ||||
''' | ||||
stdin, stdout = os.popen4(cmd) | ||||
laurent.dufrechou@gmail.com
|
r1817 | result = stdout.read().decode('cp437').\ | ||
encode(locale.getpreferredencoding()) | ||||
Laurent Dufréchou
|
r1221 | #we use print command because the shell command is called | ||
#inside IPython instance and thus is redirected to thread cout | ||||
Ville M. Vainio
|
r1112 | #"\x01\x1b[1;36m\x02" <-- add colour to the text... | ||
print "\x01\x1b[1;36m\x02"+result | ||||
stdout.close() | ||||
stdin.close() | ||||