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'}}
+)