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:
Xaver K 2020-03-22 12:52:45 +01:00 committed by GitHub
parent c9a327f77c
commit ff9d635f89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 24 deletions

View File

@ -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/

View File

@ -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

View File

@ -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():

View File

@ -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': {}}

View 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();