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..b8689d0de 100644
--- a/nbconvert/exporters/html.py
+++ b/nbconvert/exporters/html.py
@@ -5,11 +5,14 @@
# 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
+import jinja2
from nbconvert.filters.highlight import Highlight2HTML
from nbconvert.filters.markdown_mistune import IPythonRenderer, MarkdownWithMath
@@ -33,17 +36,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 +97,30 @@ 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)
+ data = env.loader.get_source(env, name)[0]
+ data = base64.b64encode(data.encode('utf8'))
+ data = data.replace(b'\n', b'').decode('ascii')
+ print(type(data), data)
+ 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..723b22d45 100644
--- a/nbconvert/exporters/latex.py
+++ b/nbconvert/exporters/latex.py
@@ -27,25 +27,21 @@ 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")
-
+
+ @default('template_extension')
+ def _template_extension_default(self):
+ return '.tex'
+
+ @default('template_name')
+ def _template_name_default(self):
+ return 'latex'
+
#Extension that the template files use.
- template_extension = Unicode(".tplx").tag(config=True)
+ template_extension = Unicode(".tex").tag(config=True)
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/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..f1f35ded2 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'
output_mimetype = 'text/html'
diff --git a/nbconvert/exporters/templateexporter.py b/nbconvert/exporters/templateexporter.py
index 834480ad7..938004363 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,14 @@ 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):
+ 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 +430,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 +454,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..b9f5ab64c 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' *))
((* 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..2c0130cb9 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' -%}
{%- 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"
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"
@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"
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'
- 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'
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'
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'
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'
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'
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'
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'
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/tests/fake_exporters.py b/nbconvert/tests/fake_exporters.py
index bf75cca51..904665813 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'
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
similarity index 98%
rename from nbconvert/templates/asciidoc.tpl
rename to share/jupyter/nbconvert/templates/asciidoc/index.asciidoc
index 8f32c4a6e..1512a2daa 100644
--- a/nbconvert/templates/asciidoc.tpl
+++ b/share/jupyter/nbconvert/templates/asciidoc/index.asciidoc
@@ -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
similarity index 98%
rename from nbconvert/templates/html/basic.tpl
rename to share/jupyter/nbconvert/templates/base/base.html
index 110e86423..e9bd3eb54 100644
--- a/nbconvert/templates/html/basic.tpl
+++ b/share/jupyter/nbconvert/templates/base/base.html
@@ -1,5 +1,5 @@
-{%- extends 'display_priority.tpl' -%}
-{% from 'celltags.tpl' import celltags %}
+{%- extends 'display_priority.j2' -%}
+{% from 'celltags.j2' import celltags %}
{% block codecell %}