Templating

This guide explains how to write Jinja2 templates for the sphinxnotes.render-based extension (sphinxnotes.render.ext, sphinxnotes.any and etc.). You should already be comfortable with basic Jinja2 syntax before reading this page.

What is a Template

A template is a Jinja2 text that defines how structured data is converted into reStructuredText or Markdown markup. The rendered text is then parsed by Sphinx and inserted into the document.

The way of defining template will vary depending on the extension you use. For sphinxnotes.render.ext, you can use data.template or data_define_directives.

Tip

Internally, template is a Template object. It is provide by method BaseDataDefineDirective.current_template() or BaseDataDefineRole.current_template()

What Data is Available

Your template receives data from two sources: main context and extra context.

Main Context

When you define data through a directive (such as data.define) or role in your document, the template receives that data as its main context. This is the data explicitly provided by the markup itself.

For example, when you use the data.define directive, the generated main context looks like the Python dict on the right:

Source
.. data.define:: mimi
   :color: black and brown

   I like fish!
Result
{
   'name': 'mimi',
   'attrs': {'color': 'black and brown'},
   'content': 'I like fish!'

   # Lifted attrs
   'color': 'black and brown',
}

The template receives the argument (mimi), options (:color: black ...), and body content (I like fish!) as the main context.

The following template variables are available:

{{ name }}

For directives, this refers to the directive argument.

Source
.. data.template::

   {{ name }}

.. data.define:: This is the argument
Result

This is the argument

For roles, this is not available.

{{ attrs }}

For directives, this refers to directive options. It is a mapping of option field to value, so {{ attrs.label }} and {{ attrs['label'] }} are equivalent.

Source
.. data.template::

   Label is {{ attrs.label }}.

.. data.define::
   :label: Important
Result

Label is Important.

For roles, this is not available.

Attribute values are lifted to the top-level template context when there is no name conflict. For example, {{ label }} can be used instead of {{ attrs.label }}:

Source
.. data.template::

   Label is {{ label }}.

.. data.define::
   :label: Important
Result

Label is Important.

{{ content }}

For directives, this refers to the directive body.

Source
.. data.template::

   {{ content }}

.. data.define::

   This is the body content.
Result

This is the body content.

For roles, this refers to the interpreted text.

Source
.. data.template::

   {{ content }}

 :data:`This is the interpreted text`
Result

The type of each variable depends on the corresponding schema. Different extensions define schemas differently. For example, the sphinxnotes.render.ext extension defines the schema through the data.schema directive or schema field of data_define_directives.

Tip

Internally, Main context is a ParsedData object.

Directive or role subclassed from BaseDataDefineDirective or BaseDataDefineRole can generate main context.

Extra Context

Extra context provides access to pre-prepared structured data from external sources (such as Sphinx application, JSON file, and etc.). Unlike main context which comes from the directive/role itself, extra context lets you fetch data that was prepared beforehand.

Extra contexts are typically generated on demand at different construction stages, so you need to declare them in advance, and load it in the template using the load_extra() function:

The way of declaring extra context is vary depending on the extension you use. For sphinxnotes.render.ext extension, data.template:extra, data.render:extra and the templat.extra field of data_define_directives are for this.

Source
.. data.render::
   :extra: doc

   {% set doc = load_extra('doc') %}

   Document Title: "{{ doc.title }}"
Result

Document Title: “Templating”

Built-in Extra Contexts

The following extra contexts are available:

sphinx
Phase:

all

A proxy to the sphinx.application.Sphinx object.

Source
.. data.render::
   :extra: sphinx

   {% set app = load_extra('sphinx') %}

   **{{ app.extensions | length }}**
   extensions are loaded.
Result

74 extensions are loaded.

env
Phase:

all

A proxy to the sphinx.environment.BuildEnvironment object.

Source
.. data.render::
   :extra: env

   {% set env = load_extra('env') %}

   **{{ env.all_docs | length }}**
   documents found.
Result

6 documents found.

markup
Phase:

parsing and later

