Skip to content

a template for exporting markdownish notebooks¤

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

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

    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

we KEEP some blocks from the classic templates

    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()

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

    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))

yield all the blocks recursing into the found matches

    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"))

initialize our exporter and jinja environment

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

    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)

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

    def is_pidgy(nb):
        for cell in nb["cells"]:
            if cell["cell_type"] == "code":
                if PIDGY.match("".join(cell["source"])):
                    return True
        return False
    PIDGY = re.compile(r"\s*%(re)?load_ext pidgy")
    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
    @(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

generating the template¤

this template holds so of our own logic.

    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 %}


    {% 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 %}
    """
    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) }}
    """

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

    template = get_template()

the generated template¤

    if "__file__" not in locals():
        display({"text/markdown": F"``````````````````````````html+jinja\n{template}\n``````````````````````````"}, raw=True)
{%- 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 %}


{% 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 alt="No description has been provided for this image" 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" frameborder="0" scrolling="auto" src="data:text/html;base64,{{output.data['text/html'] | text_base64}}" style="height:520px; width:100%; margin:0; padding: 0">
</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 %}="" 'image="" (cell="" -%}="" <img="" alt="{{ alttext }}" alttext="" class="unconfined" else="" endif="" get_metadata('alt'))="" get_metadata('alt',="" get_metadata('height',="" get_metadata('unconfined',="" get_metadata('width',="" height="" if="" is="" none="" not="" or="" output="" png')="" png'))="" set="" src="data:image/png;base64,{{ output.data['image/png'] }}" width="" {%-="" |="" }}=""/>
</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 %}="" 'image="" (cell="" -%}="" <img="" alt="{{ alttext }}" alttext="" class="unconfined" else="" endif="" get_metadata('alt'))="" get_metadata('alt',="" get_metadata('height',="" get_metadata('unconfined',="" get_metadata('width',="" height="" if="" is="" jpeg')="" jpeg'))="" none="" not="" or="" output="" set="" src="data:image/jpeg;base64,{{ output.data['image/jpeg'] }}" width="" {%-="" |="" }}=""/>
</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 class="output_subarea output_widget_state {{ extra_class }}" id="{{ div_id }}">
<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 class="output_subarea output_widget_view {{ extra_class }}" id="{{ div_id }}">
<script type="text/javascript">
var element = $('#{{ div_id }}');
</script>
<script type="{{ datatype }}">
{{ output.data[datatype] | json_dumps }}
</script>
</div>
{%- endblock data_widget_view -%}