From f4c7daefe0e24055a92f185d6fd803abf909db2f Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Fri, 28 Jun 2019 11:29:20 +0200 Subject: [PATCH] Refactor template system to be easily extensible and voila compatible Co-authored-by: Sylvain Corlay --- .travis.yml | 2 +- nbconvert/exporters/asciidoc.py | 4 +- nbconvert/exporters/exporter.py | 2 +- nbconvert/exporters/html.py | 60 +++- nbconvert/exporters/latex.py | 19 +- nbconvert/exporters/markdown.py | 6 +- nbconvert/exporters/pdf.py | 5 + nbconvert/exporters/python.py | 6 +- nbconvert/exporters/rst.py | 6 +- nbconvert/exporters/script.py | 6 +- nbconvert/exporters/slides.py | 14 +- nbconvert/exporters/templateexporter.py | 147 ++++++++-- nbconvert/exporters/tests/test_html.py | 26 +- nbconvert/exporters/tests/test_latex.py | 23 +- nbconvert/exporters/tests/test_slides.py | 2 +- .../exporters/tests/test_templateexporter.py | 47 +-- nbconvert/nbconvertapp.py | 5 +- nbconvert/preprocessors/csshtmlheader.py | 32 +-- nbconvert/templates/skeleton/null.tpl | 102 ------- nbconvert/tests/fake_exporters.py | 4 + nbconvert/tests/test_nbconvertapp.py | 10 +- setup.py | 99 +++++-- .../nbconvert/templates/asciidoc/conf.json | 6 + .../templates/asciidoc/index.asciidoc.j2 | 2 +- .../nbconvert/templates/base/base.html.j2 | 6 +- .../nbconvert/templates/base/celltags.j2 | 0 .../templates/base/display_priority.j2 | 2 +- .../nbconvert/templates/base/mathjax.html.j2 | 0 .../jupyter/nbconvert/templates/base/null.j2 | 108 +++++++ .../nbconvert/templates/classic/conf.json | 6 + .../nbconvert/templates/classic/index.html.j2 | 47 ++- .../nbconvert/templates/lab/base.html.j2 | 270 ++++++++++++++++++ .../jupyter/nbconvert/templates/lab/conf.json | 6 + .../nbconvert/templates/lab/index.html.j2 | 29 ++ .../nbconvert/templates/latex/base.tex.j2 | 2 +- .../nbconvert/templates/latex/conf.json | 7 + .../templates/latex/display_priority.j2 | 2 +- .../templates/latex/document_contents.tex.j2 | 2 +- .../nbconvert/templates/latex/index.tex.j2 | 2 +- .../jupyter/nbconvert/templates/latex/null.j2 | 0 .../nbconvert/templates/latex/report.tex.j2 | 2 +- .../templates/latex/style_bw_ipython.tex.j2 | 2 +- .../templates/latex/style_bw_python.tex.j2 | 2 +- .../templates/latex/style_ipython.tex.j2 | 4 +- .../templates/latex/style_jupyter.tex.j2 | 2 +- .../templates/latex/style_python.tex.j2 | 2 +- .../nbconvert/templates/markdown/conf.json | 6 + .../nbconvert/templates/markdown/index.md.j2 | 2 +- .../nbconvert/templates/python/conf.json | 6 + .../nbconvert/templates/python/index.py.j2 | 2 +- .../nbconvert/templates/reveal/conf.json | 6 + .../nbconvert/templates/reveal/index.html.j2 | 4 +- .../jupyter/nbconvert/templates/rst/conf.json | 6 + .../nbconvert/templates/rst/index.rst.j2 | 2 +- .../nbconvert/templates/script/conf.json | 6 + .../nbconvert/templates/script/script.j2 | 2 +- 56 files changed, 869 insertions(+), 311 deletions(-) delete mode 100644 nbconvert/templates/skeleton/null.tpl create mode 100644 share/jupyter/nbconvert/templates/asciidoc/conf.json rename nbconvert/templates/asciidoc.tpl => share/jupyter/nbconvert/templates/asciidoc/index.asciidoc.j2 (98%) rename nbconvert/templates/html/basic.tpl => share/jupyter/nbconvert/templates/base/base.html.j2 (98%) rename nbconvert/templates/html/celltags.tpl => share/jupyter/nbconvert/templates/base/celltags.j2 (100%) rename nbconvert/templates/skeleton/display_priority.tpl => share/jupyter/nbconvert/templates/base/display_priority.j2 (98%) rename nbconvert/templates/html/mathjax.tpl => share/jupyter/nbconvert/templates/base/mathjax.html.j2 (100%) create mode 100644 share/jupyter/nbconvert/templates/base/null.j2 create mode 100644 share/jupyter/nbconvert/templates/classic/conf.json rename nbconvert/templates/html/full.tpl => share/jupyter/nbconvert/templates/classic/index.html.j2 (71%) create mode 100644 share/jupyter/nbconvert/templates/lab/base.html.j2 create mode 100644 share/jupyter/nbconvert/templates/lab/conf.json create mode 100644 share/jupyter/nbconvert/templates/lab/index.html.j2 rename nbconvert/templates/latex/base.tplx => share/jupyter/nbconvert/templates/latex/base.tex.j2 (99%) create mode 100644 share/jupyter/nbconvert/templates/latex/conf.json rename nbconvert/templates/latex/skeleton/display_priority.tplx => share/jupyter/nbconvert/templates/latex/display_priority.j2 (98%) rename nbconvert/templates/latex/document_contents.tplx => share/jupyter/nbconvert/templates/latex/document_contents.tex.j2 (98%) rename nbconvert/templates/latex/article.tplx => share/jupyter/nbconvert/templates/latex/index.tex.j2 (89%) rename nbconvert/templates/latex/skeleton/null.tplx => share/jupyter/nbconvert/templates/latex/null.j2 (100%) rename nbconvert/templates/latex/report.tplx => share/jupyter/nbconvert/templates/latex/report.tex.j2 (94%) rename nbconvert/templates/latex/style_bw_ipython.tplx => share/jupyter/nbconvert/templates/latex/style_bw_ipython.tex.j2 (98%) rename nbconvert/templates/latex/style_bw_python.tplx => share/jupyter/nbconvert/templates/latex/style_bw_python.tex.j2 (93%) rename nbconvert/templates/latex/style_ipython.tplx => share/jupyter/nbconvert/templates/latex/style_ipython.tex.j2 (96%) rename nbconvert/templates/latex/style_jupyter.tplx => share/jupyter/nbconvert/templates/latex/style_jupyter.tex.j2 (99%) rename nbconvert/templates/latex/style_python.tplx => share/jupyter/nbconvert/templates/latex/style_python.tex.j2 (95%) create mode 100644 share/jupyter/nbconvert/templates/markdown/conf.json rename nbconvert/templates/markdown.tpl => share/jupyter/nbconvert/templates/markdown/index.md.j2 (97%) create mode 100644 share/jupyter/nbconvert/templates/python/conf.json rename nbconvert/templates/python.tpl => share/jupyter/nbconvert/templates/python/index.py.j2 (94%) create mode 100644 share/jupyter/nbconvert/templates/reveal/conf.json rename nbconvert/templates/html/slides_reveal.tpl => share/jupyter/nbconvert/templates/reveal/index.html.j2 (98%) create mode 100644 share/jupyter/nbconvert/templates/rst/conf.json rename nbconvert/templates/rst.tpl => share/jupyter/nbconvert/templates/rst/index.rst.j2 (98%) create mode 100644 share/jupyter/nbconvert/templates/script/conf.json rename nbconvert/templates/script.tpl => share/jupyter/nbconvert/templates/script/script.j2 (68%) diff --git a/.travis.yml b/.travis.yml index 9cb9c6768..34d144cf5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ install: - pip install check-manifest - python -m ipykernel.kernelspec --user script: - - check-manifest + - check-manifest --ignore "share/**" # cd so we test the install, not the repo - cd `mktemp -d` - py.test --cov nbconvert -v --pyargs nbconvert diff --git a/nbconvert/exporters/asciidoc.py b/nbconvert/exporters/asciidoc.py index 84320d9ef..ea128c487 100644 --- a/nbconvert/exporters/asciidoc.py +++ b/nbconvert/exporters/asciidoc.py @@ -18,8 +18,8 @@ class ASCIIDocExporter(TemplateExporter): def _file_extension_default(self): return '.asciidoc' - @default('template_file') - def _template_file_default(self): + @default('template_name') + def _template_name_default(self): return 'asciidoc' output_mimetype = 'text/asciidoc' diff --git a/nbconvert/exporters/exporter.py b/nbconvert/exporters/exporter.py index ffd6af4de..e6160cf9b 100644 --- a/nbconvert/exporters/exporter.py +++ b/nbconvert/exporters/exporter.py @@ -52,7 +52,7 @@ class Exporter(LoggingConfigurable): accompanying resources dict. """ - file_extension = FilenameExtension('.txt', + file_extension = FilenameExtension( help="Extension of the file that should be written to disk" ).tag(config=True) diff --git a/nbconvert/exporters/html.py b/nbconvert/exporters/html.py index bf2b3c005..490a38232 100644 --- a/nbconvert/exporters/html.py +++ b/nbconvert/exporters/html.py @@ -5,11 +5,15 @@ # Distributed under the terms of the Modified BSD License. import os +import mimetypes +import base64 from traitlets import default, Unicode from traitlets.config import Config from jupyter_core.paths import jupyter_path from jinja2 import contextfilter +from jinja2.loaders import split_template_path +import jinja2 from nbconvert.filters.highlight import Highlight2HTML from nbconvert.filters.markdown_mistune import IPythonRenderer, MarkdownWithMath @@ -33,17 +37,18 @@ class HTMLExporter(TemplateExporter): def _file_extension_default(self): return '.html' - @default('default_template_path') - def _default_template_path_default(self): - return os.path.join("..", "templates", "html") + @default('template_name') + def _template_name_default(self): + return 'classic' @default('template_data_paths') def _template_data_paths_default(self): return jupyter_path("nbconvert", "templates", "html") - @default('template_file') - def _template_file_default(self): - return 'full.tpl' + + theme = Unicode('light', + help='Template specific theme(e.g. the JupyterLab CSS theme for the lab template)' + ).tag(config=True) output_mimetype = 'text/html' @@ -93,3 +98,46 @@ def from_notebook_node(self, nb, resources=None, **kw): highlight_code = self.filters.get('highlight_code', Highlight2HTML(pygments_lexer=lexer, parent=self)) self.register_filter('highlight_code', highlight_code) return super(HTMLExporter, self).from_notebook_node(nb, resources, **kw) + + def _init_resources(self, resources): + def resources_include_css(name): + env = self.environment + code = """""" % (env.loader.get_source(env, name)[0]) + return jinja2.Markup(code) + + def resources_include_js(name): + env = self.environment + code = """""" % (env.loader.get_source(env, name)[0]) + return jinja2.Markup(code) + + def resources_include_url(name): + env = self.environment + mime_type, encoding = mimetypes.guess_type(name) + try: + # we try to load via the jinja loader, but that tries to load + # as (encoded) text + data = env.loader.get_source(env, name)[0].encode('utf8') + except UnicodeDecodeError: + # if that fails (for instance a binary file, png or ttf) + # we mimic jinja2 + pieces = split_template_path(name) + searchpaths = self.get_template_paths() + for searchpath in searchpaths: + filename = os.path.join(searchpath, *pieces) + print(filename, os.path.exists(filename)) + if os.path.exists(filename): + with open(filename, "rb") as f: + data = f.read() + break + else: + raise ValueError("No file %r found in %r" % (name, searchpaths)) + data = base64.b64encode(data) + data = data.replace(b'\n', b'').decode('ascii') + src = 'data:{mime_type};base64,{data}'.format(mime_type=mime_type, data=data) + return jinja2.Markup(src) + resources = super(HTMLExporter, self)._init_resources(resources) + resources['theme'] = self.theme + resources['include_css'] = resources_include_css + resources['include_js'] = resources_include_js + resources['include_url'] = resources_include_url + return resources diff --git a/nbconvert/exporters/latex.py b/nbconvert/exporters/latex.py index b9f82eb4e..21793d27a 100644 --- a/nbconvert/exporters/latex.py +++ b/nbconvert/exporters/latex.py @@ -27,25 +27,14 @@ class LatexExporter(TemplateExporter): def _file_extension_default(self): return '.tex' - @default('template_file') - def _template_file_default(self): - return 'article.tplx' - - # Latex constants - @default('default_template_path') - def _default_template_path_default(self): - return os.path.join("..", "templates", "latex") - - @default('template_skeleton_path') - def _template_skeleton_path_default(self): - return os.path.join("..", "templates", "latex", "skeleton") @default('template_data_paths') def _template_data_paths_default(self): return jupyter_path("nbconvert", "templates", "latex") - - #Extension that the template files use. - template_extension = Unicode(".tplx").tag(config=True) + + @default('template_name') + def _template_name_default(self): + return 'latex' output_mimetype = 'text/latex' diff --git a/nbconvert/exporters/markdown.py b/nbconvert/exporters/markdown.py index b810d3181..24d357f95 100644 --- a/nbconvert/exporters/markdown.py +++ b/nbconvert/exporters/markdown.py @@ -19,9 +19,9 @@ class MarkdownExporter(TemplateExporter): def _file_extension_default(self): return '.md' - @default('template_file') - def _template_file_default(self): - return 'markdown.tpl' + @default('template_name') + def _template_name_default(self): + return 'markdown' output_mimetype = 'text/markdown' diff --git a/nbconvert/exporters/pdf.py b/nbconvert/exporters/pdf.py index a84824362..33619d1a9 100644 --- a/nbconvert/exporters/pdf.py +++ b/nbconvert/exporters/pdf.py @@ -73,6 +73,11 @@ class PDFExporter(LatexExporter): def _file_extension_default(self): return '.pdf' + + @default('template_extension') + def _template_extension_default(self): + return '.tex.j2' + def run_command(self, command_list, filename, count, log_function, raise_on_failure=None): """Run command_list count times. diff --git a/nbconvert/exporters/python.py b/nbconvert/exporters/python.py index 642003632..aa833c813 100644 --- a/nbconvert/exporters/python.py +++ b/nbconvert/exporters/python.py @@ -16,8 +16,8 @@ class PythonExporter(TemplateExporter): def _file_extension_default(self): return '.py' - @default('template_file') - def _template_file_default(self): - return 'python.tpl' + @default('template_name') + def _template_name_default(self): + return 'python' output_mimetype = 'text/x-python' diff --git a/nbconvert/exporters/rst.py b/nbconvert/exporters/rst.py index 568144ba9..b27b9535c 100644 --- a/nbconvert/exporters/rst.py +++ b/nbconvert/exporters/rst.py @@ -18,9 +18,9 @@ class RSTExporter(TemplateExporter): def _file_extension_default(self): return '.rst' - @default('template_file') - def _template_file_default(self): - return 'rst.tpl' + @default('template_name') + def _template_name_default(self): + return 'rst' output_mimetype = 'text/restructuredtext' export_from_notebook = "reST" diff --git a/nbconvert/exporters/script.py b/nbconvert/exporters/script.py index 0875df60f..c9986289d 100644 --- a/nbconvert/exporters/script.py +++ b/nbconvert/exporters/script.py @@ -18,7 +18,11 @@ class ScriptExporter(TemplateExporter): @default('template_file') def _template_file_default(self): - return 'script.tpl' + return 'script.j2' + + @default('template_name') + def _template_name_default(self): + return 'script' def _get_language_exporter(self, lang_name): """Find an exporter for the language name from notebook metadata. diff --git a/nbconvert/exporters/slides.py b/nbconvert/exporters/slides.py index 500eef854..1f9333068 100644 --- a/nbconvert/exporters/slides.py +++ b/nbconvert/exporters/slides.py @@ -77,6 +77,14 @@ class SlidesExporter(HTMLExporter): export_from_notebook = "Reveal.js slides" + @default('template_name') + def _template_name_default(self): + return 'reveal' + + template_name = Unicode('reveal', + help="Name of the template to use" + ).tag(config=True, affects_template=True) + reveal_url_prefix = Unicode( help="""The URL prefix for reveal.js (version 3.x). This defaults to the reveal CDN, but can be any url pointing to a copy @@ -160,9 +168,9 @@ def _reveal_url_prefix_default(self): def _file_extension_default(self): return '.slides.html' - @default('template_file') - def _template_file_default(self): - return 'slides_reveal.tpl' + @default('template_extension') + def _template_extension_default(self): + return '.html.j2' output_mimetype = 'text/html' diff --git a/nbconvert/exporters/templateexporter.py b/nbconvert/exporters/templateexporter.py index 834480ad7..81b6d0930 100644 --- a/nbconvert/exporters/templateexporter.py +++ b/nbconvert/exporters/templateexporter.py @@ -11,6 +11,7 @@ import uuid import json +from jupyter_core.paths import jupyter_path from traitlets import HasTraits, Unicode, List, Dict, Bool, default, observe from traitlets.config import Config from traitlets.utils.importstring import import_item @@ -28,6 +29,10 @@ # Jinja2 extensions to load. JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols'] +ROOT = os.path.dirname(__file__) +DEV_MODE = os.path.exists(os.path.join(ROOT, '../../setup.py')) and os.path.exists(os.path.join(ROOT, '../../share')) + + default_filters = { 'indent': filters.indent, 'markdown2html': filters.markdown2html, @@ -61,6 +66,29 @@ 'strip_trailing_newline': filters.strip_trailing_newline, } + +# copy of https://github.com/jupyter/jupyter_server/blob/b62458a7f5ad6b5246d2f142258dedaa409de5d9/jupyter_server/config_manager.py#L19 +def recursive_update(target, new): + """Recursively update one dictionary using another. + None values will delete their keys. + """ + for k, v in new.items(): + if isinstance(v, dict): + if k not in target: + target[k] = {} + recursive_update(target[k], v) + if not target[k]: + # Prune empty subdicts + del target[k] + + elif v is None: + target.pop(k, None) + + else: + target[k] = v + return target # return for convenience + + class ExtensionTolerantLoader(BaseLoader): """A template loader which optionally adds a given extension when searching. @@ -139,7 +167,10 @@ def default_config(self): c.merge(super(TemplateExporter, self).default_config) return c - template_file = Unicode( + template_name = Unicode(help="Name of the template to use" + ).tag(config=True, affects_template=True) + + template_file = Unicode(None, allow_none=True, help="Name of the template file to use" ).tag(config=True, affects_template=True) @@ -165,27 +196,20 @@ def _template_file_changed(self, change): @default('template_file') def _template_file_default(self): - return self.default_template + if self.template_extension: + return 'index' + self.template_extension @observe('raw_template') def _raw_template_changed(self, change): if not change['new']: - self.template_file = self.default_template or self._last_template_file + self.template_file = self._last_template_file self._invalidate_template_cache() - default_template = Unicode(u'').tag(affects_template=True) - template_path = List(['.']).tag(config=True, affects_environment=True) - default_template_path = Unicode( - os.path.join("..", "templates"), - help="Path where the template files are located." - ).tag(affects_environment=True) + #Extension that the template files use. + template_extension = Unicode().tag(config=True, affects_environment=True) - template_skeleton_path = Unicode( - os.path.join("..", "templates", "skeleton"), - help="Path where the template skeleton files are located.", - ).tag(affects_environment=True) template_data_paths = List( jupyter_path('nbconvert','templates'), @@ -194,6 +218,17 @@ def _raw_template_changed(self, change): #Extension that the template files use. template_extension = Unicode(".tpl").tag(config=True, affects_environment=True) + @default('template_extension') + def _template_extension_default(self): + if self.file_extension: + return self.file_extension + ".j2" + else: + return self.file_extension + + @default('template_file') + def _template_file_default(self): + if self.template_extension: + return 'index' + self.template_extension exclude_input = Bool(False, help = "This allows you to exclude code cell inputs from all templates if set to True." @@ -398,19 +433,8 @@ def _create_environment(self): """ Create the Jinja templating environment. """ - here = os.path.dirname(os.path.realpath(__file__)) - - additional_paths = self.template_data_paths - for path in additional_paths: - try: - ensure_dir_exists(path, mode=0o700) - except OSError: - pass - - paths = self.template_path + \ - additional_paths + \ - [os.path.join(here, self.default_template_path), - os.path.join(here, self.template_skeleton_path)] + paths = self.get_template_paths() + self.log.info('template paths:\n\t%s', '\n\t'.join(paths)) loaders = self.extra_loaders + [ ExtensionTolerantLoader(FileSystemLoader(paths), self.template_extension), @@ -433,3 +457,74 @@ def _create_environment(self): self._register_filter(environment, key, user_filter) return environment + + def get_template_paths(self, prune=False, root_dirs=None): + full_paths = [] + paths = list(self.template_path) + root_dirs = self.get_prefix_root_dirs() + template_names = self.get_template_names() + for template_name in template_names: + for root_dir in root_dirs: + base_dir = os.path.join(root_dir, 'nbconvert', 'templates') + path = os.path.join(base_dir, template_name) + if not prune or os.path.exists(path): + paths.append(path) + + for root_dir in root_dirs: + # we include root_dir for when we want to be very explicit, e.g. + # {% extends 'nbconvert/templates/classic/base.html' %} + paths.append(root_dir) + # we include base_dir for when we want to be explicit, but less than root_dir, e.g. + # {% extends 'classic/base.html' %} + base_dir = os.path.join(root_dir, 'nbconvert', 'templates') + paths.append(base_dir) + + additional_paths = self.template_data_paths + for path in additional_paths: + try: + ensure_dir_exists(path, mode=0o700) + except OSError: + pass + + + return additional_paths + paths + + def get_template_names(self): + # finds a list of template name where each successive template name is the base template + template_names = [] + root_dirs = self.get_prefix_root_dirs() + template_name = self.template_name + merged_conf = {} # the configuration once all conf files are merged + while template_name is not None: + template_names.append(template_name) + conf = {} + found_at_least_one = False + for root_dir in root_dirs: + template_dir = os.path.join(root_dir, 'nbconvert', 'templates', template_name) + if os.path.exists(template_dir): + found_at_least_one = True + conf_file = os.path.join(template_dir, 'conf.json') + if os.path.exists(conf_file): + with open(conf_file) as f: + conf = recursive_update(json.load(f), conf) + if not found_at_least_one: + paths = "\n\t".join(root_dirs) + raise ValueError('No template sub-directory with name %r found in the following paths:\n\t%s' % (template_name, paths)) + merged_conf = recursive_update(dict(conf), merged_conf) + template_name = conf.get('base_template') + conf = merged_conf + mimetypes = [mimetype for mimetype, enabled in conf.get('mimetypes', {}).items() if enabled] + if self.output_mimetype and self.output_mimetype not in mimetypes: + supported_mimetypes = '\n\t'.join(mimetypes) + raise ValueError('Unsupported mimetype %r for template %r, mimetypes supported are: \n\t%s' %\ + (self.output_mimetype, self.template_name, supported_mimetypes)) + return template_names + + def get_prefix_root_dirs(self): + # We look at the usual jupyter locations, and for development purposes also + # relative to the package directory (first entry, meaning with highest precedence) + root_dirs = [] + if DEV_MODE: + root_dirs.append(os.path.abspath(os.path.join(ROOT, '..', '..', 'share', 'jupyter'))) + root_dirs.extend(jupyter_path()) + return root_dirs diff --git a/nbconvert/exporters/tests/test_html.py b/nbconvert/exporters/tests/test_html.py index 507a713a1..ef1137bd0 100644 --- a/nbconvert/exporters/tests/test_html.py +++ b/nbconvert/exporters/tests/test_html.py @@ -33,26 +33,26 @@ def test_export(self): assert len(output) > 0 - def test_export_basic(self): + def test_export_classic(self): """ - Can a HTMLExporter export using the 'basic' template? + Can a HTMLExporter export using the 'classic' template? """ - (output, resources) = HTMLExporter(template_file='basic').from_filename(self._get_notebook()) + (output, resources) = HTMLExporter(template_name='classic').from_filename(self._get_notebook()) assert len(output) > 0 - def test_export_full(self): + def test_export_notebook(self): """ - Can a HTMLExporter export using the 'full' template? + Can a HTMLExporter export using the 'lab' template? """ - (output, resources) = HTMLExporter(template_file='full').from_filename(self._get_notebook()) + (output, resources) = HTMLExporter(template_name='lab').from_filename(self._get_notebook()) assert len(output) > 0 def test_prompt_number(self): """ Does HTMLExporter properly format input and output prompts? """ - (output, resources) = HTMLExporter(template_file='full').from_filename( + (output, resources) = HTMLExporter(template_name='lab').from_filename( self._get_notebook(nb_name="prompt_numbers.ipynb")) in_regex = r"In \[(.*)\]:" out_regex = r"Out\[(.*)\]:" @@ -74,7 +74,7 @@ def test_prompt_number(self): } } ) - exporter = HTMLExporter(config=no_prompt_conf, template_file='full') + exporter = HTMLExporter(config=no_prompt_conf, template_name='lab') (output, resources) = exporter.from_filename( self._get_notebook(nb_name="prompt_numbers.ipynb")) in_regex = r"In \[(.*)\]:" @@ -85,9 +85,9 @@ def test_prompt_number(self): def test_png_metadata(self): """ - Does HTMLExporter with the 'basic' template treat pngs with width/height metadata correctly? + Does HTMLExporter with the 'classic' template treat pngs with width/height metadata correctly? """ - (output, resources) = HTMLExporter(template_file='basic').from_filename( + (output, resources) = HTMLExporter(template_name='classic').from_filename( self._get_notebook(nb_name="pngmetadata.ipynb")) check_for_png = re.compile(r']*?)>') result = check_for_png.search(output) @@ -108,11 +108,11 @@ def test_javascript_output(self): ) ] ) - (output, resources) = HTMLExporter(template_file='basic').from_notebook_node(nb) + (output, resources) = HTMLExporter(template_name='classic').from_notebook_node(nb) self.assertIn('javascript_output', output) def test_attachments(self): - (output, resources) = HTMLExporter(template_file='basic').from_file( + (output, resources) = HTMLExporter(template_name='classic').from_file( self._get_notebook(nb_name='attachment.ipynb') ) check_for_png = re.compile(r']*?)>') @@ -137,6 +137,6 @@ def custom_highlight_code(source, language="python", metadata=None): filters = { "highlight_code": custom_highlight_code } - (output, resources) = HTMLExporter(template_file='basic', filters=filters).from_notebook_node(nb) + (output, resources) = HTMLExporter(template_name='classic', filters=filters).from_notebook_node(nb) self.assertTrue("ADDED_TEXT" in output) diff --git a/nbconvert/exporters/tests/test_latex.py b/nbconvert/exporters/tests/test_latex.py index 63e97d832..262bff8b9 100644 --- a/nbconvert/exporters/tests/test_latex.py +++ b/nbconvert/exporters/tests/test_latex.py @@ -53,23 +53,6 @@ def test_export_book(self): assert len(output) > 0 - @onlyif_cmds_exist('pandoc') - def test_export_basic(self): - """ - Can a LatexExporter export using 'article' template? - """ - (output, resources) = LatexExporter(template_file='article').from_filename(self._get_notebook()) - assert len(output) > 0 - - - @onlyif_cmds_exist('pandoc') - def test_export_article(self): - """ - Can a LatexExporter export using 'article' template? - """ - (output, resources) = LatexExporter(template_file='article').from_filename(self._get_notebook()) - assert len(output) > 0 - @onlyif_cmds_exist('pandoc') def test_very_long_cells(self): """ @@ -104,7 +87,7 @@ def test_very_long_cells(self): with open(nbfile, 'w') as f: write(nb, f, 4) - (output, resources) = LatexExporter(template_file='article').from_filename(nbfile) + (output, resources) = LatexExporter().from_filename(nbfile) assert len(output) > 0 @onlyif_cmds_exist('pandoc') @@ -133,7 +116,7 @@ def test_prompt_number_color_ipython(self): """ my_loader_tplx = DictLoader({'my_template': """ - ((* extends 'style_ipython.tplx' *)) + ((* extends 'style_ipython.tex.j2' *)) ((* block docclass *)) \documentclass[11pt]{article} @@ -184,7 +167,7 @@ def test_in_memory_template_tplx(self): # Loads in an in memory latex template (.tplx) using jinja2.DictLoader # creates a class that uses this template with the template_file argument # converts an empty notebook using this mechanism - my_loader_tplx = DictLoader({'my_template': "{%- extends 'article.tplx' -%}"}) + my_loader_tplx = DictLoader({'my_template': "{%- extends 'index' -%}"}) class MyExporter(LatexExporter): template_file = 'my_template' diff --git a/nbconvert/exporters/tests/test_slides.py b/nbconvert/exporters/tests/test_slides.py index f7873c159..5890d99d0 100644 --- a/nbconvert/exporters/tests/test_slides.py +++ b/nbconvert/exporters/tests/test_slides.py @@ -33,7 +33,7 @@ def test_export_reveal(self): """ Can a SlidesExporter export using the 'reveal' template? """ - (output, resources) = SlidesExporter(template_file='slides_reveal').from_filename(self._get_notebook()) + (output, resources) = SlidesExporter().from_filename(self._get_notebook()) assert len(output) > 0 def build_notebook(self): diff --git a/nbconvert/exporters/tests/test_templateexporter.py b/nbconvert/exporters/tests/test_templateexporter.py index 0a57b6ebe..4f3c2f40a 100644 --- a/nbconvert/exporters/tests/test_templateexporter.py +++ b/nbconvert/exporters/tests/test_templateexporter.py @@ -22,7 +22,7 @@ import pytest -raw_template = """{%- extends 'rst.tpl' -%} +raw_template = """{%- extends 'index.rst.j2' -%} {%- block in_prompt -%} blah {%- endblock in_prompt -%} @@ -140,7 +140,7 @@ def test_raw_template_attr(self): class AttrExporter(TemplateExporter): raw_template = raw_template - exporter_attr = AttrExporter() + exporter_attr = AttrExporter(template_name='rst') output_attr, _ = exporter_attr.from_notebook_node(nb) assert "blah" in output_attr @@ -163,7 +163,7 @@ def __init__(self, *args, **kwargs): output_init, _ = exporter_init.from_notebook_node(nb) assert "blah" in output_init exporter_init.raw_template = '' - assert exporter_init.template_file == "rst.tpl" + assert exporter_init.template_file == "index.rst.j2" output_init, _ = exporter_init.from_notebook_node(nb) assert "blah" not in output_init @@ -178,19 +178,19 @@ def test_raw_template_dynamic_attr(self): nb.cells.append(v4.new_code_cell("some_text")) class AttrDynamicExporter(TemplateExporter): - @default('template_file') + @default('default_template_file') def _template_file_default(self): - return "rst.tpl" + return "index.rst.j2" @default('raw_template') def _raw_template_default(self): return raw_template - exporter_attr_dynamic = AttrDynamicExporter() + exporter_attr_dynamic = AttrDynamicExporter(template_name='rst') output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb) assert "blah" in output_attr_dynamic exporter_attr_dynamic.raw_template = '' - assert exporter_attr_dynamic.template_file == "rst.tpl" + assert exporter_attr_dynamic.template_file == "index.rst.j2" output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb) assert "blah" not in output_attr_dynamic @@ -209,15 +209,15 @@ class AttrDynamicExporter(TemplateExporter): def _raw_template_default(self): return raw_template - @default('template_file') + @default('default_template_file') def _template_file_default(self): - return "rst.tpl" + return 'index.rst.j2' - exporter_attr_dynamic = AttrDynamicExporter() + exporter_attr_dynamic = AttrDynamicExporter(template_name='rst') output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb) assert "blah" in output_attr_dynamic exporter_attr_dynamic.raw_template = '' - assert exporter_attr_dynamic.template_file == "rst.tpl" + assert exporter_attr_dynamic.template_file == 'index.rst.j2' output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb) assert "blah" not in output_attr_dynamic @@ -229,7 +229,7 @@ def test_raw_template_constructor(self): nb = v4.new_notebook() nb.cells.append(v4.new_code_cell("some_text")) - output_constructor, _ = TemplateExporter( + output_constructor, _ = TemplateExporter(template_name='rst', raw_template=raw_template).from_notebook_node(nb) assert "blah" in output_constructor @@ -239,7 +239,7 @@ def test_raw_template_assignment(self): """ nb = v4.new_notebook() nb.cells.append(v4.new_code_cell("some_text")) - exporter_assign = TemplateExporter() + exporter_assign = TemplateExporter(template_name='rst') exporter_assign.raw_template = raw_template output_assign, _ = exporter_assign.from_notebook_node(nb) assert "blah" in output_assign @@ -250,7 +250,7 @@ def test_raw_template_reassignment(self): """ nb = v4.new_notebook() nb.cells.append(v4.new_code_cell("some_text")) - exporter_reassign = TemplateExporter() + exporter_reassign = TemplateExporter(template_name='rst') exporter_reassign.raw_template = raw_template output_reassign, _ = exporter_reassign.from_notebook_node(nb) assert "blah" in output_reassign @@ -270,7 +270,7 @@ def test_raw_template_deassignment(self): output_deassign, _ = exporter_deassign.from_notebook_node(nb) assert "blah" in output_deassign exporter_deassign.raw_template = '' - assert exporter_deassign.template_file == 'rst.tpl' + assert exporter_deassign.template_file == 'index.rst.j2' output_deassign, _ = exporter_deassign.from_notebook_node(nb) assert "blah" not in output_deassign @@ -289,7 +289,7 @@ def test_raw_template_dereassignment(self): output_dereassign, _ = exporter_dereassign.from_notebook_node(nb) assert "baz" in output_dereassign exporter_dereassign.raw_template = '' - assert exporter_dereassign.template_file == 'rst.tpl' + assert exporter_dereassign.template_file == 'index.rst.j2' output_dereassign, _ = exporter_dereassign.from_notebook_node(nb) assert "blah" not in output_dereassign @@ -317,8 +317,8 @@ def test_exclude_code_cell(self): } } c_no_io = Config(no_io) - exporter_no_io = TemplateExporter(config=c_no_io) - exporter_no_io.template_file = 'markdown' + exporter_no_io = TemplateExporter(config=c_no_io, template_name='markdown') + exporter_no_io.template_file = 'index.md.j2' nb_no_io, resources_no_io = exporter_no_io.from_filename(self._get_notebook()) assert not resources_no_io['global_content_filter']['include_input'] @@ -335,8 +335,8 @@ def test_exclude_code_cell(self): } } c_no_code = Config(no_code) - exporter_no_code = TemplateExporter(config=c_no_code) - exporter_no_code.template_file = 'markdown' + exporter_no_code = TemplateExporter(config=c_no_code, template_name='markdown') + exporter_no_code.template_file = 'index.md.j2' nb_no_code, resources_no_code = exporter_no_code.from_filename(self._get_notebook()) assert not resources_no_code['global_content_filter']['include_code'] @@ -375,8 +375,8 @@ def test_exclude_markdown(self): } c_no_md = Config(no_md) - exporter_no_md = TemplateExporter(config=c_no_md) - exporter_no_md.template_file = 'python' + exporter_no_md = TemplateExporter(config=c_no_md, template_name='python') + exporter_no_md.template_file = 'index.py.j2' nb_no_md, resources_no_md = exporter_no_md.from_filename(self._get_notebook()) assert not resources_no_md['global_content_filter']['include_markdown'] @@ -423,5 +423,6 @@ def _make_exporter(self, config=None): exporter = TemplateExporter(config=config) if not exporter.template_file: # give it a default if not specified - exporter.template_file = 'python' + exporter.template_name = 'python' + exporter.template_file = 'index.py.j2' return exporter diff --git a/nbconvert/nbconvertapp.py b/nbconvert/nbconvertapp.py index 0a6a7f865..1860552c5 100755 --- a/nbconvert/nbconvertapp.py +++ b/nbconvert/nbconvertapp.py @@ -51,7 +51,8 @@ def validate(self, obj, value): nbconvert_aliases.update(base_aliases) nbconvert_aliases.update({ 'to' : 'NbConvertApp.export_format', - 'template' : 'TemplateExporter.template_file', + 'template' : 'TemplateExporter.template_name', + 'template-file' : 'TemplateExporter.template_file', 'writer' : 'NbConvertApp.writer_class', 'post': 'NbConvertApp.postprocessor_class', 'output': 'NbConvertApp.output_base', @@ -191,7 +192,7 @@ def _classes_default(self): 'base', 'article' and 'report'. HTML includes 'basic' and 'full'. You can specify the flavor of the format used. - > jupyter nbconvert --to html --template basic mynotebook.ipynb + > jupyter nbconvert --to html --template lab mynotebook.ipynb You can also pipe the output to stdout, rather than a file diff --git a/nbconvert/preprocessors/csshtmlheader.py b/nbconvert/preprocessors/csshtmlheader.py index 191a2ea6c..0fa7b9bb8 100755 --- a/nbconvert/preprocessors/csshtmlheader.py +++ b/nbconvert/preprocessors/csshtmlheader.py @@ -9,9 +9,11 @@ import hashlib import nbconvert.resources -from traitlets import Unicode -from .base import Preprocessor +from traitlets import Unicode, Union, Type +from pygments.style import Style +from jupyterlab_pygments import JupyterStyle +from .base import Preprocessor try: from notebook import DEFAULT_STATIC_FILES_PATH @@ -28,8 +30,9 @@ class CSSHTMLHeaderPreprocessor(Preprocessor): help="CSS highlight class identifier" ).tag(config=True) - style = Unicode('default', - help='Name of the pygments style to use' + style = Union([Unicode('default'), Type(klass=Style)], + help='Name of the pygments style to use', + default_value=JupyterStyle ).tag(config=True) def __init__(self, *pargs, **kwargs): @@ -40,9 +43,9 @@ def preprocess(self, nb, resources): """Fetch and add CSS to the resource dictionary Fetch CSS from IPython and Pygments to add at the beginning - of the html files. Add this css in resources in the + of the html files. Add this css in resources in the "inlining.css" key - + Parameters ---------- nb : NotebookNode @@ -56,24 +59,13 @@ def preprocess(self, nb, resources): return nb, resources def _generate_header(self, resources): - """ - Fills self.header with lines of CSS extracted from IPython + """ + Fills self.header with lines of CSS extracted from IPython and Pygments. """ from pygments.formatters import HtmlFormatter header = [] - - # Construct path to Jupyter CSS - sheet_filename = os.path.join( - os.path.dirname(nbconvert.resources.__file__), - 'style.min.css', - ) - - # Load style CSS file. - with io.open(sheet_filename, encoding='utf-8') as f: - header.append(f.read()) - - # Add pygments CSS + formatter = HtmlFormatter(style=self.style) pygments_css = formatter.get_style_defs(self.highlight_class) header.append(pygments_css) diff --git a/nbconvert/templates/skeleton/null.tpl b/nbconvert/templates/skeleton/null.tpl deleted file mode 100644 index 32886a867..000000000 --- a/nbconvert/templates/skeleton/null.tpl +++ /dev/null @@ -1,102 +0,0 @@ -{# - -DO NOT USE THIS AS A BASE, -IF YOU ARE COPY AND PASTING THIS FILE -YOU ARE PROBABLY DOING THINGS INCORRECTLY. - -Null template, does nothing except defining a basic structure -To layout the different blocks of a notebook. - -Subtemplates can override blocks to define their custom representation. - -If one of the block you do overwrite is not a leave block, consider -calling super. - -{%- block nonLeaveBlock -%} - #add stuff at beginning - {{ super() }} - #add stuff at end -{%- endblock nonLeaveBlock -%} - -consider calling super even if it is a leave block, we might insert more blocks later. - -#} -{%- block header -%} -{%- endblock header -%} -{%- block body -%} -{%- for cell in nb.cells -%} - {%- block any_cell scoped -%} - {%- if cell.cell_type == 'code'-%} - {%- if resources.global_content_filter.include_code -%} - {%- block codecell scoped -%} - {%- if resources.global_content_filter.include_input and not cell.get("transient",{}).get("remove_source", false) -%} - {%- block input_group -%} - {%- if resources.global_content_filter.include_input_prompt -%} - {%- block in_prompt -%}{%- endblock in_prompt -%} - {%- endif -%} - {%- block input -%}{%- endblock input -%} - {%- endblock input_group -%} - {%- endif -%} - {%- if cell.outputs and resources.global_content_filter.include_output -%} - {%- block output_group -%} - {%- if resources.global_content_filter.include_output_prompt -%} - {%- block output_prompt -%}{%- endblock output_prompt -%} - {%- endif -%} - {%- block outputs scoped -%} - {%- for output in cell.outputs -%} - {%- block output scoped -%} - {%- if output.output_type == 'execute_result' -%} - {%- block execute_result scoped -%}{%- endblock execute_result -%} - {%- elif output.output_type == 'stream' -%} - {%- block stream scoped -%} - {%- if output.name == 'stdout' -%} - {%- block stream_stdout scoped -%} - {%- endblock stream_stdout -%} - {%- elif output.name == 'stderr' -%} - {%- block stream_stderr scoped -%} - {%- endblock stream_stderr -%} - {%- endif -%} - {%- endblock stream -%} - {%- elif output.output_type == 'display_data' -%} - {%- block display_data scoped -%} - {%- block data_priority scoped -%} - {%- endblock data_priority -%} - {%- endblock display_data -%} - {%- elif output.output_type == 'error' -%} - {%- block error scoped -%} - {%- for line in output.traceback -%} - {%- block traceback_line scoped -%}{%- endblock traceback_line -%} - {%- endfor -%} - {%- endblock error -%} - {%- endif -%} - {%- endblock output -%} - {%- endfor -%} - {%- endblock outputs -%} - {%- endblock output_group -%} - {%- endif -%} - {%- endblock codecell -%} - {%- endif -%} - {%- elif cell.cell_type in ['markdown'] -%} - {%- if resources.global_content_filter.include_markdown and not cell.get("transient",{}).get("remove_source", false) -%} - {%- block markdowncell scoped-%} {%- endblock markdowncell -%} - {%- endif -%} - {%- elif cell.cell_type in ['raw'] -%} - {%- if resources.global_content_filter.include_raw and not cell.get("transient",{}).get("remove_source", false) -%} - {%- block rawcell scoped -%} - {%- if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) -%} - {{ cell.source }} - {%- endif -%} - {%- endblock rawcell -%} - {%- endif -%} - {%- else -%} - {%- if resources.global_content_filter.include_unknown and not cell.get("transient",{}).get("remove_source", false) -%} - {%- block unknowncell scoped-%} - {%- endblock unknowncell -%} - {%- endif -%} - {%- endif -%} - {%- endblock any_cell -%} -{%- endfor -%} -{%- endblock body -%} - -{%- block footer -%} -{%- endblock footer -%} diff --git a/nbconvert/tests/fake_exporters.py b/nbconvert/tests/fake_exporters.py index bf75cca51..f3ec356b3 100644 --- a/nbconvert/tests/fake_exporters.py +++ b/nbconvert/tests/fake_exporters.py @@ -19,3 +19,7 @@ def _file_extension_default(self): The new file extension is `.test_ext` """ return '.test_ext' + + @default('template_extension') + def _template_extension_default(self): + return '.html.j2' diff --git a/nbconvert/tests/test_nbconvertapp.py b/nbconvert/tests/test_nbconvertapp.py index 48b876b59..b6b5590f0 100644 --- a/nbconvert/tests/test_nbconvertapp.py +++ b/nbconvert/tests/test_nbconvertapp.py @@ -91,27 +91,27 @@ def test_explicit(self): assert os.path.isfile('notebook2.py') def test_absolute_template_file(self): - """--template '/path/to/template.tpl'""" + """--template-file '/path/to/template.tpl'""" with self.create_temp_cwd(['notebook*.ipynb']), tempdir.TemporaryDirectory() as td: template = os.path.join(td, 'mytemplate.tpl') test_output = 'success!' with open(template, 'w') as f: f.write(test_output) - self.nbconvert('--log-level 0 notebook2 --template %s' % template) + self.nbconvert('--log-level 0 notebook2 --template-file %s' % template) assert os.path.isfile('notebook2.html') with open('notebook2.html') as f: text = f.read() assert text == test_output def test_relative_template_file(self): - """Test --template 'relative/path.tpl'""" + """Test --template-file 'relative/path.tpl'""" with self.create_temp_cwd(['notebook*.ipynb']): os.mkdir('relative') template = os.path.join('relative', 'path.tpl') test_output = 'success!' with open(template, 'w') as f: f.write(test_output) - self.nbconvert('--log-level 0 notebook2 --template %s' % template) + self.nbconvert('--log-level 0 notebook2 --template-file %s' % template) assert os.path.isfile('notebook2.html') with open('notebook2.html') as f: text = f.read() @@ -171,7 +171,7 @@ def test_png_base64_html_ok(self): """Is embedded png data well formed in HTML?""" with self.create_temp_cwd(['notebook2.ipynb']): self.nbconvert('--log-level 0 --to HTML ' - 'notebook2.ipynb --template full') + 'notebook2.ipynb --template lab') assert os.path.isfile('notebook2.html') with open('notebook2.html') as f: assert "data:image/png;base64,b'" not in f.read() diff --git a/setup.py b/setup.py index 1cf78b1bb..2dcf91a1a 100644 --- a/setup.py +++ b/setup.py @@ -65,13 +65,27 @@ ], } - notebook_css_version = '5.4.0' -css_url = "https://cdn.jupyter.org/notebook/%s/style/style.min.css" % notebook_css_version +notebook_css_url = "https://cdn.jupyter.org/notebook/%s/style/style.min.css" % notebook_css_version + + +jupyterlab_css_version = '0.1.0' +jupyterlab_css_url = "https://unpkg.com/@jupyterlab/nbconvert-css@%s/style/index.css" % jupyterlab_css_version + +jupyterlab_theme_light_version = '0.19.1' +jupyterlab_theme_light_url = "https://unpkg.com/@jupyterlab/theme-light-extension@%s/static/embed.css" % jupyterlab_theme_light_version + +jupyterlab_theme_dark_version = '0.19.1' +jupyterlab_theme_dark_url = "https://unpkg.com/@jupyterlab/theme-dark-extension@%s/static/embed.css" % jupyterlab_theme_dark_version + +template_css_urls = { + 'lab': [(jupyterlab_css_url, 'index.css'), (jupyterlab_theme_light_url, 'theme-light.css'), (jupyterlab_theme_dark_url, 'theme-dark.css')], + 'classic': [(notebook_css_url, 'style.css')] +} class FetchCSS(Command): - description = "Fetch Notebook CSS from Jupyter CDN" + description = "Fetch CSS from CDN" user_options = [] def initialize_options(self): pass @@ -79,9 +93,9 @@ def initialize_options(self): def finalize_options(self): pass - def _download(self): + def _download(self, url): try: - return urlopen(css_url).read() + return urlopen(url).read() except Exception as e: if 'ssl' in str(e).lower(): try: @@ -91,39 +105,48 @@ def _download(self): raise e else: print("Failed, trying again with PycURL to avoid outdated SSL.", file=sys.stderr) - return self._download_pycurl() + return self._download_pycurl(url) raise e - def _download_pycurl(self): + def _download_pycurl(self, url): """Download CSS with pycurl, in case of old SSL (e.g. Python < 2.7.9).""" import pycurl c = pycurl.Curl() - c.setopt(c.URL, css_url) + c.setopt(c.URL, url) buf = BytesIO() c.setopt(c.WRITEDATA, buf) c.perform() return buf.getvalue() def run(self): - dest = os.path.join('nbconvert', 'resources', 'style.min.css') - if not os.path.exists('.git') and os.path.exists(dest): - # not running from git, nothing to do - return - print("Downloading CSS: %s" % css_url) - try: - css = self._download() - except Exception as e: - msg = "Failed to download css from %s: %s" % (css_url, e) - print(msg, file=sys.stderr) - if os.path.exists(dest): - print("Already have CSS: %s, moving on." % dest) - else: - raise OSError("Need Notebook CSS to proceed: %s" % dest) - return - - with open(dest, 'wb') as f: - f.write(css) - print("Downloaded Notebook CSS to %s" % dest) + for template_name, resources in template_css_urls.items(): + for url, filename in resources: + directory = os.path.join('share', 'jupyter', 'nbconvert', 'templates', template_name, 'static') + dest = os.path.join(directory, filename) + if not os.path.exists(directory): + os.makedirs(directory) + if not os.path.exists('.git') and os.path.exists(dest): + # not running from git, nothing to do + return + print("Downloading CSS: %s" % url) + try: + css = self._download(url) + except Exception as e: + msg = "Failed to download css from %s: %s" % (url, e) + print(msg, file=sys.stderr) + if os.path.exists(dest): + print("Already have CSS: %s, moving on." % dest) + else: + raise OSError("Need CSS to proceed.") + return + + with open(dest, 'wb') as f: + f.write(css) + print("Downloaded Notebook CSS to %s" % dest) + + # update package data in case this created new files + self.distribution.data_files = get_data_files() + update_package_data(self.distribution) cmdclass = {'css': FetchCSS} @@ -160,6 +183,24 @@ def run(self): with io.open(pjoin(here, 'README.md'), encoding='utf-8') as f: long_description = f.read() + +def update_package_data(distribution): + """update package_data to catch changes during setup""" + build_py = distribution.get_command_obj('build_py') + # distribution.package_data = find_package_data() + # re-init build_py options which load package_data + build_py.finalize_options() + + +def get_data_files(): + # Add all the templates + data_files = [] + for (dirpath, dirnames, filenames) in os.walk('share/jupyter/nbconvert/templates/'): + if filenames: + data_files.append((dirpath, [os.path.join(dirpath, filename) for filename in filenames])) + return data_files + + setup_args = dict( name = name, description = "Converting Jupyter Notebooks", @@ -168,6 +209,7 @@ def run(self): packages = packages, long_description= long_description, package_data = package_data, + data_files = get_data_files(), cmdclass = cmdclass, python_requires = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', author = 'Jupyter Development Team', @@ -199,7 +241,8 @@ def run(self): setup_args['install_requires'] = [ 'mistune>=0.8.1,<2', 'jinja2>=2.4', - 'pygments', + 'pygments>=2.4.1', + 'jupyterlab_pygments', 'traitlets>=4.2', 'jupyter_core', 'nbformat>=4.4', diff --git a/share/jupyter/nbconvert/templates/asciidoc/conf.json b/share/jupyter/nbconvert/templates/asciidoc/conf.json new file mode 100644 index 000000000..37cdfa7bc --- /dev/null +++ b/share/jupyter/nbconvert/templates/asciidoc/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "base", + "mimetypes": { + "text/asciidoc": true + } +} \ No newline at end of file diff --git a/nbconvert/templates/asciidoc.tpl b/share/jupyter/nbconvert/templates/asciidoc/index.asciidoc.j2 similarity index 98% rename from nbconvert/templates/asciidoc.tpl rename to share/jupyter/nbconvert/templates/asciidoc/index.asciidoc.j2 index 8f32c4a6e..1512a2daa 100644 --- a/nbconvert/templates/asciidoc.tpl +++ b/share/jupyter/nbconvert/templates/asciidoc/index.asciidoc.j2 @@ -1,4 +1,4 @@ -{% extends 'display_priority.tpl' %} +{% extends 'display_priority.j2' %} {% block input %} diff --git a/nbconvert/templates/html/basic.tpl b/share/jupyter/nbconvert/templates/base/base.html.j2 similarity index 98% rename from nbconvert/templates/html/basic.tpl rename to share/jupyter/nbconvert/templates/base/base.html.j2 index 110e86423..e9bd3eb54 100644 --- a/nbconvert/templates/html/basic.tpl +++ b/share/jupyter/nbconvert/templates/base/base.html.j2 @@ -1,5 +1,5 @@ -{%- extends 'display_priority.tpl' -%} -{% from 'celltags.tpl' import celltags %} +{%- extends 'display_priority.j2' -%} +{% from 'celltags.j2' import celltags %} {% block codecell %}
@@ -240,7 +240,7 @@ var element = $('#{{ div_id }}');
- +{%- endblock html_head_js_requirejs -%} +{%- endblock html_head_js -%} {% block ipywidgets %} {%- if "widgets" in nb.metadata -%} @@ -43,12 +48,18 @@ {%- endif -%} {% endblock ipywidgets %} +{% block extra_css %} +{% endblock extra_css %} + {% for css in resources.inlining.css -%} {% endfor %} + +{% block notebook_css %} +{{ resources.include_css("static/style.css") }} +{% endblock notebook_css %} + +{% block custom_css %} +{% endblock custom_css %} {{ mathjax() }} + + +{%- block html_head_css -%} +{%- endblock html_head_css -%} + {%- endblock html_head -%} {%- endblock header -%} -{% block body %} +{# using the body block like this makes is difficult to be composable, added body_content/body_cells block for that#} +{% block body_header %}
-{{ super() }} +{% endblock body_header %} + +{% block body_footer %}
-{%- endblock body %} +{% endblock body_footer %} {% block footer %} +{% block footer_js %} +{% endblock footer_js %} {{ super() }} {% endblock footer %} diff --git a/share/jupyter/nbconvert/templates/lab/base.html.j2 b/share/jupyter/nbconvert/templates/lab/base.html.j2 new file mode 100644 index 000000000..eb9c45feb --- /dev/null +++ b/share/jupyter/nbconvert/templates/lab/base.html.j2 @@ -0,0 +1,270 @@ +{%- extends 'display_priority.j2' -%} +{% from 'celltags.j2' import celltags %} + +{% block codecell %} +{%- if not cell.outputs -%} +{%- set extra_class="jp-mod-noOutput" -%} +{%- endif -%} + +{%- endblock codecell %} + +{% block input_group -%} + +{% endblock input_group %} + +{% block input %} + +{%- endblock input %} + +{% block output_group %} + +{% endblock output_group %} + +{% block outputs %} + +{% endblock outputs %} + +{% block in_prompt -%} + +{%- endblock in_prompt %} + +{% block empty_in_prompt -%} + +{%- endblock empty_in_prompt %} + +{# + output_prompt doesn't do anything in HTML, + because there is a prompt div in each output area (see output block) + #} +{% block output_prompt %} +{% endblock output_prompt %} + +{% block output_area_prompt %} + +{% endblock output_area_prompt %} + +{% block output %} + +{% endblock output %} + +{% block markdowncell scoped %} + +{%- endblock markdowncell %} + +{% block unknowncell scoped %} +unknown type {{ cell.type }} +{% endblock unknowncell %} + +{% block execute_result -%} +{%- set extra_class="jp-OutputArea-executeResult" -%} +{% block data_priority scoped %} +{{ super() }} +{% endblock data_priority %} +{%- set extra_class="" -%} +{%- endblock execute_result %} + +{% block stream_stdout -%} + +{%- endblock stream_stdout %} + +{% block stream_stderr -%} + +{%- endblock stream_stderr %} + +{% block data_svg scoped -%} + +{%- endblock data_svg %} + +{% block data_html scoped -%} + +{%- endblock data_html %} + +{% block data_markdown scoped -%} + +{%- endblock data_markdown %} + +{% block data_png scoped %} + +{%- endblock data_png %} + +{% block data_jpg scoped %} + +{%- endblock data_jpg %} + +{% block data_latex scoped %} + +{%- endblock data_latex %} + +{% block error -%} + +{%- endblock error %} + +{%- block traceback_line %} +{{ line | ansi2html }} +{%- endblock traceback_line %} + +{%- block data_text scoped %} + +{%- endblock -%} + +{# + ############################################################################### + # TODO: how to better handle JavaScript repr? # + ############################################################################### + #} + +{% set div_id = uuid4() %} +{%- block data_javascript scoped %} +
+ +{%- endblock -%} + +{%- block data_widget_state scoped %} +{% set div_id = uuid4() %} +{% set datatype_list = output.data | filter_data_type %} +{% set datatype = datatype_list[0]%} +
+
+ + +
+{%- endblock data_widget_state -%} + +{%- block data_widget_view scoped %} +{% set div_id = uuid4() %} +{% set datatype_list = output.data | filter_data_type %} +{% set datatype = datatype_list[0]%} +
+ +{%- endblock data_widget_view -%} + +{%- block footer %} +{% set mimetype = 'application/vnd.jupyter.widget-state+json'%} +{% if mimetype in nb.metadata.get("widgets",{})%} + +{% endif %} +{{ super() }} +{%- endblock footer-%} diff --git a/share/jupyter/nbconvert/templates/lab/conf.json b/share/jupyter/nbconvert/templates/lab/conf.json new file mode 100644 index 000000000..e11030fc9 --- /dev/null +++ b/share/jupyter/nbconvert/templates/lab/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "classic", + "mimetypes": { + "text/html": true + } +} \ No newline at end of file diff --git a/share/jupyter/nbconvert/templates/lab/index.html.j2 b/share/jupyter/nbconvert/templates/lab/index.html.j2 new file mode 100644 index 000000000..e1c7bec00 --- /dev/null +++ b/share/jupyter/nbconvert/templates/lab/index.html.j2 @@ -0,0 +1,29 @@ +{%- extends 'classic/index.html.j2' -%} + + +{% block notebook_css %} +{{ resources.include_css("static/index.css") }} +{% if resources.theme == 'dark' %} + {{ resources.include_css("static/theme-dark.css") }} +{% else %} + {{ resources.include_css("static/theme-light.css") }} +{% endif %} + + +{% endblock notebook_css %} + + +{%- block body_header -%} +{% if resources.theme == 'dark' %} + +{% else %} + +{% endif %} +{%- endblock body_header -%} diff --git a/nbconvert/templates/latex/base.tplx b/share/jupyter/nbconvert/templates/latex/base.tex.j2 similarity index 99% rename from nbconvert/templates/latex/base.tplx rename to share/jupyter/nbconvert/templates/latex/base.tex.j2 index f3335be23..68f763f30 100644 --- a/nbconvert/templates/latex/base.tplx +++ b/share/jupyter/nbconvert/templates/latex/base.tex.j2 @@ -4,7 +4,7 @@ functions. Figures, data_text, This template defines defines a default docclass, the inheriting class should override this.-=)) -((*- extends 'document_contents.tplx' -*)) +((*- extends 'document_contents.tex.j2' -*)) %=============================================================================== % Abstract overrides diff --git a/share/jupyter/nbconvert/templates/latex/conf.json b/share/jupyter/nbconvert/templates/latex/conf.json new file mode 100644 index 000000000..1ac7a640d --- /dev/null +++ b/share/jupyter/nbconvert/templates/latex/conf.json @@ -0,0 +1,7 @@ +{ + "base_template": "base", + "mimetypes": { + "text/latex": true, + "application/pdf": true + } +} diff --git a/nbconvert/templates/latex/skeleton/display_priority.tplx b/share/jupyter/nbconvert/templates/latex/display_priority.j2 similarity index 98% rename from nbconvert/templates/latex/skeleton/display_priority.tplx rename to share/jupyter/nbconvert/templates/latex/display_priority.j2 index 3b9f7001d..9f33a74e1 100644 --- a/nbconvert/templates/latex/skeleton/display_priority.tplx +++ b/share/jupyter/nbconvert/templates/latex/display_priority.j2 @@ -2,7 +2,7 @@ To edit this file, please refer to ../../skeleton/README.md =)) -((*- extends 'null.tplx' -*)) +((*- extends 'null.j2' -*)) ((=display data priority=)) diff --git a/nbconvert/templates/latex/document_contents.tplx b/share/jupyter/nbconvert/templates/latex/document_contents.tex.j2 similarity index 98% rename from nbconvert/templates/latex/document_contents.tplx rename to share/jupyter/nbconvert/templates/latex/document_contents.tex.j2 index 24f969596..5e0fa7881 100644 --- a/nbconvert/templates/latex/document_contents.tplx +++ b/share/jupyter/nbconvert/templates/latex/document_contents.tex.j2 @@ -1,4 +1,4 @@ -((*- extends 'display_priority.tplx' -*)) +((*- extends 'display_priority.j2' -*)) %=============================================================================== % Support blocks diff --git a/nbconvert/templates/latex/article.tplx b/share/jupyter/nbconvert/templates/latex/index.tex.j2 similarity index 89% rename from nbconvert/templates/latex/article.tplx rename to share/jupyter/nbconvert/templates/latex/index.tex.j2 index b87a7ff18..840c9d4de 100644 --- a/nbconvert/templates/latex/article.tplx +++ b/share/jupyter/nbconvert/templates/latex/index.tex.j2 @@ -1,7 +1,7 @@ ((=- Default to the notebook output style -=)) ((*- if not cell_style is defined -*)) - ((* set cell_style = 'style_jupyter.tplx' *)) + ((* set cell_style = 'style_jupyter.tex.j2' *)) ((*- endif -*)) ((=- Inherit from the specified cell style. -=)) diff --git a/nbconvert/templates/latex/skeleton/null.tplx b/share/jupyter/nbconvert/templates/latex/null.j2 similarity index 100% rename from nbconvert/templates/latex/skeleton/null.tplx rename to share/jupyter/nbconvert/templates/latex/null.j2 diff --git a/nbconvert/templates/latex/report.tplx b/share/jupyter/nbconvert/templates/latex/report.tex.j2 similarity index 94% rename from nbconvert/templates/latex/report.tplx rename to share/jupyter/nbconvert/templates/latex/report.tex.j2 index 1ade514c5..669562a2f 100644 --- a/nbconvert/templates/latex/report.tplx +++ b/share/jupyter/nbconvert/templates/latex/report.tex.j2 @@ -1,7 +1,7 @@ % Default to the notebook output style ((* if not cell_style is defined *)) - ((* set cell_style = 'style_ipython.tplx' *)) + ((* set cell_style = 'style_ipython.tex.j2' *)) ((* endif *)) % Inherit from the specified cell style. diff --git a/nbconvert/templates/latex/style_bw_ipython.tplx b/share/jupyter/nbconvert/templates/latex/style_bw_ipython.tex.j2 similarity index 98% rename from nbconvert/templates/latex/style_bw_ipython.tplx rename to share/jupyter/nbconvert/templates/latex/style_bw_ipython.tex.j2 index 43ccc5563..07fdfa391 100644 --- a/nbconvert/templates/latex/style_bw_ipython.tplx +++ b/share/jupyter/nbconvert/templates/latex/style_bw_ipython.tex.j2 @@ -1,6 +1,6 @@ ((= Black&white ipython input/output style =)) -((*- extends 'base.tplx' -*)) +((*- extends 'base.tex.j2' -*)) %=============================================================================== % Input diff --git a/nbconvert/templates/latex/style_bw_python.tplx b/share/jupyter/nbconvert/templates/latex/style_bw_python.tex.j2 similarity index 93% rename from nbconvert/templates/latex/style_bw_python.tplx rename to share/jupyter/nbconvert/templates/latex/style_bw_python.tex.j2 index d309ca424..dea279405 100644 --- a/nbconvert/templates/latex/style_bw_python.tplx +++ b/share/jupyter/nbconvert/templates/latex/style_bw_python.tex.j2 @@ -1,6 +1,6 @@ ((= Black&white Python input/output style =)) -((*- extends 'base.tplx' -*)) +((*- extends 'base.tex.j2' -*)) %=============================================================================== % Input diff --git a/nbconvert/templates/latex/style_ipython.tplx b/share/jupyter/nbconvert/templates/latex/style_ipython.tex.j2 similarity index 96% rename from nbconvert/templates/latex/style_ipython.tplx rename to share/jupyter/nbconvert/templates/latex/style_ipython.tex.j2 index 10f01a80b..9fa238baf 100644 --- a/nbconvert/templates/latex/style_ipython.tplx +++ b/share/jupyter/nbconvert/templates/latex/style_ipython.tex.j2 @@ -1,6 +1,6 @@ -((= IPython input/output style =)) + style_bw_python.\TeX((= IPython input/output style =)) -((*- extends 'base.tplx' -*)) +((*- extends 'base.tex.j2' -*)) % Custom definitions ((* block definitions *)) diff --git a/nbconvert/templates/latex/style_jupyter.tplx b/share/jupyter/nbconvert/templates/latex/style_jupyter.tex.j2 similarity index 99% rename from nbconvert/templates/latex/style_jupyter.tplx rename to share/jupyter/nbconvert/templates/latex/style_jupyter.tex.j2 index 66699287c..7ecefb5dc 100644 --- a/nbconvert/templates/latex/style_jupyter.tplx +++ b/share/jupyter/nbconvert/templates/latex/style_jupyter.tex.j2 @@ -1,5 +1,5 @@ ((=- IPython input/output style -=)) -((*- extends 'base.tplx' -*)) +((*- extends 'base.tex.j2' -*)) ((*- block packages -*)) \usepackage[breakable]{tcolorbox} diff --git a/nbconvert/templates/latex/style_python.tplx b/share/jupyter/nbconvert/templates/latex/style_python.tex.j2 similarity index 95% rename from nbconvert/templates/latex/style_python.tplx rename to share/jupyter/nbconvert/templates/latex/style_python.tex.j2 index c21fc5a6b..60cc2ad71 100644 --- a/nbconvert/templates/latex/style_python.tplx +++ b/share/jupyter/nbconvert/templates/latex/style_python.tex.j2 @@ -1,6 +1,6 @@ ((= Python input/output style =)) -((*- extends 'base.tplx' -*)) +((*- extends 'base.tex.j2' -*)) % Custom definitions ((* block definitions *)) diff --git a/share/jupyter/nbconvert/templates/markdown/conf.json b/share/jupyter/nbconvert/templates/markdown/conf.json new file mode 100644 index 000000000..98b819dd9 --- /dev/null +++ b/share/jupyter/nbconvert/templates/markdown/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "base", + "mimetypes": { + "text/markdown": true + } +} \ No newline at end of file diff --git a/nbconvert/templates/markdown.tpl b/share/jupyter/nbconvert/templates/markdown/index.md.j2 similarity index 97% rename from nbconvert/templates/markdown.tpl rename to share/jupyter/nbconvert/templates/markdown/index.md.j2 index ce694d710..8906eff87 100644 --- a/nbconvert/templates/markdown.tpl +++ b/share/jupyter/nbconvert/templates/markdown/index.md.j2 @@ -1,4 +1,4 @@ -{% extends 'display_priority.tpl' %} +{% extends 'display_priority.j2' %} {% block in_prompt %} diff --git a/share/jupyter/nbconvert/templates/python/conf.json b/share/jupyter/nbconvert/templates/python/conf.json new file mode 100644 index 000000000..d7ccd0358 --- /dev/null +++ b/share/jupyter/nbconvert/templates/python/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "base", + "mimetypes": { + "text/x-python": true + } +} \ No newline at end of file diff --git a/nbconvert/templates/python.tpl b/share/jupyter/nbconvert/templates/python/index.py.j2 similarity index 94% rename from nbconvert/templates/python.tpl rename to share/jupyter/nbconvert/templates/python/index.py.j2 index eed2f923f..a09fe9d1f 100644 --- a/nbconvert/templates/python.tpl +++ b/share/jupyter/nbconvert/templates/python/index.py.j2 @@ -1,4 +1,4 @@ -{%- extends 'null.tpl' -%} +{%- extends 'null.j2' -%} {%- block header -%} #!/usr/bin/env python diff --git a/share/jupyter/nbconvert/templates/reveal/conf.json b/share/jupyter/nbconvert/templates/reveal/conf.json new file mode 100644 index 000000000..e11030fc9 --- /dev/null +++ b/share/jupyter/nbconvert/templates/reveal/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "classic", + "mimetypes": { + "text/html": true + } +} \ No newline at end of file diff --git a/nbconvert/templates/html/slides_reveal.tpl b/share/jupyter/nbconvert/templates/reveal/index.html.j2 similarity index 98% rename from nbconvert/templates/html/slides_reveal.tpl rename to share/jupyter/nbconvert/templates/reveal/index.html.j2 index 0ddf84473..309d88c37 100644 --- a/nbconvert/templates/html/slides_reveal.tpl +++ b/share/jupyter/nbconvert/templates/reveal/index.html.j2 @@ -1,5 +1,5 @@ -{%- extends 'basic.tpl' -%} -{% from 'mathjax.tpl' import mathjax %} +{%- extends 'classic/index.html.j2' -%} +{% from 'mathjax.html.j2' import mathjax %} {%- block any_cell scoped -%} {%- if cell.metadata.get('slide_start', False) -%} diff --git a/share/jupyter/nbconvert/templates/rst/conf.json b/share/jupyter/nbconvert/templates/rst/conf.json new file mode 100644 index 000000000..93bcd13ae --- /dev/null +++ b/share/jupyter/nbconvert/templates/rst/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "base", + "mimetypes": { + "text/restructuredtext": true + } +} \ No newline at end of file diff --git a/nbconvert/templates/rst.tpl b/share/jupyter/nbconvert/templates/rst/index.rst.j2 similarity index 98% rename from nbconvert/templates/rst.tpl rename to share/jupyter/nbconvert/templates/rst/index.rst.j2 index bf070c1b9..bda5c5fb9 100644 --- a/nbconvert/templates/rst.tpl +++ b/share/jupyter/nbconvert/templates/rst/index.rst.j2 @@ -1,4 +1,4 @@ -{%- extends 'display_priority.tpl' -%} +{%- extends 'display_priority.j2' -%} {% block in_prompt %} diff --git a/share/jupyter/nbconvert/templates/script/conf.json b/share/jupyter/nbconvert/templates/script/conf.json new file mode 100644 index 000000000..f2793d779 --- /dev/null +++ b/share/jupyter/nbconvert/templates/script/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "base", + "mimetypes": { + "text/plain": true + } +} \ No newline at end of file diff --git a/nbconvert/templates/script.tpl b/share/jupyter/nbconvert/templates/script/script.j2 similarity index 68% rename from nbconvert/templates/script.tpl rename to share/jupyter/nbconvert/templates/script/script.j2 index cbd971d06..3b30ea89e 100644 --- a/nbconvert/templates/script.tpl +++ b/share/jupyter/nbconvert/templates/script/script.j2 @@ -1,4 +1,4 @@ -{%- extends 'null.tpl' -%} +{%- extends 'null.j2' -%} {% block input %} {{ cell.source }}