mirror of
https://github.com/XaverKlemenschits/jupyter-c-kernel.git
synced 2025-04-14 10:26:09 +00:00
Stdin using notebook raw_input (#1)
* Added stdin input via the notebook frontend * Added support for single line code snippets. * Updated README. * Updated Dockerfile. * Changed file cleanup to happen right after execution, rather than before kernel shutdown.
This commit is contained in:
parent
c9a327f77c
commit
ff9d635f89
@ -1,5 +1,5 @@
|
||||
FROM jupyter/minimal-notebook
|
||||
MAINTAINER Brendan Rius <ping@brendan-rius.com>
|
||||
MAINTAINER Xaver Klemenschits <klemenschits@iue.tuwien.ac.at>
|
||||
|
||||
USER root
|
||||
|
||||
@ -7,8 +7,8 @@ WORKDIR /tmp
|
||||
|
||||
COPY ./ jupyter_c_kernel/
|
||||
|
||||
RUN pip install --no-cache-dir jupyter_c_kernel/
|
||||
RUN cd jupyter_c_kernel && install_c_kernel --user
|
||||
RUN pip install --no-cache-dir -e jupyter_c_kernel/ > piplog.txt
|
||||
RUN cd jupyter_c_kernel && install_c_kernel --user > installlog.txt
|
||||
|
||||
WORKDIR /home/$NB_USER/
|
||||
|
||||
|
22
README.md
22
README.md
@ -2,10 +2,10 @@
|
||||
|
||||
## Use with Docker (recommended)
|
||||
|
||||
* `docker pull brendanrius/jupyter-c-kernel`
|
||||
* `docker run -p 8888:8888 brendanrius/jupyter-c-kernel`
|
||||
* `docker pull xaverklemenschits/jupyter-c-kernel`
|
||||
* `docker run -p 8888:8888 xaverklemenschits/jupyter-c-kernel`
|
||||
* Copy the given URL containing the token, and browse to it. For instance:
|
||||
|
||||
|
||||
```
|
||||
Copy/paste this URL into your browser when you connect for the first time,
|
||||
to login with a token:
|
||||
@ -24,9 +24,14 @@ Works only on Linux and OS X. Windows is not supported yet. If you want to use t
|
||||
* pip
|
||||
|
||||
### Step-by-step:
|
||||
* `pip install jupyter-c-kernel`
|
||||
* `install_c_kernel`
|
||||
* `jupyter-notebook`. Enjoy!
|
||||
```bash
|
||||
git clone https://github.com/XaverKlemenschits/jupyter-c-kernel.git
|
||||
cd jupyter-c-kernel
|
||||
pip install -e .
|
||||
cd jupyter_c_kernel && install_c_kernel --user
|
||||
# now you can start the notebook
|
||||
jupyter notebook
|
||||
```
|
||||
|
||||
## Example of notebook
|
||||
|
||||
@ -47,9 +52,10 @@ change the code in real-time in Docker. For that, just run the docker box like
|
||||
that:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/brendan-rius/jupyter-c-kernel.git
|
||||
git clone https://github.com/XaverKlemenschits/jupyter-c-kernel.git
|
||||
cd jupyter-c-kernel
|
||||
docker run -v $(pwd):/jupyter/jupyter_c_kernel/ -p 8888:8888 brendanrius/jupyter-c-kernel
|
||||
docker build -t myName/jupyter .
|
||||
docker run -v $(pwd):/tmp/jupyter_c_kernel/ -p 8888:8888 myName/jupyter
|
||||
```
|
||||
|
||||
This clones the source, run the kernel, and binds the current folder (the one
|
||||
|
@ -29,7 +29,7 @@ def install_my_kernel_spec(user=True, prefix=None):
|
||||
# TODO: Copy resources once they're specified
|
||||
|
||||
print('Installing IPython kernel spec')
|
||||
KernelSpecManager().install_kernel_spec(td, 'c', user=user, replace=True, prefix=prefix)
|
||||
KernelSpecManager().install_kernel_spec(td, 'c', user=user, prefix=prefix)
|
||||
|
||||
|
||||
def _is_root():
|
||||
|
@ -14,7 +14,9 @@ class RealTimeSubprocess(subprocess.Popen):
|
||||
A subprocess that allows to read its stdout and stderr in real time
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, write_to_stdout, write_to_stderr):
|
||||
inputRequest = "<inputRequest>"
|
||||
|
||||
def __init__(self, cmd, write_to_stdout, write_to_stderr, read_from_stdin):
|
||||
"""
|
||||
:param cmd: the command to execute
|
||||
:param write_to_stdout: a callable that will be called with chunks of data from stdout
|
||||
@ -22,8 +24,9 @@ class RealTimeSubprocess(subprocess.Popen):
|
||||
"""
|
||||
self._write_to_stdout = write_to_stdout
|
||||
self._write_to_stderr = write_to_stderr
|
||||
self._read_from_stdin = read_from_stdin
|
||||
|
||||
super().__init__(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
|
||||
super().__init__(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
|
||||
|
||||
self._stdout_queue = Queue()
|
||||
self._stdout_thread = Thread(target=RealTimeSubprocess._enqueue_output, args=(self.stdout, self._stdout_queue))
|
||||
@ -60,10 +63,23 @@ class RealTimeSubprocess(subprocess.Popen):
|
||||
|
||||
stdout_contents = read_all_from_queue(self._stdout_queue)
|
||||
if stdout_contents:
|
||||
self._write_to_stdout(stdout_contents)
|
||||
contents = stdout_contents.decode()
|
||||
# if there is input request, make output and then
|
||||
# ask frontend for input
|
||||
start = contents.find(self.__class__.inputRequest)
|
||||
if(start >= 0):
|
||||
contents = contents.replace(self.__class__.inputRequest, '')
|
||||
if(len(contents) > 0):
|
||||
self._write_to_stdout(contents)
|
||||
readLine = self._read_from_stdin()
|
||||
self.stdin.write(readLine.encode())
|
||||
self.stdin.write(b"\n")
|
||||
else:
|
||||
self._write_to_stdout(contents)
|
||||
|
||||
stderr_contents = read_all_from_queue(self._stderr_queue)
|
||||
if stderr_contents:
|
||||
self._write_to_stderr(stderr_contents)
|
||||
self._write_to_stderr(stderr_contents.decode())
|
||||
|
||||
|
||||
class CKernel(Kernel):
|
||||
@ -71,25 +87,36 @@ class CKernel(Kernel):
|
||||
implementation_version = '1.0'
|
||||
language = 'c'
|
||||
language_version = 'C11'
|
||||
language_info = {'name': 'c',
|
||||
language_info = {'name': 'text/x-csrc',
|
||||
'mimetype': 'text/plain',
|
||||
'file_extension': '.c'}
|
||||
banner = "C kernel.\n" \
|
||||
"Uses gcc, compiles in C11, and creates source code files and executables in temporary folder.\n"
|
||||
|
||||
main_head = "#include <stdio.h>\n" \
|
||||
"#include <math.h>\n" \
|
||||
"int main(){\n"
|
||||
|
||||
main_foot = "\nreturn 0;\n}"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CKernel, self).__init__(*args, **kwargs)
|
||||
self._allow_stdin = True
|
||||
self.files = []
|
||||
mastertemp = tempfile.mkstemp(suffix='.out')
|
||||
os.close(mastertemp[0])
|
||||
self.master_path = mastertemp[1]
|
||||
filepath = path.join(path.dirname(path.realpath(__file__)), 'resources', 'master.c')
|
||||
self.resDir = path.join(path.dirname(path.realpath(__file__)), 'resources')
|
||||
filepath = path.join(self.resDir, 'master.c')
|
||||
subprocess.call(['gcc', filepath, '-std=c11', '-rdynamic', '-ldl', '-o', self.master_path])
|
||||
|
||||
def cleanup_files(self):
|
||||
"""Remove all the temporary files created by the kernel"""
|
||||
# keep the list of files create in case there is an exception
|
||||
# before they can be deleted as usual
|
||||
for file in self.files:
|
||||
os.remove(file)
|
||||
if(os.path.exists(file)):
|
||||
os.remove(file)
|
||||
os.remove(self.master_path)
|
||||
|
||||
def new_temp_file(self, **kwargs):
|
||||
@ -107,10 +134,14 @@ class CKernel(Kernel):
|
||||
def _write_to_stderr(self, contents):
|
||||
self.send_response(self.iopub_socket, 'stream', {'name': 'stderr', 'text': contents})
|
||||
|
||||
def _read_from_stdin(self):
|
||||
return self.raw_input()
|
||||
|
||||
def create_jupyter_subprocess(self, cmd):
|
||||
return RealTimeSubprocess(cmd,
|
||||
lambda contents: self._write_to_stdout(contents.decode()),
|
||||
lambda contents: self._write_to_stderr(contents.decode()))
|
||||
self._write_to_stdout,
|
||||
self._write_to_stderr,
|
||||
self._read_from_stdin)
|
||||
|
||||
def compile_with_gcc(self, source_filename, binary_filename, cflags=None, ldflags=None):
|
||||
cflags = ['-std=c11', '-fPIC', '-shared', '-rdynamic'] + cflags
|
||||
@ -123,6 +154,8 @@ class CKernel(Kernel):
|
||||
'ldflags': [],
|
||||
'args': []}
|
||||
|
||||
actualCode = ''
|
||||
|
||||
for line in code.splitlines():
|
||||
if line.startswith('//%'):
|
||||
key, value = line[3:].split(":", 2)
|
||||
@ -136,12 +169,33 @@ class CKernel(Kernel):
|
||||
for argument in re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', value):
|
||||
magics['args'] += [argument.strip('"')]
|
||||
|
||||
return magics
|
||||
# only keep lines which did not contain magics
|
||||
else:
|
||||
actualCode += line + '\n'
|
||||
|
||||
return magics, actualCode
|
||||
|
||||
# check whether int main() is specified, if not add it around the code
|
||||
# also add common magics like -lm
|
||||
def _add_main(self, magics, code):
|
||||
x = re.search("int\s+main\s*\(", code)
|
||||
if x is None:
|
||||
code = self.main_head + code + self.main_foot
|
||||
magics['cflags'] += ['-lm']
|
||||
|
||||
return magics, code
|
||||
|
||||
def do_execute(self, code, silent, store_history=True,
|
||||
user_expressions=None, allow_stdin=False):
|
||||
user_expressions=None, allow_stdin=True):
|
||||
|
||||
magics = self._filter_magics(code)
|
||||
magics, code = self._filter_magics(code)
|
||||
|
||||
magics, code = self._add_main(magics, code)
|
||||
|
||||
# replace stdio with wrapped version
|
||||
headerDir = "\"" + self.resDir + "/stdio_wrap.h" + "\""
|
||||
code = code.replace("<stdio.h>", headerDir)
|
||||
code = code.replace("\"stdio.h\"", headerDir)
|
||||
|
||||
with self.new_temp_file(suffix='.c') as source_file:
|
||||
source_file.write(code)
|
||||
@ -155,6 +209,11 @@ class CKernel(Kernel):
|
||||
self._write_to_stderr(
|
||||
"[C kernel] GCC exited with code {}, the executable will not be executed".format(
|
||||
p.returncode))
|
||||
|
||||
# delete source files before exit
|
||||
os.remove(source_file.name)
|
||||
os.remove(binary_file.name)
|
||||
|
||||
return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [],
|
||||
'user_expressions': {}}
|
||||
|
||||
@ -163,6 +222,10 @@ class CKernel(Kernel):
|
||||
p.write_contents()
|
||||
p.write_contents()
|
||||
|
||||
# now remove the files we have just created
|
||||
os.remove(source_file.name)
|
||||
os.remove(binary_file.name)
|
||||
|
||||
if p.returncode != 0:
|
||||
self._write_to_stderr("[C kernel] Executable exited with code {}".format(p.returncode))
|
||||
return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}}
|
||||
|
10
jupyter_c_kernel/resources/stdio_wrap.h
Normal file
10
jupyter_c_kernel/resources/stdio_wrap.h
Normal file
@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
|
||||
/* Replace all the necessary input functions */
|
||||
#define scanf(...) printf("<inputRequest>");\
|
||||
fflush(stdout);\
|
||||
scanf(__VA_ARGS__);
|
||||
|
||||
#define getchar() printf("<inputRequest>");\
|
||||
fflush(stdout);\
|
||||
getchar();
|
Loading…
x
Reference in New Issue
Block a user