# -*- coding: utf-8 -*- r""" Parser for Jupyter notebooks ============================ Class that holds the Jupyter notebook information """ # Author: Óscar Nájera # License: 3-clause BSD from __future__ import division, absolute_import, print_function from functools import partial import argparse import json import re import sys from .py_source_parser import split_code_and_text_blocks def jupyter_notebook_skeleton(): """Returns a dictionary with the elements of a Jupyter notebook""" py_version = sys.version_info notebook_skeleton = { "cells": [], "metadata": { "kernelspec": { "display_name": "Python " + str(py_version[0]), "language": "python", "name": "python" + str(py_version[0]) }, "language_info": { "codemirror_mode": { "name": "ipython", "version": py_version[0] }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython" + str(py_version[0]), "version": '{0}.{1}.{2}'.format(*sys.version_info[:3]) } }, "nbformat": 4, "nbformat_minor": 0 } return notebook_skeleton def directive_fun(match, directive): """Helper to fill in directives""" directive_to_alert = dict(note="info", warning="danger") return ('

{1}

{2}

' .format(directive_to_alert[directive], directive.capitalize(), match.group(1).strip())) def rst2md(text): """Converts the RST text from the examples docstrigs and comments into markdown text for the Jupyter notebooks""" top_heading = re.compile(r'^=+$\s^([\w\s-]+)^=+$', flags=re.M) text = re.sub(top_heading, r'# \1', text) math_eq = re.compile(r'^\.\. math::((?:.+)?(?:\n+^ .+)*)', flags=re.M) text = re.sub(math_eq, lambda match: r'\begin{{align}}{0}\end{{align}}'.format( match.group(1).strip()), text) inline_math = re.compile(r':math:`(.+?)`', re.DOTALL) text = re.sub(inline_math, r'$\1$', text) directives = ('warning', 'note') for directive in directives: directive_re = re.compile(r'^\.\. %s::((?:.+)?(?:\n+^ .+)*)' % directive, flags=re.M) text = re.sub(directive_re, partial(directive_fun, directive=directive), text) links = re.compile(r'^ *\.\. _.*:.*$\n', flags=re.M) text = re.sub(links, '', text) refs = re.compile(r':ref:`') text = re.sub(refs, '`', text) contents = re.compile(r'^\s*\.\. contents::.*$(\n +:\S+: *$)*\n', flags=re.M) text = re.sub(contents, '', text) images = re.compile( r'^\.\. image::(.*$)(?:\n *:alt:(.*$)\n)?(?: +:\S+:.*$\n)*', flags=re.M) text = re.sub( images, lambda match: '![{1}]({0})\n'.format( match.group(1).strip(), (match.group(2) or '').strip()), text) return text def jupyter_notebook(script_blocks): """Generate a Jupyter notebook file cell-by-cell Parameters ---------- script_blocks: list script execution cells """ work_notebook = jupyter_notebook_skeleton() add_code_cell(work_notebook, "%matplotlib inline") fill_notebook(work_notebook, script_blocks) return work_notebook def add_code_cell(work_notebook, code): """Add a code cell to the notebook Parameters ---------- code : str Cell content """ code_cell = { "cell_type": "code", "execution_count": None, "metadata": {"collapsed": False}, "outputs": [], "source": [code.strip()] } work_notebook["cells"].append(code_cell) def add_markdown_cell(work_notebook, text): """Add a markdown cell to the notebook Parameters ---------- code : str Cell content """ markdown_cell = { "cell_type": "markdown", "metadata": {}, "source": [rst2md(text)] } work_notebook["cells"].append(markdown_cell) def fill_notebook(work_notebook, script_blocks): """Writes the Jupyter notebook cells Parameters ---------- script_blocks : list of tuples """ for blabel, bcontent, lineno in script_blocks: if blabel == 'code': add_code_cell(work_notebook, bcontent) else: add_markdown_cell(work_notebook, bcontent + '\n') def save_notebook(work_notebook, write_file): """Saves the Jupyter work_notebook to write_file""" with open(write_file, 'w') as out_nb: json.dump(work_notebook, out_nb, indent=2) ############################################################################### # Notebook shell utility def python_to_jupyter_cli(args=None, namespace=None): """Exposes the jupyter notebook renderer to the command line Takes the same arguments as ArgumentParser.parse_args """ parser = argparse.ArgumentParser( description='Sphinx-Gallery Notebook converter') parser.add_argument('python_src_file', nargs='+', help='Input Python file script to convert. ' 'Supports multiple files and shell wildcards' ' (e.g. *.py)') args = parser.parse_args(args, namespace) for src_file in args.python_src_file: file_conf, blocks = split_code_and_text_blocks(src_file) print('Converting {0}'.format(src_file)) example_nb = jupyter_notebook(blocks) save_notebook(example_nb, src_file.replace('.py', '.ipynb'))