Skip to content

Latest commit

 

History

History
407 lines (259 loc) · 9.76 KB

File metadata and controls

407 lines (259 loc) · 9.76 KB

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 :rst:dir:`data.template` or :confval:`render_ext_data_define_directives`.

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 :rst:dir:`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:

.. example::

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

      I like fish!

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:

.. glossary::

   ``{{ name }}``
      For directives, this refers to the directive argument.

      .. example::

         .. data.template::

            {{ name }}

         .. data.define:: 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.

      .. example::

         .. data.template::

            Label is {{ attrs.label }}.

         .. data.define::
            :label: 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 }}``:

      .. example::

         .. data.template::

            Label is {{ label }}.

         .. data.define::
            :label: Important

   ``{{ content }}``
      For directives, this refers to the directive body.

      .. example::

         .. data.template::

            {{ content }}

         .. data.define::

            This is the body content.

      For roles, this refers to the interpreted text.

      .. example::

         .. data.template::

            {{ content }}

          :data:`This is the interpreted text`

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 :rst:dir:`data.schema` directive or schema field of :confval:`render_ext_data_define_directives`.

Tip

Internally, Main context is a :py:class:`~sphinxnotes.render.ParsedData` object.

Directive or role subclassed from :py:class:`~sphinxnotes.render.BaseDataDefineDirective` or :py:class:`~sphinxnotes.render.BaseDataDefineRole` can generate main context.

Extra Context

.. glossary::

   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.

   ``load_extra``
      Extra contexts are generated on demand. Load them in the template using the
      ``load_extra()`` function. You can also pass positional and keyword arguments
      to ``load_extra()``, which are forwarded to the extra context's
      ``generate()`` method.

.. example::

   .. data.render::

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

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

   .. data.render::

      {% set docs = load_extra('all_docs', count=3) %}

      {{ docs | join(', ') }}

Built-in Extra Contexts

The following extra contexts are available:

app
Phase:all

A proxy to the :py:class:`sphinx.application.Sphinx` object.

.. example::

   .. data.render::

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

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

env
Phase:all

A proxy to the :py:class:`sphinx.environment.BuildEnvironment` object.

.. example::

   .. data.render::

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

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

markup
Phase::term:`parsing`

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

.. example::

   .. data.render::

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

      .. code::

         {% for line in m.split('\n') -%}
         {{ line }}
         {% endfor %}

section
Phase::term:`parsed` and :term:`resolving`

A proxy to the current :py:class:`docutils.nodes.section` node, when one exists. This extra context is not available during the parsing phase.

.. example::

   .. data.render::
      :on: parsed

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

doc
Phase:all

A proxy to the current :py:class:`docutils.notes.document` node.

.. example::

   .. data.render::

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

.. seealso:: :ref:`ext-extra-context`

TODO: the proxy object.

Built-in Filters

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

.. glossary::

   ``roles``
      Produces role markup from a sequence of strings.

      .. example::

         .. data.render::

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

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

   ``jsonify``
      Convert value to JSON.

      .. example::

         .. data.render::

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

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

.. seealso:: :ref:`ext-filters`

Render Phases

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

.. glossary::

   ``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.

      .. example::

         .. data.render::
            :on: parsing

            {% 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).

   ``parsed``
      Render after the current document has been parsed.

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

      .. example::

         .. data.render::
            :on: parsed

            {% 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).

   ``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.

      .. example::

         .. data.render::
            :on: resolving

            {% 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).

Tip

Internally, each phase corresponds to a :py:class:`~sphinxnotes.render.Phase` enum value. The on option maps to :py:attr:`~sphinxnotes.render.Template.phase`.

Debugging

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

.. example::

   .. data.render::
      :debug:

      {{ 1 + 1 }}

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.