skip to main content

@tonyfast s notebooks

site navigation
notebook summary
title
a template for exporting markdownish notebooks
description
we need a combination of markdown and html templates to operate optimally with mkdocs. this document frankensteins templates and gives us some nice pixels.
cells
26 total
14 code
state
executed out of order
kernel
Python [conda env:root] *
language
python
name
conda-root-py
lines of code
89
outputs
1
table of contents
{"kernelspec": {"display_name": "Python [conda env:root] *", "language": "python", "name": "conda-root-py"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13"}, "widgets": {"application/vnd.jupyter.widget-state+json": {"state": {}, "version_major": 2, "version_minor": 0}}, "title": "a template for exporting markdownish notebooks", "description": "we need a combination of markdown and html templates to operate optimally with mkdocs.\nthis document frankensteins templates and gives us some nice pixels."}
notebook toolbar
Activate
cell ordering
1

a template for exporting markdownish notebooks

2

we need a combination of markdown and html templates to operate optimally with mkdocs. this document frankensteins templates and gives us some nice pixels.

3

get_template creates our hybrid template from the initial TEMPLATE and goes on to append blocks from nbconvert templates.

4
    def get_template() -> str:
        blocks = __import__("collections").ChainMap(*map(get_blocks, "classic/base.html.j2 classic/index.html.j2".split()))
        template = TEMPLATE 
        for k in KEEP:
            if k in blocks: template += blocks[k].group("block") + "\n" * 3
        return template
5

we KEEP some blocks from the classic templates

6
    KEEP = "notebook_css execute_result  stream_stdout stream_stderr \
    data_svg data_html data_png data_jpg error traceback_line data_widget_state data_widget_view".split()
7

get_blocks loads a template and returns a dictionary of its blocks.

8
    def get_blocks(alias) -> dict[str, (T := __import__("typing")).Pattern]:
        template = get_exporter().environment.get_template(alias)
        with open(template.filename) as file: body = file.read()
        return dict(yield_blocks(body))
9

yield all the blocks recursing into the found matches

10
    def yield_blocks(string) -> T.Iterator[tuple[str, T.Pattern]]:
        from tonyfast.regexs import jinja_block
        for m in jinja_block.finditer(string):
            yield m.group("name"), m
            yield from yield_blocks(m.group("inner"))
11

initialize our exporter and jinja environment

12

i want to hide the input code, but no remove it; a custom exporter felt like a better way.

13
    import nbconvert, re
    class PidgyExporter(nbconvert.exporters.HTMLExporter):
        def from_notebook_node(self, nb, resources=None, **kw):
            resources = self._init_resources(dict(is_pidgy=is_pidgy(nb)))
            return super().from_notebook_node(nb, resources, **kw)
14

we moved is_pidgy and PIDGY from the mkdocs plugin because them make more self here.

15
    def is_pidgy(nb):
        for cell in nb["cells"]:
            if cell["cell_type"] == "code":
                if PIDGY.match("".join(cell["source"])):
                    return True
        return False
16
    PIDGY = re.compile(r"\s*%(re)?load_ext pidgy")
17
    def replace_attachments(cell):
        source = "".join(cell["source"])
        if cell.get("attachments"):
            for k, v in cell["attachments"].items():
                for t, v in v.items():
                    source = source.replace("attachment:" + k, "data:" + t + ";base64," + v)
        return source
18
    @(cache := __import__("functools").lru_cache)
    def get_exporter() -> "nbconvert.TemplateExporter":
        exporter = PidgyExporter()
        exporter.environment.filters.setdefault("highlight_code", lambda x: x)
        exporter.environment.filters.setdefault("attachment", replace_attachments)
        return exporter
19

generating the template

this template holds so of our own logic.

20
    TEMPLATE = """{%- extends 'display_priority.j2' -%}
    {% block body_footer %}
    {{super()}}
    {% set mimetype = 'application/vnd.jupyter.widget-state+json'%}
    {% if mimetype in nb.metadata.get("widgets",{})%}
    <script type="{{ mimetype }}">
    {{ nb.metadata.widgets[mimetype] | json_dumps  }}
    </script>
    {% endif %}
    </script>

    {% endblock %}
    
    {% block data_markdown scoped %}
    
    {{output.data['text/markdown']}}
    
    {% endblock data_markdown %}

    {% block markdowncell scoped %}
    
    {{cell | attachment}}
    
    {% endblock markdowncell %} 
    
    {% block input %}
    {% if not resources.is_pidgy %}
    ``````````````````````````````````````````````````````````````python
    {{cell.source}}
    
    ``````````````````````````````````````````````````````````````
    {% endif %}
    {% endblock %}
    """
21
    HEAD = """{% from 'base/jupyter_widgets.html.j2' import jupyter_widgets %}
    <script src="{{ resources.require_js_url }}"></script>
    {{ jupyter_widgets(resources.jupyter_widgets_base_url, resources.html_manager_semver_range, resources.widget_renderer_url) }}
    """
22

we can then combine our base template with existing nbconvert ones to compute the final template.

23
    template = get_template()
24

the generated template

25
    if "__file__" not in locals():
        display({"text/markdown": F"``````````````````````````html+jinja\n{template}\n``````````````````````````"}, raw=True)
1 outputs.
{%- extends 'display_priority.j2' -%}
{% block body_footer %}
{{super()}}
{% set mimetype = 'application/vnd.jupyter.widget-state+json'%}
{% if mimetype in nb.metadata.get("widgets",{})%}
<script type="{{ mimetype }}">
{{ nb.metadata.widgets[mimetype] | json_dumps  }}
</script>
{% endif %}
</script>

{% endblock %}

{% block input %}
{% if resources.is_pidgy %}<div class="cell source" hidden>
{% endif %}
````````````````````````python
{{cell.source}}
````````````````````````
{% if resources.is_pidgy %}</div>
{% endif %}
{% endblock input %}

{% block data_markdown scoped %}

{{output.data['text/markdown']}}

{% endblock data_markdown %}

{% block markdowncell scoped %}

{{cell | attachment}}

{% endblock markdowncell %} 

{% block notebook_css %}
{{ resources.include_css("static/style.css") }}
<style type="text/css">
/* Overrides of notebook CSS for static HTML export */
body {
  overflow: visible;
  padding: 8px;
}

div#notebook {
  overflow: visible;
  border-top: none;
}

{%- if resources.global_content_filter.no_prompt-%}
div#notebook-container{
  padding: 6ex 12ex 8ex 12ex;
}
{%- endif -%}

