diff options
Diffstat (limited to 'nikola/plugins/compile/ipynb.py')
| -rw-r--r-- | nikola/plugins/compile/ipynb.py | 188 |
1 files changed, 109 insertions, 79 deletions
diff --git a/nikola/plugins/compile/ipynb.py b/nikola/plugins/compile/ipynb.py index a9dedde..039604b 100644 --- a/nikola/plugins/compile/ipynb.py +++ b/nikola/plugins/compile/ipynb.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2013-2015 Damián Avila, Chris Warrick and others. +# Copyright © 2013-2020 Damián Avila, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -24,76 +24,95 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Implementation of compile_html based on nbconvert.""" +"""Page compiler plugin for nbconvert.""" -from __future__ import unicode_literals, print_function import io +import json import os -import sys try: - import IPython - from IPython.nbconvert.exporters import HTMLExporter - if IPython.version_info[0] >= 3: # API changed with 3.0.0 - from IPython import nbformat - current_nbformat = nbformat.current_nbformat - from IPython.kernel import kernelspec - else: - import IPython.nbformat.current as nbformat - current_nbformat = 'json' - kernelspec = None - - from IPython.config import Config + import nbconvert + from nbconvert.exporters import HTMLExporter + import nbformat + current_nbformat = nbformat.current_nbformat + from jupyter_client import kernelspec + from traitlets.config import Config + NBCONVERT_VERSION_MAJOR = int(nbconvert.__version__.partition(".")[0]) flag = True except ImportError: flag = None +from nikola import shortcodes as sc from nikola.plugin_categories import PageCompiler -from nikola.utils import makedirs, req_missing, get_logger, STDERR_HANDLER +from nikola.utils import makedirs, req_missing, LocaleBorg class CompileIPynb(PageCompiler): - """Compile IPynb into HTML.""" name = "ipynb" - friendly_name = "Jupyter/IPython Notebook" + friendly_name = "Jupyter Notebook" demote_headers = True - default_kernel = 'python2' if sys.version_info[0] == 2 else 'python3' - - def set_site(self, site): - """Set Nikola site.""" - self.logger = get_logger('compile_ipynb', STDERR_HANDLER) - super(CompileIPynb, self).set_site(site) + default_kernel = 'python3' + supports_metadata = True - def compile_html_string(self, source, is_two_file=True): + def _compile_string(self, nb_json): """Export notebooks as HTML strings.""" - if flag is None: - req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)') - HTMLExporter.default_template = 'basic' - c = Config(self.site.config['IPYNB_CONFIG']) + self._req_missing_ipynb() + c = Config(get_default_jupyter_config()) + c.merge(Config(self.site.config['IPYNB_CONFIG'])) + if 'template_file' not in self.site.config['IPYNB_CONFIG'].get('Exporter', {}): + if NBCONVERT_VERSION_MAJOR >= 6: + c['Exporter']['template_file'] = 'classic/base.html.j2' + else: + c['Exporter']['template_file'] = 'basic.tpl' # not a typo exportHtml = HTMLExporter(config=c) - with io.open(source, "r", encoding="utf8") as in_file: - nb_json = nbformat.read(in_file, current_nbformat) - (body, resources) = exportHtml.from_notebook_node(nb_json) + body, _ = exportHtml.from_notebook_node(nb_json) return body - def compile_html(self, source, dest, is_two_file=True): - """Compile source file into HTML and save as dest.""" + @staticmethod + def _nbformat_read(in_file): + return nbformat.read(in_file, current_nbformat) + + def _req_missing_ipynb(self): + if flag is None: + req_missing(['notebook>=4.0.0'], 'build this site (compile ipynb)') + + def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None): + """Compile notebooks into HTML strings.""" + new_data, shortcodes = sc.extract_shortcodes(data) + output = self._compile_string(nbformat.reads(new_data, current_nbformat)) + return self.site.apply_shortcodes_uuid(output, shortcodes, filename=source_path, extra_context={'post': post}) + + def compile(self, source, dest, is_two_file=False, post=None, lang=None): + """Compile the source file into HTML and save as dest.""" makedirs(os.path.dirname(dest)) - with io.open(dest, "w+", encoding="utf8") as out_file: - out_file.write(self.compile_html_string(source, is_two_file)) + with io.open(dest, "w+", encoding="utf-8") as out_file: + with io.open(source, "r", encoding="utf-8-sig") as in_file: + nb_str = in_file.read() + output, shortcode_deps = self.compile_string(nb_str, source, + is_two_file, post, + lang) + out_file.write(output) + if post is None: + if shortcode_deps: + self.logger.error( + "Cannot save dependencies for post {0} (post unknown)", + source) + else: + post._depfile[dest] += shortcode_deps - def read_metadata(self, post, file_metadata_regexp=None, unslugify_titles=False, lang=None): + def read_metadata(self, post, lang=None): """Read metadata directly from ipynb file. - As ipynb file support arbitrary metadata as json, the metadata used by Nikola + As ipynb files support arbitrary metadata as json, the metadata used by Nikola will be assume to be in the 'nikola' subfield. """ - if flag is None: - req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)') - source = post.source_path - with io.open(source, "r", encoding="utf8") as in_file: + self._req_missing_ipynb() + if lang is None: + lang = LocaleBorg().current_lang + source = post.translated_source_path(lang) + with io.open(source, "r", encoding="utf-8-sig") as in_file: nb_json = nbformat.read(in_file, current_nbformat) # Metadata might not exist in two-file posts or in hand-crafted # .ipynb files. @@ -101,11 +120,10 @@ class CompileIPynb(PageCompiler): def create_post(self, path, **kw): """Create a new post.""" - if flag is None: - req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)') + self._req_missing_ipynb() content = kw.pop('content', None) onefile = kw.pop('onefile', False) - kernel = kw.pop('ipython_kernel', None) + kernel = kw.pop('jupyter_kernel', None) # is_page is not needed to create the file kw.pop('is_page', False) @@ -119,40 +137,52 @@ class CompileIPynb(PageCompiler): # imported .ipynb file, guaranteed to start with "{" because it’s JSON. nb = nbformat.reads(content, current_nbformat) else: - if IPython.version_info[0] >= 3: - nb = nbformat.v4.new_notebook() - nb["cells"] = [nbformat.v4.new_markdown_cell(content)] - else: - nb = nbformat.new_notebook() - nb["worksheets"] = [nbformat.new_worksheet(cells=[nbformat.new_text_cell('markdown', [content])])] - - if kernelspec is not None: - if kernel is None: - kernel = self.default_kernel - self.logger.notice('No kernel specified, assuming "{0}".'.format(kernel)) - - IPYNB_KERNELS = {} - ksm = kernelspec.KernelSpecManager() - for k in ksm.find_kernel_specs(): - IPYNB_KERNELS[k] = ksm.get_kernel_spec(k).to_dict() - IPYNB_KERNELS[k]['name'] = k - del IPYNB_KERNELS[k]['argv'] - - if kernel not in IPYNB_KERNELS: - self.logger.error('Unknown kernel "{0}". Maybe you mispelled it?'.format(kernel)) - self.logger.info("Available kernels: {0}".format(", ".join(sorted(IPYNB_KERNELS)))) - raise Exception('Unknown kernel "{0}"'.format(kernel)) - - nb["metadata"]["kernelspec"] = IPYNB_KERNELS[kernel] - else: - # Older IPython versions don’t need kernelspecs. - pass + nb = nbformat.v4.new_notebook() + nb["cells"] = [nbformat.v4.new_markdown_cell(content)] + + if kernel is None: + kernel = self.default_kernel + self.logger.warning('No kernel specified, assuming "{0}".'.format(kernel)) + + IPYNB_KERNELS = {} + ksm = kernelspec.KernelSpecManager() + for k in ksm.find_kernel_specs(): + IPYNB_KERNELS[k] = ksm.get_kernel_spec(k).to_dict() + IPYNB_KERNELS[k]['name'] = k + del IPYNB_KERNELS[k]['argv'] + + if kernel not in IPYNB_KERNELS: + self.logger.error('Unknown kernel "{0}". Maybe you mispelled it?'.format(kernel)) + self.logger.info("Available kernels: {0}".format(", ".join(sorted(IPYNB_KERNELS)))) + raise Exception('Unknown kernel "{0}"'.format(kernel)) + + nb["metadata"]["kernelspec"] = IPYNB_KERNELS[kernel] if onefile: nb["metadata"]["nikola"] = metadata - with io.open(path, "w+", encoding="utf8") as fd: - if IPython.version_info[0] >= 3: - nbformat.write(nb, fd, 4) - else: - nbformat.write(nb, fd, 'ipynb') + with io.open(path, "w+", encoding="utf-8") as fd: + nbformat.write(nb, fd, 4) + + +def get_default_jupyter_config(): + """Search default jupyter configuration location paths. + + Return dictionary from configuration json files. + """ + config = {} + from jupyter_core.paths import jupyter_config_path + + for parent in jupyter_config_path(): + try: + for file in os.listdir(parent): + if 'nbconvert' in file and file.endswith('.json'): + abs_path = os.path.join(parent, file) + with open(abs_path) as config_file: + config.update(json.load(config_file)) + except OSError: + # some paths jupyter uses to find configurations + # may not exist + pass + + return config |
