Major rewrite

main
Nils Bruin 7 years ago
parent ce85257bf7
commit 31082f3de2

1
.gitignore vendored

@ -2,4 +2,5 @@ __pycache__
*.pyc *.pyc
build/ build/
dist/ dist/
magma_kernel.egg-info/
MANIFEST MANIFEST

@ -1,4 +1,6 @@
Copyright (c) 2015, Thomas Kluyver and contributors Copyright (c) 2015, Thomas Kluyver and contributors
Copyright (c) 2018, Nils Bruin and contributors
All rights reserved. All rights reserved.
BSD 3-clause license: BSD 3-clause license:

@ -0,0 +1 @@
include README.rst

@ -1,22 +1,24 @@
A simple IPython kernel for bash A simple Jupyter kernel for the Magma computer algebra system
This requires IPython 3. This requires IPython 3.
To install:: To install::
pip install bash_kernel pip install magma_kernel
python -m bash_kernel.install python -m magma_kernel.install
To use it, run one of: To use it, run one of:
.. code:: shell .. code:: shell
ipython notebook jupyter notebook
# In the notebook interface, select Bash from the 'New' menu # In the notebook interface, select Magma from the 'New' menu
ipython qtconsole --kernel bash jupyter qtconsole --kernel magma
ipython console --kernel bash jupyter console --kernel magma
For details of how this works, see the Jupyter docs on `wrapper kernels This code is based on a Magma kernel for IPython written by Christopher
<http://jupyter-client.readthedocs.org/en/latest/wrapperkernels.html>`_, and Granade, which was in turn based on the Bash example kernel by Thomas
Pexpect's docs on the `replwrap module Kluyver. Improvements made in the current version include Tab
<http://pexpect.readthedocs.org/en/latest/api/replwrap.html>`_ completion, processing of help requests by returning an appropriate
help query URL for Magma online documentation, and the reporting of
partial output.

@ -1,11 +0,0 @@
[metadata]
module = magma_kernel
author = Chris Granade
author-email = cgranade@cgranade.com
home-page = https://github.com/takluyver/magma_kernel
requires = pexpect (>=3.3)
description-file = README.rst
classifiers = Framework :: IPython
License :: OSI Approved :: BSD License
Programming Language :: Python :: 3

@ -1,3 +1,3 @@
"""A magma kernel for Jupyter""" """A magma kernel for Jupyter"""
__version__ = '0.0.1dev1' from .kernel import __version__

@ -1,48 +0,0 @@
import base64
import imghdr
import os
#from IPython.
_TEXT_SAVED_IMAGE = "bash_kernel: saved image data to:"
image_setup_cmd = """
display () {
TMPFILE=$(mktemp ${TMPDIR-/tmp}/bash_kernel.XXXXXXXXXX)
cat > $TMPFILE
echo "%s $TMPFILE" >&2
}
""" % _TEXT_SAVED_IMAGE
def display_data_for_image(filename):
with open(filename, 'rb') as f:
image = f.read()
os.unlink(filename)
image_type = imghdr.what(None, image)
if image_type is None:
raise ValueError("Not a valid image: %s" % image)
image_data = base64.b64encode(image).decode('ascii')
content = {
'data': {
'image/' + image_type: image_data
},
'metadata': {}
}
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

