diff --git a/bash_kernel.py b/bash_kernel.py index 0bcf501..a90837b 100644 --- a/bash_kernel.py +++ b/bash_kernel.py @@ -1,24 +1,34 @@ from IPython.kernel.zmq.kernelbase import Kernel from pexpect import replwrap, EOF -import signal from subprocess import check_output +from os import unlink + +import base64 +import imghdr import re +import signal +import urllib __version__ = '0.2' version_pat = re.compile(r'version (\d+(\.\d+)+)') +_TEXT_SAVED_IMAGE = "bash_kernel: saved image data to:" + + class BashKernel(Kernel): implementation = 'bash_kernel' implementation_version = __version__ language = 'bash' + @property def language_version(self): m = version_pat.search(self.banner) return m.group(1) _banner = None + @property def banner(self): if self._banner is None: @@ -27,9 +37,8 @@ class BashKernel(Kernel): language_info = {'codemirror_mode': 'shell', 'mimetype': 'text/x-sh', - 'file_extension': 'sh' - } - + 'file_extension': 'sh'} + def __init__(self, **kwargs): Kernel.__init__(self, **kwargs) self._start_bash() @@ -45,8 +54,18 @@ class BashKernel(Kernel): finally: signal.signal(signal.SIGINT, sig) - def do_execute(self, code, silent, store_history=True, user_expressions=None, - allow_stdin=False): + # Register Bash function to write image data to temporary file + bash_rc = """ + display () { + TMPFILE=$(mktemp ${TMPDIR-/tmp}/bash_kernel.XXXXXXXXXX) + cat > $TMPFILE + echo "%s $TMPFILE" >&2 + } + """ % _TEXT_SAVED_IMAGE + self.bashwrapper.run_command(bash_rc) + + def do_execute(self, code, silent, store_history=True, + user_expressions=None, allow_stdin=False): if not code.strip(): return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}} @@ -64,12 +83,25 @@ class BashKernel(Kernel): self._start_bash() if not silent: + image_filenames, output = extract_image_filenames(output) + + # Send standard output stream_content = {'name': 'stdout', 'text': output} self.send_response(self.iopub_socket, 'stream', stream_content) - + + # Send images, if any + for filename in image_filenames: + try: + data = display_data(filename) + except ValueError as e: + message = {'name': 'stdout', 'text': str(e)} + self.send_response(self.iopub_socket, 'stream', message) + else: + self.send_response(self.iopub_socket, 'display_data', data) + if interrupted: return {'status': 'abort', 'execution_count': self.execution_count} - + try: exitcode = int(self.bashwrapper.run_command('echo $?').rstrip()) except Exception: @@ -109,6 +141,40 @@ class BashKernel(Kernel): 'cursor_end': cursor_pos, 'metadata': dict(), 'status': 'ok'} + +def display_data(filename): + with open(filename, 'rb') as f: + image = f.read() + unlink(filename) + + image_type = imghdr.what(None, image) + if image_type is None: + raise ValueError("Not a valid image: %s" % image) + + image_data = urllib.parse.quote(base64.b64encode(image)) + content = { + 'source': 'kernel', + 'data': { + 'image/' + image_type: image_data + } + } + return content + + +def extract_image_filenames(output): + output_lines = [] + image_filenames = [] + + for line in output.split("\n"): + if line.startswith(_TEXT_SAVED_IMAGE): + filename = line.rstrip().split(": ")[-1] + image_filenames.append(filename) + else: + output_lines.append(line) + + output = "\n".join(output_lines) + return image_filenames, output + if __name__ == '__main__': from IPython.kernel.zmq.kernelapp import IPKernelApp IPKernelApp.launch_instance(kernel_class=BashKernel)