Skip to content

Commit 3c63dea

Browse files
committed
Add jupyter configuration files to export .jl files
Add Jupyter configs to automatically export a script file whenever a Jupyter Notebook file (.ipynb) in the repo is saved. This makes diffing changes in git significantly easier. The files are exported to a top-level directory called .nbexports, which mirrors the filepaths of the real files. (Note, when scripts are renamed / deleted, it will require manual cleanup from the .nbexports directory)
1 parent c92547d commit 3c63dea

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed

jupyter_notebook_config.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import io
2+
import os
3+
4+
dir_path = os.path.dirname(os.path.realpath(__file__))
5+
nbexports_path = os.path.join(dir_path, ".nbexports")
6+
7+
from notebook.utils import to_api_path
8+
9+
_script_exporter = None
10+
11+
def script_post_save(model, os_path, contents_manager, **kwargs):
12+
if model['type'] != 'notebook':
13+
return
14+
15+
from nbconvert.exporters.script import ScriptExporter
16+
from nbconvert.exporters.html import HTMLExporter
17+
18+
global _script_exporter
19+
if _script_exporter is None:
20+
_script_exporter = ScriptExporter(parent=contents_manager)
21+
_script_exporter.template_file = os.path.join(dir_path, 'jupyter_script_export_template.tpl')
22+
23+
export_script(_script_exporter, model, os_path, contents_manager, **kwargs)
24+
25+
def export_script(exporter, model, os_path, contents_manager, **kwargs):
26+
"""convert notebooks to Python script after save with nbconvert
27+
replaces `ipython notebook --script`
28+
"""
29+
base, ext = os.path.splitext(os_path)
30+
script, resources = exporter.from_filename(os_path)
31+
script_fname = base + resources.get('output_extension', '.txt')
32+
script_repopath = to_api_path(script_fname, contents_manager.root_dir)
33+
log = contents_manager.log
34+
script_fullpath = os.path.join(dir_path, ".nbexports", script_repopath)
35+
os.makedirs(os.path.dirname(script_fullpath), exist_ok=True)
36+
log.info("Saving script /%s", script_fullpath)
37+
with io.open(script_fullpath, 'w', encoding='utf-8') as f:
38+
f.write(script)
39+
40+
41+
c.FileContentsManager.post_save_hook = script_post_save
42+

jupyter_script_export_template.tpl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{# Lines inside these brackets are comments #}
2+
{#- Brackets with a `-` mean to skip whitespace before or after the block. -#}
3+
4+
{#-
5+
# This file defines a Jinja Template for converting .ipynb files into scripts
6+
# for either delve or julia. We need this because the default script exporter
7+
# doesn't render Markdown for any languages except Python.
8+
# Exporting the markdown makes github reviews of .ipynb files easier.
9+
# This template is invoked by our custom `jupyter_notebook_config.py`. You can
10+
# read more about the Jinja template specification here:
11+
# http://jinja.pocoo.org/docs/2.10/templates/#comments
12+
# and here:
13+
# https://nbconvert.readthedocs.io/en/latest/customizing.html
14+
# And the filter functions used in this file are defined here:
15+
# https://github.com/pallets/jinja/blob/master/jinja2/filters.py
16+
# and here:
17+
# https://github.com/jupyter/nbconvert/blob/master/nbconvert/filters/strings.py
18+
-#}
19+
20+
{#- ---------
21+
# Lines up here, before the `extends` section, go at the top of the file, before any other
22+
# content from the notebook itself.
23+
#----------- -#}
24+
25+
{%- if 'name' in nb.metadata.get('kernelspec', {}) and
26+
nb.metadata.kernelspec.name == 'julia' -%}
27+
# This file was generated from a Julia language jupyter notebook.
28+
{% endif -%}
29+
30+
{% extends 'script.tpl'%}
31+
32+
{% block markdowncell %}
33+
{#- Turn the contents of the markdown cell into a wrapped comment block, and trim empty lines. -#}
34+
35+
{#-
36+
# NOTE: We used `kernelspec.name` not `language_info.name`, for reasons specific to
37+
# our custom jupyter kernel. I think `language_info.name` might be more robust?
38+
-#}
39+
{%- if 'name' in nb.metadata.get('kernelspec', {}) and
40+
nb.metadata.kernelspec.name == 'julia' -%}
41+
{%- set commentprefix = '# ' -%}
42+
{#-
43+
# Add other languages here as if-else block, e.g. C++ would use '// '
44+
-#}
45+
{%- else -%}
46+
{#- Assume python by default -#}
47+
{%- set commentprefix = '# ' -%}
48+
{%- endif -%}
49+
50+
{%- set commentlen = 92-(commentprefix|length) -%}
51+
{{- '\n' -}}
52+
{{- commentprefix ~ '-' * commentlen -}}
53+
{{- '\n' -}}
54+
55+
{#- Turn the contents of the markdown cell into a wrapped comment block, and trim empty lines. -#}
56+
{#- Note: `comment_lines` and `wrap_text` are defined in nbconvert/filters/strings.py -#}
57+
{{- cell.source | wrap_text(width=commentlen) | comment_lines(prefix=commentprefix) | replace(commentprefix~"\n", commentprefix|trim ~ "\n") -}}
58+
59+
{{- '\n' -}}
60+
{{- commentprefix ~ '-' * commentlen -}}
61+
{{- '\n' -}}
62+
63+
{% endblock markdowncell %}

0 commit comments

Comments
 (0)