From fe6de313e7dd30fce55a63921efd1caa99d104d4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Jul 2014 06:11:17 -0500 Subject: [PATCH 1/6] Fix handling of exitcode, name of "payload" key, add stub for shutdown. --- bash_kernel.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bash_kernel.py b/bash_kernel.py index 00cc795..3e9c491 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -1,5 +1,6 @@ from IPython.kernel.zmq.kernelbase import Kernel from pexpect import replwrap +import pexpect import signal from subprocess import check_output @@ -41,7 +42,7 @@ class BashKernel(Kernel): allow_stdin=False): if not code.strip(): return {'status': 'ok', 'execution_count': self.execution_count, - 'payloads': [], 'user_expressions': {}} + 'payload': [], 'user_expressions': {}} interrupted = False try: @@ -51,16 +52,19 @@ class BashKernel(Kernel): interrupted = True self.bashwrapper._expect_prompt() output = self.bashwrapper.child.before + except pexpect.EOF: + # TODO: how do we shut down gracefully here? + output = '' if not silent: - stream_content = {'name': 'stdout', 'data':output} + stream_content = {'name': 'stdout', 'data': output} self.send_response(self.iopub_socket, 'stream', stream_content) if interrupted: return {'status': 'abort', 'execution_count': self.execution_count} try: - exitcode = int(self.run_command('echo $?').rstrip()) + exitcode = int(self.bashwrapper.run_command('echo $?').rstrip()) except Exception: exitcode = 1 @@ -69,7 +73,7 @@ class BashKernel(Kernel): 'ename': '', 'evalue': str(exitcode), 'traceback': []} else: return {'status': 'ok', 'execution_count': self.execution_count, - 'payloads': [], 'user_expressions': {}} + 'payload': [], 'user_expressions': {}} if __name__ == '__main__': from IPython.kernel.zmq.kernelapp import IPKernelApp From 59f5dc5e3e2dd9afcdb41b24394fa42d47a97b87 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Jul 2014 14:49:56 -0500 Subject: [PATCH 2/6] Add code completion using bash compgen command. --- bash_kernel.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bash_kernel.py b/bash_kernel.py index 3e9c491..1959ff0 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -75,6 +75,24 @@ class BashKernel(Kernel): return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}} + def do_complete(self, code, cursor_pos): + code = code[:cursor_pos] + if code[-1] == ' ': + return + tokens = code.replace(';', ' ').split() + if not tokens: + return + token = tokens[-1] + # check for valid function name + if not re.match('\A[a-zA-Z_]', token): + return + start = cursor_pos - len(code) + cmd = 'compgen -c %s' % token + output = self.bashwrapper.run_command(cmd).rstrip() + return {'matches': output.split(), 'cursor_start': start, + 'cursor_end': cursor_pos, 'metadata': dict(), + 'status': 'ok'} + if __name__ == '__main__': from IPython.kernel.zmq.kernelapp import IPKernelApp IPKernelApp.launch_instance(kernel_class=BashKernel) From f8629ab8ff5742620a16530776091bf3fdf0e78a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Jul 2014 14:52:01 -0500 Subject: [PATCH 3/6] Use length of token not len of code for start_pos --- bash_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash_kernel.py b/bash_kernel.py index 1959ff0..c8b63e0 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -86,7 +86,7 @@ class BashKernel(Kernel): # check for valid function name if not re.match('\A[a-zA-Z_]', token): return - start = cursor_pos - len(code) + start = cursor_pos - len(token) cmd = 'compgen -c %s' % token output = self.bashwrapper.run_command(cmd).rstrip() return {'matches': output.split(), 'cursor_start': start, From 5a3d3b795ccdae5df700e2ee8042b138a088fb23 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Jul 2014 20:02:50 -0500 Subject: [PATCH 4/6] Allow completions to work for files, directories, and aliases. --- bash_kernel.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bash_kernel.py b/bash_kernel.py index c8b63e0..2318e08 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -77,19 +77,23 @@ class BashKernel(Kernel): def do_complete(self, code, cursor_pos): code = code[:cursor_pos] + default = {'matches': [], 'cursor_start': 0, + 'cursor_end': cursor_pos, 'metadata': dict(), + 'status': 'ok'} if code[-1] == ' ': - return + return default tokens = code.replace(';', ' ').split() if not tokens: - return + return default token = tokens[-1] - # check for valid function name - if not re.match('\A[a-zA-Z_]', token): - return start = cursor_pos - len(token) - cmd = 'compgen -c %s' % token + cmd = 'compgen -cdfa %s' % token output = self.bashwrapper.run_command(cmd).rstrip() - return {'matches': output.split(), 'cursor_start': start, + matches = output.split() + if not matches: + return default + matches = [m for m in matches if m.startswith(token)] + return {'matches': matches, 'cursor_start': start, 'cursor_end': cursor_pos, 'metadata': dict(), 'status': 'ok'} From 41bc603506cddf0edb74bd5f7f863ef88f6bddc5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Jul 2014 20:52:19 -0500 Subject: [PATCH 5/6] Fix possible IndexError and add whitespace --- bash_kernel.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bash_kernel.py b/bash_kernel.py index 2318e08..e514b4a 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -80,19 +80,24 @@ class BashKernel(Kernel): default = {'matches': [], 'cursor_start': 0, 'cursor_end': cursor_pos, 'metadata': dict(), 'status': 'ok'} - if code[-1] == ' ': + + if not code or code[-1] == ' ': return default + tokens = code.replace(';', ' ').split() if not tokens: return default + token = tokens[-1] start = cursor_pos - len(token) cmd = 'compgen -cdfa %s' % token output = self.bashwrapper.run_command(cmd).rstrip() + matches = output.split() if not matches: return default matches = [m for m in matches if m.startswith(token)] + return {'matches': matches, 'cursor_start': start, 'cursor_end': cursor_pos, 'metadata': dict(), 'status': 'ok'} From 8da70a352afb905e4d20d51a318d97ca88624e98 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Jul 2014 21:00:05 -0500 Subject: [PATCH 6/6] Restart bash if we get an EOF error. Restart bash if there are any errors Limit catches to pexpect.EOF, print previous output Catch keyboard interrupts while waiting for bash prompt Revert try/catch around expect_prompt --- bash_kernel.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bash_kernel.py b/bash_kernel.py index e514b4a..631b1a8 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -1,6 +1,5 @@ from IPython.kernel.zmq.kernelbase import Kernel -from pexpect import replwrap -import pexpect +from pexpect import replwrap, EOF import signal from subprocess import check_output @@ -28,6 +27,9 @@ class BashKernel(Kernel): def __init__(self, **kwargs): Kernel.__init__(self, **kwargs) + self._start_bash() + + def _start_bash(self): # Signal handlers are inherited by forked processes, and we can't easily # reset it from the subprocess. Since kernelapp ignores SIGINT except in # message handlers, we need to temporarily reset the SIGINT handler here @@ -52,9 +54,9 @@ class BashKernel(Kernel): interrupted = True self.bashwrapper._expect_prompt() output = self.bashwrapper.child.before - except pexpect.EOF: - # TODO: how do we shut down gracefully here? - output = '' + except EOF: + output = self.bashwrapper.child.before + 'Restarting Bash' + self._start_bash() if not silent: stream_content = {'name': 'stdout', 'data': output}