diff --git a/previewer_jupyter/MANIFEST.in b/previewer_jupyter/MANIFEST.in new file mode 100644 index 0000000..eea6f48 --- /dev/null +++ b/previewer_jupyter/MANIFEST.in @@ -0,0 +1 @@ +graft indico_previewer_jupyter/templates diff --git a/previewer_jupyter/indico_previewer_jupyter/__init__.py b/previewer_jupyter/indico_previewer_jupyter/__init__.py new file mode 100644 index 0000000..061ae9e --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/__init__.py @@ -0,0 +1,21 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +"""Syntax highlighting for IPython/Jupyter Notebooks.""" + +from .plugin import JupyterPreviewerPlugin + +__all__ = ('JupyterPreviewerPlugin',) diff --git a/previewer_jupyter/indico_previewer_jupyter/blueprint.py b/previewer_jupyter/indico_previewer_jupyter/blueprint.py new file mode 100644 index 0000000..eb6691c --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/blueprint.py @@ -0,0 +1,22 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from indico.core.plugins import IndicoPluginBlueprint +from indico_previewer_jupyter.controllers import RHEventPreviewIPyNB + + +blueprint = IndicoPluginBlueprint('previewer_jupyter', __name__) +blueprint.add_url_rule('/preview/ipynb/', 'preview_ipynb', RHEventPreviewIPyNB) diff --git a/previewer_jupyter/indico_previewer_jupyter/controllers.py b/previewer_jupyter/indico_previewer_jupyter/controllers.py new file mode 100644 index 0000000..b4bdc8e --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/controllers.py @@ -0,0 +1,53 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +import nbformat +from flask import session, render_template, request +from nbconvert.exporters import HTMLExporter +from traitlets.config import Config +from werkzeug.exceptions import Forbidden + +from indico.modules.attachments import Attachment + +from MaKaC.webinterface.rh.base import RH + +from indico_previewer_jupyter.cpp_highlighter import CppHighlighter + + +class RHEventPreviewIPyNB(RH): + def _checkProtection(self): + RH._checkProtection(self) + if not self.attachment.can_access(session.user): + raise Forbidden + + def _checkParams(self, params): + RH._checkParams(self, params) + self.attachment = Attachment.find_one(id=request.view_args['attachment_id'], is_deleted=False) + + def _process(self): + config = Config() + config.HTMLExporter.preprocessors = [CppHighlighter] + config.HTMLExporter.template_file = 'full' + + with self.attachment.file.open() as f: + notebook = nbformat.read(f, as_version=4) + + html_exporter = HTMLExporter(config=config) + body, resources = html_exporter.from_notebook_node(notebook) + css_code = '\n'.join(resources['inlining'].get('css', [])) + + return render_template('previewer_jupyter:ipynb_preview.html', attachment=self.attachment, + html_code=body, css_code=css_code) diff --git a/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py b/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py new file mode 100644 index 0000000..5e215bd --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/cpp_highlighter.py @@ -0,0 +1,73 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +""" +This preprocessor marks cells' metadata so that the appropriate highlighter can be used in the `highlight` filter. + +More precisely, the language of a cell is set to C++ in two scenarios: + - Python notebooks: cells with `%%cpp` or `%%dcl` magic extensions. + - ROOT prompt C++ notebooks: all cells. +This preprocessor relies on the metadata section of the notebook to find out about the notebook's language. + +Code courtesy of the ROOT project (https://root.cern.ch). +""" + +import re +from nbconvert.preprocessors.base import Preprocessor + + +class CppHighlighter(Preprocessor): + + """Detects and tags code cells that use the C++ language.""" + + magics = ['%%cpp', '%%dcl'] + cpp = 'cpp' + python = 'python' + + def __init__(self, config=None, **kw): + super(CppHighlighter, self).__init__(config=config, **kw) + + # Build regular expressions to catch language extensions or switches and choose + # an adequate pygments lexer + any_magic = "|".join(self.magics) + self.re_magic_language = re.compile(r"^\s*({0}).*".format(any_magic), re.DOTALL) + + def matches(self, source, reg_expr): + return bool(reg_expr.match(source)) + + def _preprocess_cell_python(self, cell, resources, cell_index): + # Mark %%cpp and %%dcl code cells as cpp + if cell.cell_type == "code" and self.matches(cell.source, self.re_magic_language): + cell['metadata']['magics_language'] = self.cpp + + return cell, resources + + def _preprocess_cell_cpp(self, cell, resources, cell_index): + # Mark all code cells as cpp + if cell.cell_type == "code": + cell['metadata']['magics_language'] = self.cpp + + return cell, resources + + def preprocess(self, nb, resources): + self.preprocess_cell = self._preprocess_cell_python + try: + if nb.metadata.kernelspec.language == "c++": + self.preprocess_cell = self._preprocess_cell_cpp + except: + # if no language metadata, keep python as default + pass + return super(CppHighlighter, self).preprocess(nb, resources) diff --git a/previewer_jupyter/indico_previewer_jupyter/plugin.py b/previewer_jupyter/indico_previewer_jupyter/plugin.py new file mode 100644 index 0000000..4425dee --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/plugin.py @@ -0,0 +1,61 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +import mimetypes +import re + +from flask import render_template + +from indico.core import signals +from indico.core.plugins import IndicoPlugin, url_for_plugin +from indico.modules.attachments.preview import Previewer + +from indico_previewer_jupyter.blueprint import blueprint + + +def register_custom_mimetypes(): + mimetypes.add_type(b'application/x-ipynb+json', b'.ipynb') + + +register_custom_mimetypes() + + +class NotebookPreviewer(Previewer): + ALLOWED_CONTENT_TYPE = re.compile(r"^application/x-ipynb\+json$") + + @classmethod + def generate_content(cls, attachment): + return render_template('previewer_jupyter:iframe_preview.html', + source_url=url_for_plugin('previewer_jupyter.preview_ipynb', attachment)) + + +class JupyterPreviewerPlugin(IndicoPlugin): + + """Jupyter Notebook renderer""" + + configurable = False + + def init(self): + super(JupyterPreviewerPlugin, self).init() + self.connect(signals.attachments.get_file_previewers, self._get_file_previewers) + + def get_blueprints(self): + yield blueprint + + def _get_file_previewers(self, sender, **kwargs): + yield NotebookPreviewer diff --git a/previewer_jupyter/indico_previewer_jupyter/templates/iframe_preview.html b/previewer_jupyter/indico_previewer_jupyter/templates/iframe_preview.html new file mode 100644 index 0000000..18ab2d4 --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/templates/iframe_preview.html @@ -0,0 +1 @@ + diff --git a/previewer_jupyter/indico_previewer_jupyter/templates/ipynb_preview.html b/previewer_jupyter/indico_previewer_jupyter/templates/ipynb_preview.html new file mode 100644 index 0000000..83e8cf3 --- /dev/null +++ b/previewer_jupyter/indico_previewer_jupyter/templates/ipynb_preview.html @@ -0,0 +1,12 @@ + + + + Preview + + + + {{ html_code | safe }} + + diff --git a/previewer_jupyter/setup.py b/previewer_jupyter/setup.py new file mode 100644 index 0000000..a12feec --- /dev/null +++ b/previewer_jupyter/setup.py @@ -0,0 +1,39 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from setuptools import setup, find_packages + + +setup( + name='indico_previewer_jupyter', + version='0.1', + url='https://github.com/indico/indico-plugins', + license='https://www.gnu.org/licenses/gpl-3.0.txt', + author='Indico Team', + author_email='indico-team@cern.ch', + packages=find_packages(), + zip_safe=False, + include_package_data=True, + platforms='any', + install_requires=[ + 'indico>=1.9.4', + 'nbconvert>=4.0.0', + 'functools32' + ], + entry_points={'indico.plugins': {'previewer_jupyter = indico_previewer_jupyter:JupyterPreviewerPlugin'}} +)