@ -1,18 +1,19 @@
import json import json
import os import os
import sys import sys
import argparse
from jupyter_client.kernelspec import install_kernel_spec from jupyter_client.kernelspec import KernelSpecManager
from IPython.utils.tempdir import TemporaryDirectory from IPython.utils.tempdir import TemporaryDirectory
kernel_json = {"argv":[sys.executable,"-m","magma_kernel", "-f", "{connection_file}"], kernel_json = {"argv":[sys.executable,"-m","bash_kernel", "-f", "{connection_file}"],
"display_name":"Magma", "display_name":"Magma",
"language":"magma", "language":"magma",
"codemirror_mode":"magma", "codemirror_mode":"magma",
"env":{"PS1": "$"} "env":{"PS1": "$"}
} }
def install_my_kernel_spec(user=True): def install_my_kernel_spec(user=True, prefix=None):
with TemporaryDirectory() as td: with TemporaryDirectory() as td:
os.chmod(td, 0o755) # Starts off as 700, not user readable os.chmod(td, 0o755) # Starts off as 700, not user readable
with open(os.path.join(td, 'kernel.json'), 'w') as f: with open(os.path.join(td, 'kernel.json'), 'w') as f:
@ -20,7 +21,7 @@ def install_my_kernel_spec(user=True):
# TODO: Copy resources once they're specified # TODO: Copy resources once they're specified
print('Installing IPython kernel spec') print('Installing IPython kernel spec')
install_kernel_spec(td, 'magma', user=user, replace=True) KernelSpecManager().install_kernel_spec(td, 'magma', user=user, replace=True, prefix=prefix)
def _is_root(): def _is_root():
try: try:
@ -28,9 +29,41 @@ def _is_root():
except AttributeError: except AttributeError:
return False # assume not an admin on non-Unix platforms return False # assume not an admin on non-Unix platforms
def main(argv=[]): def main(argv=None):
user = '--user' in argv or not _is_root() parser = argparse.ArgumentParser(
install_my_kernel_spec(user=user) description='Install KernelSpec for Bash Kernel'
)
prefix_locations = parser.add_mutually_exclusive_group()
prefix_locations.add_argument(
'--user',
help='Install KernelSpec in user homedirectory',
action='store_true'
)
prefix_locations.add_argument(
'--sys-prefix',
help='Install KernelSpec in sys.prefix. Useful in conda / virtualenv',
action='store_true',
dest='sys_prefix'
)
prefix_locations.add_argument(
'--prefix',
help='Install KernelSpec in this prefix',
default=None
)
args = parser.parse_args(argv)
user = False
prefix = None
if args.sys_prefix:
prefix = sys.prefix
elif args.prefix:
prefix = args.prefix
elif args.user or not _is_root():
user = True
install_my_kernel_spec(user=user, prefix=prefix)
if __name__ == '__main__': if __name__ == '__main__':
main(argv=sys.argv) main()

