Major rewrite
parent
ce85257bf7
commit
31082f3de2
@ -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,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…
Reference in New Issue