@media print {
  body {
    margin: 0;
  }
  div.cell {
    display: block;
    page-break-inside: avoid;
  }
  div.output_wrapper {
    display: block;
    page-break-inside: avoid;
  }
  div.output {
    display: block;
    page-break-inside: avoid;
  }
}
</style>
{% endblock notebook_css %}


{% block execute_result -%}
{%- set extra_class="output_execute_result" -%}
{% block data_priority scoped %}
{{ super() }}
{% endblock data_priority %}
{%- set extra_class="" -%}
{%- endblock execute_result %}


{% block stream_stdout -%}
<div class="output_subarea output_stream output_stdout output_text">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endblock stream_stdout %}


{% block stream_stderr -%}
<div class="output_subarea output_stream output_stderr output_text">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endblock stream_stderr %}


{% block data_svg scoped -%}
<div class="output_svg output_subarea {{ extra_class }}">
{%- if output.svg_filename %}
<img src="{{ output.svg_filename | posix_path }}">
{%- else %}
{{ output.data['image/svg+xml'] }}
{%- endif %}
</div>
{%- endblock data_svg %}


{% block data_html scoped -%}
<div class="output_html rendered_html output_subarea {{ extra_class }}">
{%- if output.get('metadata', {}).get('text/html', {}).get('isolated') -%}
<iframe
    class="isolated-iframe"
    style="height:520px; width:100%; margin:0; padding: 0"
    frameborder="0"
    scrolling="auto"
    src="data:text/html;base64,{{output.data['text/html'] | text_base64}}">
</iframe>
{%- else -%}
{{ output.data['text/html'] }}
{%- endif -%}
</div>
{%- endblock data_html %}


{% block data_png scoped %}
<div class="output_png output_subarea {{ extra_class }}">
{%- if 'image/png' in output.metadata.get('filenames', {}) %}
<img src="{{ output.metadata.filenames['image/png'] | posix_path }}"
{%- else %}
<img src="data:image/png;base64,{{ output.data['image/png'] }}"
{%- endif %}
{%- set width=output | get_metadata('width', 'image/png') -%}
{%- if width is not none %}
width={{ width }}
{%- endif %}
{%- set height=output | get_metadata('height', 'image/png') -%}
{%- if height is not none %}
height={{ height }}
{%- endif %}
{%- if output | get_metadata('unconfined', 'image/png') %}
class="unconfined"
{%- endif %}
{%- set alttext=(output | get_metadata('alt', 'image/png')) or (cell | get_metadata('alt')) -%}
{%- if alttext is not none %}
alt="{{ alttext }}"
{%- endif %}
>
</div>
{%- endblock data_png %}


{% block data_jpg scoped %}
<div class="output_jpeg output_subarea {{ extra_class }}">
{%- if 'image/jpeg' in output.metadata.get('filenames', {}) %}
<img src="{{ output.metadata.filenames['image/jpeg'] | posix_path }}"
{%- else %}
<img src="data:image/jpeg;base64,{{ output.data['image/jpeg'] }}"
{%- endif %}
{%- set width=output | get_metadata('width', 'image/jpeg') -%}
{%- if width is not none %}
width={{ width }}
{%- endif %}
{%- set height=output | get_metadata('height', 'image/jpeg') -%}
{%- if height is not none %}
height={{ height }}
{%- endif %}
{%- if output | get_metadata('unconfined', 'image/jpeg') %}
class="unconfined"
{%- endif %}
{%- set alttext=(output | get_metadata('alt', 'image/jpeg')) or (cell | get_metadata('alt')) -%}
{%- if alttext is not none %}
alt="{{ alttext }}"
{%- endif %}
>
</div>
{%- endblock data_jpg %}


{% block error -%}
<div class="output_subarea output_text output_error">
<pre>
{{- super() -}}
</pre>
</div>
{%- endblock error %}


{%- block traceback_line %}
{{ line | ansi2html }}
{%- endblock traceback_line %}


{%- block data_widget_state scoped %}
{% set div_id = uuid4() %}
{% set datatype_list = output.data | filter_data_type %}
{% set datatype = datatype_list[0]%}
<div id="{{ div_id }}" class="output_subarea output_widget_state {{ extra_class }}">
<script type="text/javascript">
var element = $('#{{ div_id }}');
</script>
<script type="{{ datatype }}">
{{ output.data[datatype] | json_dumps }}
</script>
</div>
{%- endblock data_widget_state -%}


{%- block data_widget_view scoped %}
{% set div_id = uuid4() %}
{% set datatype_list = output.data | filter_data_type %}
{% set datatype = datatype_list[0]%}
<div id="{{ div_id }}" class="output_subarea output_widget_view {{ extra_class }}">
<script type="text/javascript">
var element = $('#{{ div_id }}');
</script>
<script type="{{ datatype }}">
{{ output.data[datatype] | json_dumps }}
</script>
</div>
{%- endblock data_widget_view -%}
26