@ -1,129 +1,170 @@
from ipykernel.kernelbase import Kernel from ipykernel.kernelbase import Kernel
from pexpect import replwrap, EOF, spawn from pexpect import TIMEOUT, EOF, spawn
from subprocess import Popen, PIPE
from pathlib import Path
import jupyter_core.paths
from subprocess import check_output
from os import unlink
import base64
import imghdr
import re
import signal import signal
import urllib import re
import glob
__version__ = '0.0.1dev1'
version_pat = re.compile(r'version (\d+(\.\d+)+)')
from .images import (
extract_image_filenames, display_data_for_image, image_setup_cmd
)
__version__ = '0.1a1'
class MagmaKernel(Kernel): class MagmaKernel(Kernel):
implementation = 'magma_kernel' implementation = 'magma_kernel'
implementation_version = __version__ implementation_version = __version__
'''
@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:
self._banner = check_output(['bash', '--version']).decode('utf-8')
return self._banner
'''
@property
def banner(self):
return ""
language_info = {'name': 'magma', language_info = {'name': 'magma',
'codemirror_mode': 'magma', 'codemirror_mode': 'python',
'mimetype': 'text/x-sh', 'mimetype': 'text/x-magma',
'file_extension': '.mgm'} 'file_extension': '.m'}
def __init__(self, **kwargs): def __init__(self, **kwargs):
Kernel.__init__(self, **kwargs) Kernel.__init__(self, **kwargs)
self._prompt="$PEXPECT_PROMPT$"
self._start_magma() self._start_magma()
self.completions = self._fetch_completions()
def _fetch_completions(self):
P=Path(jupyter_core.paths.jupyter_data_dir())
P=P.joinpath('kernels')
if not P.exists():
P.mkdir()
P=P.joinpath('magma')
if not P.exists():
P.mkdir()
P=P.joinpath('magma-completions.'+self.language_info['version'])
if P.exists():
with P.open('r') as F:
completions = F.read().split()
else:
child=Popen("magma",stdin=PIPE,stdout=PIPE)
child.stdin.write("ListSignatures(Any);".encode())
child.stdin.close();
result=child.stdout.read().decode().split()
completions = sorted({r[:r.index('(')] for r in result if '(' in r})
with P.open('w') as F:
F.write('\n'.join(completions))
return completions
def _start_magma(self): def _start_magma(self):
# Signal handlers are inherited by forked processes, and we can't easily # Signal handlers are inherited by forked processes, and we can't easily
# reset it from the subprocess. Since kernelapp ignores SIGINT except in # reset it from the subprocess. Since kernelapp ignores SIGINT except in
# message handlers, we need to temporarily reset the SIGINT handler here # message handlers, we need to temporarily reset the SIGINT handler here
# so that bash and its children are interruptible. # so that magma and its children are interruptible.
sig = signal.signal(signal.SIGINT, signal.SIG_DFL) sig = signal.signal(signal.SIGINT, signal.SIG_DFL)
try: try:
magma = spawn('magma', echo=False, encoding='utf-8') magma = spawn('magma', echo=False, encoding='utf-8')
magma.expect('> ') magma.expect_exact('> ')
banner=magma.before
magma.sendline('SetLineEditor(false);') magma.sendline('SetLineEditor(false);')
magma.expect('> ') magma.expect_exact('> ')
magma.sendline('') magma.sendline('SetColumns(0);')
self.magmawrapper = replwrap.REPLWrapper(magma, '> ', 'SetPrompt("{}");') magma.expect_exact('> ')
magma.sendline('SetPrompt("{}");'.format(self._prompt));
magma.expect_exact(self._prompt)
self.child = magma
finally: finally:
signal.signal(signal.SIGINT, sig) signal.signal(signal.SIGINT, sig)
lang_version = re.search("Magma V(\d*.\d*-\d*)",banner).group(1)
# Register Bash function to write image data to temporary file self.banner = "Magma kernel connected to Magma "+lang_version
# self.magmawrapper.run_command(image_setup_cmd) self.language_info['version']=lang_version
self.language_version=lang_version
def do_help(self, keyword):
URL="http://magma.maths.usyd.edu.au/magma/handbook/search?chapters=1&examples=1&intrinsics=1&query="+keyword
content = {
'source': 'stdout',
'data': {
# 'text/html':'<iframe src="{}" title="iframe">help</iframe>'.format(URL)
'text/html':'<a href="{}" target="magma_help">Magma help on {}</a>'.format(URL,keyword)
}
}
self.send_response(self.iopub_socket, 'display_data', content)
# we could look locally for help, but since these would be "file" URLs
# we cannot redirect to them anyway.
# pattern = re.compile('.*(<A HREF = ".*htm.*".*{}.*</A>)'.format(keyword))
# results = set()
# for name in glob.glob('/usr/local/magma/2.23-9/doc/html/ind*.htm'):
# with open(name,'r') as f:
# for r in f.readlines():
# match=pattern.match(r)
# if match:
# line = '<A target="magma_help" '+match.group(1)[2:]
# results.add(line)
# content['data']['text/html']="".join(sorted(results))
# self.send_response(self.iopub_socket, 'display_data', content)
def do_execute(self, code, silent, store_history=True, def do_execute(self, code, silent, store_history=True,
user_expressions=None, allow_stdin=False): user_expressions=None, allow_stdin=False):
if not code.strip(): if not code.strip():
return {'status': 'ok', 'execution_count': self.execution_count, return {'status': 'ok', 'execution_count': self.execution_count,
'payload': [], 'user_expressions': {}} 'payload': [], 'user_expressions': {}}
if code[0] == '?':
self.do_help(code[1:])
return {'status': 'ok', 'execution_count': self.execution_count,
'payload': [], 'user_expressions': {}}
interrupted = False interrupted = False
cmdlines = code.splitlines()
C=self.child
try: try:
output = self.magmawrapper.run_command(code.rstrip(), timeout=None) for line in cmdlines:
C.sendline(line)
j = 0
#We use a fairly short timeout intercept and send back
#updates on what is received on stdout, if there is any.
counter=10
timeout=1
while True:
v=C.expect_exact([self._prompt, TIMEOUT],timeout=timeout)
if not silent and len(C.before) > j:
stream_content = {'name': 'stdout', 'text': C.before[j:]}
self.send_response(self.iopub_socket, 'stream', stream_content)
j=len(C.before)
if v==0:
break
counter-=1
if counter<=0:
timeout=min(300,2*counter)
counter=10
output=C.before[j:]
except KeyboardInterrupt: except KeyboardInterrupt:
self.magmawrapper.child.sendintr() C.sendintr()
interrupted = True interrupted = True
self.magmawrapper._expect_prompt() C.expect_exact(self._prompt)
output = self.magmawrapper.child.before output = C.before[j:]
except EOF: except EOF:
output = self.magmawrapper.child.before + 'Restarting Bash' output = C.before[j:] + 'Restarting Magma'
self._start_magma() self._start_magma()
if not silent: if not silent:
#image_filenames, output = extract_image_filenames(output)
# Send standard output
stream_content = {'name': 'stdout', 'text': output} stream_content = {'name': 'stdout', 'text': output}
self.send_response(self.iopub_socket, 'stream', stream_content) self.send_response(self.iopub_socket, 'stream', stream_content)
# Send images, if any
'''
for filename in image_filenames:
try:
data = display_data_for_image(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: if interrupted:
return {'status': 'abort', 'execution_count': self.execution_count} return {'status': 'abort', 'execution_count': self.execution_count}
'''
try:
exitcode = int(self.bashwrapper.run_command('echo $?').rstrip())
except Exception:
exitcode = 1
if exitcode:
error_content = {'execution_count': self.execution_count,
'ename': '', 'evalue': str(exitcode), 'traceback': []}
self.send_response(self.iopub_socket, 'error', error_content)
error_content['status'] = 'error'
return error_content
else:
'''
return {'status': 'ok', 'execution_count': self.execution_count, return {'status': 'ok', 'execution_count': self.execution_count,
'payload': [], 'user_expressions': {}} 'payload': [], 'user_expressions': {}}
def do_complete(self, code, cursor_pos):
cursor_end = cursor_pos
cursor_start = cursor_end
while cursor_start>0 and code[cursor_start-1] in "abcdefghijklmnopqrstyuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_":
cursor_start -= 1
if cursor_start<cursor_end:
fragment = code[cursor_start:cursor_end]
length = len(fragment)
matches = [C for C in self.completions if fragment == C[:length]]
else:
matches = []
return {'status': 'ok',
'matches' : matches,
'cursor_start' : cursor_start,
'cursor_end' : cursor_end,
'metadata' : {}}

@ -0,0 +1,25 @@
from setuptools import setup, find_packages
def readme():
with open('README.rst') as f:
return f.read()
setup(
name="magma_kernel",
version="0.1a1",
author="Nils Bruin",
author_email="nbruin@sfu.ca",
description="A Jupyter kernel for the Magma computer algebra system",
long_description=readme(),
keywords="jupyter kernel magma",
license="BSD",
url="https://github.com/nbruin/magma_kernel",
install_requires="pexpect>=3.3",
packages=find_packages(),
include_package_data=True,
classifiers=[
"Framework :: IPython",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
]
)
Loading…
Cancel
Save