Information about the current directive or role invocation, such as its type, name, source text, and line number.

Source
.. data.render::
   :extra: markup

   {%
   set m = load_extra('markup')
           | jsonify
   %}

   .. code::

      {% for line in m.split('\n') -%}
      {{ line }}
      {% endfor %}
Result
{
  "type": "directive",
  "name": "data.render",
  "lineno": 251,
  "rawtext": ".. data.render::\n   :extra: markup\n\n   {%\n   set m = load_extra('markup')\n           | jsonify\n   %}\n\n   .. code::\n\n      {% for line in m.split('\\n') -%}\n      {{ line }}\n      {% endfor %}"
}
section
Phase:

parsing and later

A proxy to the current docutils.nodes.section node, when one exists.

Source
.. data.render::
   :extra: section

    Section Title:
   "{{ load_extra('section').title }}"
Result
doc
Phase:

parsing and later

A proxy to the current docutils.notes.document node.

Source
.. data.render::
   :extra: doc

   Document title:
   "{{ load_extra('doc').title }}".
Result

Document title: “Templating”.

TODO: the proxy object.

Built-in Filters

In addition to the Builtin Jinia Filters, this extension also provides the following filters:

roles

Produces role markup from a sequence of strings.

Source
.. data.render::

   {%
   set text = ['index', 'usage']
              | roles('doc')
              | join(', ')
   %}

   :Text: ``{{ text }}``
   :Rendered: {{ text }}
Result
Text:

:doc:`index`, :doc:`usage`

Rendered:

sphinxnotes-render, Usage

jsonify

Convert value to JSON.

Source
.. data.render::

   {% set text = {'name': 'mimi'} %}

   :Strify: ``{{ text }}``
   :JSONify: ``{{ text | jsonify }}``
Result
Strify:

{'name': 'mimi'}

JSONify:

``{ “name”: “mimi”

}``

See also

Extending ilters

Render Phases

Each template has a render phase that determines when it is processed:

parsing

Render immediately while the directive or role is running. This is the default.

Choose this when the template only needs local information and does not rely on the final doctree or cross-document state.

Source
.. data.render::
   :on: parsing
   :extra: doc env

   {% set doc = load_extra('doc') %}
   {% set env = load_extra('env') %}

   - The current document has
     {{ doc.sections | length }}
     section(s).
   - The current project has
     {{ env.all_docs | length }}
     document(s).
Result
  • The current document has 4 section(s).

  • The current project has 6 document(s).

parsed

Render after the current document has been parsed.

Choose this when the template needs the complete doctree of the current document.

Source
.. data.render::
   :on: parsed
   :extra: doc env

   {% set doc = load_extra('doc') %}
   {% set env = load_extra('env') %}

   - The current document has
     {{ doc.sections | length }}
     section(s).
   - The current project has
     {{ env.all_docs | length }}
     document(s).
Result
  • The current document has 6 section(s).

  • The current project has 6 document(s).

resolving

Render late in the build, after references and other transforms are being resolved.

Choose this when the template depends on the document structure that is only stable near the end of the pipeline.

Source
.. data.render::
   :on: resolving
   :extra: doc env

   {% set doc = load_extra('doc') %}
   {% set env = load_extra('env') %}

   - The current document has
     {{ doc.sections | length }}
     section(s).
   - The current project has
     {{ env.all_docs | length }}
     document(s).
Result
  • The current document has 6 section(s).

  • The current project has 8 document(s).

Tip

Internally, each phase corresponds to a Phase enum value. The on option maps to phase.

Debugging

Enable the debug option to see a detailed report when troubleshooting templates:

Source
.. data.render::
   :debug:

   {{ 1 + 1 }}
Result

2

This is especially useful when a template fails due to an undefined variable, unexpected data shape, or invalid generated markup.

Some Technical Details

Jinja Template

Templates are rendered in a sandboxed Jinja2 environment.

  • Undefined variables raise errors by default (undefined=DebugUndefined)

  • Extension jinja2.ext.loopcontrols, jinja2.ext.do are enabled by default.