Zola is a static site generator that I've been using for my story sites and a couple of other documentation sites. It specializes in converting trees of markdown into stylish websites through a template library. As I've been getting better at, I've really started to enjoy it. Since my scattershot brain depends on lists and lookups, I have a lot of "definition files" that are nothing but a syntax example and some explanation of what it means.

Zola uses the Tera templating engine, which closely resembles Django's, but has also extended it greatly. Tera provides macros that can be called within an HTML template to render repetitive bits of data, and Zola has a feature called shortcodes, that do the same thing but can be imported into the markdown itself.

It took a few tries, but I finally figured out how to mix Zola shortcodes and macros to create dynamic, nested information blocks. It turns out that it's actually easy to import these features directly into Zola. Zola really doesn't care too much about its templating language, which suggests it might actually be pretty good for generating EPUB as well as HTML. I'll have to look into that.

Anyway, let's say I have a collection of definitions. An HTML definition list looks like this:

<dl>
  <dt>Term to be defined</dt>
  <dd>Definition of the term.</dd>
</dl>

I'm a fan of these things because they start with the token my mind uses for a thing, and then the definition can contain the information I need to make use of the thing. So let's say that I have a definition list, and it can contain nested definitions in a JSON file named "definitions.json":

{
  "definitions": [
    { "dt": "undefined", "dd": "the thing is undefined" },
    { "dt": "defined", "dd": "the thing is defined", definitions: [
      { "dt": "truthy", "dd": "the defined thing is truthy" },
      { "dt": "falsy", "dd": "the defined thing is falsy" }
    ]}
  ]
}

I want to render this as a nested list of definitions. I'm going to start with the renderer, which is a Tera macro. The most important detail here is to notice the self:: syntax, which allows a macro to call itself recursively.

{% macro definition_body(source) %}
<dl>
  {% for entry in source %}
    <dt><kbd>{{ entry.dt | safe }}</kbd></dt>
    <dd>
      {{ entry.dd | safe }}
      {% if entry.definitions %}
        {{ self::definition_body(source=entry.definitions) }}
      {% endif %}
    </dd>
  {% endfor %}
</dl>
{% endmacro %}

You can see that we do have some expectations about the protocol: it must contain dt, dd, and children may have an optional definition fields, and definitions are always a list.

We kick off the render simply enough with a Zola shortcode block. The dashes within the top line tell Zola to eliminated any potential whitespace around that import statement.

{%- import 'macros/definition_body.html' as renderer -%}
<div>
  {% set content = load_data(path=source) %}
  {{ renderer::definition_body(source=content.definitions) }}
</div>

And we render a specific definition file with a basic shortcode invocation, which you can put anywhere in your Markdown file and it will just be assembled with the rest of the page. That @ syntax tells Zola that the file will be found off the root of the /content folder.

{­{ definition_list(source="@/docs/definitions.json") }}

And that's how you mix Zola's shortcode and macro features to handle nested, recursive list rendering.