Shortcodes
Shortcodes turn a folder of small templates into Jinja tags, the way Hugo's shortcodes work. Each file becomes a tag named after the file's stem. A file may be spelled .html, .jinja, .j2, .html.jinja, or .html.j2; the whole suffix comes off, so figure.html.jinja is the tag figure.
shortcodes/
youtube.html {# tag: {% youtube %} #}
onboarding-wizard-modal.html {# tag: {% onboarding_wizard_modal %} #}
note.html {# uses `inner` -> paired shortcode #}
Registering shortcodes
Build an extension from a folder with shortcode_extension and add it to your Jinja Environment:
from jinja2 import Environment, FileSystemLoader
from starlette_templates.shortcodes import shortcode_extension
env = Environment(
loader=FileSystemLoader("templates"),
extensions=[shortcode_extension("shortcodes")],
)
The same approach works for pages served by StaticFiles. Add the extension to the environment you build the loader around. Each call to the factory returns its own extension, so several shortcode folders can coexist in one environment.
Void and paired shortcodes
A shortcode's template decides whether the tag takes a body. When the template references the inner variable, the shortcode is paired and the caller must close it with {% end<name> %}. Otherwise the shortcode is void and takes no end tag.
shortcodes/youtube.html is void, because it does not reference inner:
<iframe src="https://www.youtube.com/embed/{{ id }}" allowfullscreen></iframe>
shortcodes/note.html is paired, because it references inner:
<div class="note note-{{ kind }}">{{ inner }}</div>
Use both in a template:
{# void shortcode: no end tag #}
{% youtube id="dQw4w9WgXcQ" %}
{# paired shortcode: the body becomes `inner` in the template #}
{% note kind="warning" %}
Do not feed the llamas after midnight.
{% endnote %}
What a shortcode template receives
A shortcode template receives three things:
- Every keyword argument from the call site, such as
idorkind. A call-site argument wins over the calling context. inner, the rendered body, for a paired shortcode.inneris reserved, so the factory rejects it as a call-site keyword argument.- The full context of the calling template, including page variables and globals.
{% note kind="tip" %}
Logged in as {{ request.user.display_name }}.
{% endnote %}
A shortcode also works in a Markdown file included with include_markdown(), because the Markdown renders through the same environment. See Shortcodes and fragments in Markdown for the two passes and the spacing rules they impose.
Tag names
normalize_shortcode_name builds each tag name. It works like slugify, but with underscores. It reduces unicode to ASCII, lowercases the result, and replaces every run of non-alphanumeric characters with a single underscore.
| File | Tag |
|---|---|
youtube.html |
youtube |
onboarding-wizard-modal.html |
onboarding_wizard_modal |
Café.html |
cafe |
Discovery and validation
The factory discovers the folder once, when you call shortcode_extension(). A file you add later does not become a tag. A discovered file you delete later raises TemplateNotFound at render time. The factory picks up an edit to a discovered template on the next render, from the file's modification time.
A file whose name cannot become a usable tag fails discovery with a ValueError, instead of being skipped. A name fails when it:
- normalizes to nothing, or to a non-identifier such as
3d.html; - collides with a reserved Jinja tag such as
for.html; - normalizes to the same tag as another file; or
- shadows another tag's end tag, such as
endnote.htmlnext tonote.html.
The factory catches these at startup, so a misnamed snippet is a loud error rather than a tag that never fires.