Source code for jup2jek.jup2jek

import os
import shutil
from glob import glob
from subprocess import check_output
from configparser import ConfigParser

__all__ = ['Jup2Jek']


[docs]class Jup2Jek(): """ A class for converting Jupyter notebooks to markdowns for use on Jekyll websites. Parameters ---------- root : str The path to the website root directory. options : dict The path to the configuration file. If None, then the default configuration file `root/jup2jek.ini` will be used. Examples -------- The contents of an example configuration file are below: >>> [JUP2JEK] >>> posts = _posts >>> assets = assets/jupyter posts The relative path to the posts directory from the website root. This is `_posts` for default Jekyll site configurations. assets The relative path to the assets folder where generated notebook assets will be stored. This is relative to the website root. .. warning:: This folder will be cleared each time `convert_notebooks()` is called. Therefore, the folder should be separate from those used for other website assets. Otherwise those assets will be deleted. """ def __init__(self, root, options=None): self.root = root if options == None: options = os.path.join(self.root, 'jup2jek.ini') elif not os.path.exists(options): options = os.path.join(self.root, options) self.load_options(options)
[docs] @staticmethod def write_default_options(path): """ Writes a configuration file with the default options. Parameters ---------- path : str Directory for writing the configuration file. The file name should not be included and will be appended as part of the method. """ config = ConfigParser() config['JUP2JEK'] = dict( posts='_posts', assets='assets/jupyter' ) p = os.path.join(path, 'jup2jek.ini') with open(p, 'wt') as fh: fh.truncate() config.write(fh)
[docs] def load_options(self, path): """ Loads the options from the specified configuration file. Parameters ---------- path : str Configuration file path. """ if os.path.exists(path): config = ConfigParser() config.read(path) self.options = {} self.options.update(config['JUP2JEK']) else: raise ValueError('Configuration file {} does not exist.'.format(path))
[docs] def posts_path(self): """Returns the posts path.""" return os.path.join(self.root, self.options['posts'])
[docs] def assets_path(self): """Returns the assets path.""" return os.path.join(self.root, self.options['assets'])
[docs] def convert(self, path): """ Converts the notebook at the specified path to markdown via the `jupyter nbconvert [notebook path] --to markdown` command. Parameters ---------- path : str Path to the jupyter notebook. """ command = 'jupyter nbconvert {} --to markdown'.format(path) check_output(command, shell=True) path = path[:-6] + '.md' with open(path, 'rt') as fh: data = fh.read() data = data.replace('<IPython.core.display.Javascript object>', '') with open(path, 'wt') as fh: fh.truncate() fh.write(data)
[docs] def notebooks(self): """Returns a list of notebook paths to be converted.""" posts = self.posts_path() notebooks = [] for folder, _, _ in os.walk(posts): if '.ipynb_checkpoints' not in folder: notebooks.extend(glob(os.path.join(folder, '*.ipynb'))) return notebooks
def _create_assets_path(self): """Creates the new assets path.""" p = self.assets_path() if not os.path.exists(p): os.makedirs(p) def _remove_assets_path(self): """Removes the jupyter assets path.""" p = self.assets_path() if os.path.exists(p): shutil.rmtree(p) def _update_markdown_paths(self, md_path, rel_assets_path): """ Updates the asset paths within the specified markdown file to that of the specified relative asset path. The final path reads as: `{{ site.url }}/[relative assets path]/[notebook filename]_files`. For example: `{{ site.url }}/[assets/jupyter/category1]/[example4]_files` (brackets included for emphasis only). Parameters ---------- md_path : str The path to the markdown file for which asset reference paths will be replaced. rel_assets_path : str The relative path to the new assets folder from the website root. See the above example path declaration. """ p = '{{{{ site.url }}}}/{}/'.format(rel_assets_path) with open(md_path, 'rt') as fh: data = fh.read() data = data.replace('![png](', '![png]({}'.format(p)) with open(md_path, 'wt') as fh: fh.truncate() fh.write(data)
[docs] def convert_notebooks(self): """ Converts all Jupyter notebooks within the posts folder specified in the class options to markdown for use on Jekyll websites. The convert process operates as follows: 1. All notebooks contained in the designated posts folder are found and converted to markdown using the jupyter nbconvert command. 2. If the nbconvert command generates an assets folder, the assets are moved to the designated site assets folder at the same relative path. 3. Asset paths referenced within the markdown files are updated to the website path. """ notebooks = self.notebooks() assets_path = self.assets_path() posts_path = self.posts_path() self._remove_assets_path() self._create_assets_path() for x in notebooks: self.convert(x) a1 = x[:-6] + '_files' if os.path.exists(a1): # Move assets from posts to assets f = os.path.relpath(a1, posts_path) a2 = os.path.join(assets_path, f) shutil.move(a1, a2) # Update the asset reference paths in markdown file m = x[:-6] + '.md' p = os.path.relpath(os.path.dirname(a1), posts_path) p = os.path.join(assets_path, p) p = os.path.relpath(p, self.root) self._update_markdown_paths(m, p)