Module

rendering.plugins.variable_substitution

Variable substitution plugin for Mistune.

Provides safe {{ variable }} replacement in markdown content while keeping code blocks literal and maintaining clear separation from template logic.

Classes

VariableSubstitutionPlugin
Mistune plugin for safe variable substitution in markdown content. ARCHITECTURE: Separation of Con…
8

Mistune plugin for safe variable substitution in markdown content.

ARCHITECTURE: Separation of Concerns

This plugin handles ONLY variable substitution ({{ vars }}) in markdown. It operates at the AST level after Mistune parses the markdown structure.

WHAT THIS HANDLES:


✅ {{ page.metadata.xxx }} - Access page frontmatter ✅ {{ site.config.xxx }} - Access site configuration ✅ {{ page.title }}, {{ page.date }}, etc. - Page properties

WHAT THIS DOESN'T HANDLE:

❌ {% if condition %} - Conditional blocks ❌ {% for item %} - Loop constructs ❌ Complex Jinja2 logic

WHY: Conditionals and loops belong in TEMPLATES, not markdown.

Example - Using in Markdown: Welcome to {{ page.metadata.product_name }} version {{ page.metadata.version }}.

Connect to {{ page.metadata.api_url }}/users

Example - Escaping Syntax: Use {{/* page.title */}} to display the page title.

This renders as: Use {{ page.title }} to display the page title.

Example - Using Conditionals in Templates: <!-- templates/page.html --> <article> {% if page.metadata.beta %} <div class="beta-notice">Beta Feature</div> {% endif %}

  {{ content }}  <!-- Markdown with {{ vars }} renders here -->
</article>

KEY FEATURE: Code blocks stay literal naturally!

Since this plugin only processes text tokens (not code tokens), code blocks and inline code automatically preserve their content:

Use `{{ page.title }}` to show the title.  ← Stays literal in output

```python
# This {{ var }} stays literal too!
print("{{ page.title }}")
```

This is the RIGHT architectural approach:

  • Single-pass parsing (fast!)
  • Natural code block handling (no escaping needed!)
  • Clear separation: content (markdown) vs logic (templates)

Methods 4

update_context
Update the rendering context (for parser reuse).
1 None
def update_context(self, context: dict[str, Any]) -> None

Update the rendering context (for parser reuse).

Parameters 1
context dict[str, Any]

New context dict with variables (page, site, config, etc.)

preprocess
Handle escaped syntax {{/* ... */}} before parsing.
1 str
def preprocess(self, text: str) -> str

Handle escaped syntax {{/* ... */}} before parsing.

Parameters 1
text str
Returns

str

substitute_variables
Substitute {{ variable }} expressions in text nodes.
1 str
def substitute_variables(self, text: str) -> str

Substitute {{ variable }} expressions in text nodes.

Parameters 1
text str
Returns

str

restore_placeholders
Restore placeholders to HTML-escaped template syntax. This uses HTML entities …
1 str
def restore_placeholders(self, html: str) -> str

Restore placeholders to HTML-escaped template syntax.

This uses HTML entities to prevent Jinja2 from processing the restored template syntax. The browser will render &#123;&#123; as {{ in the final output.

This is the correct long-term solution because:

  • Jinja2 won't see {{ so it won't try to template it
  • The browser renders entities as literal {{ for users to see
  • No timing issues or re-processing concerns
  • Works for documentation examples, code snippets, etc.
Parameters 1
html str

HTML output from Mistune

Returns

str

HTML with placeholders restored as HTML entities

Internal Methods 4
__init__
Initialize with rendering context.
1 None
def __init__(self, context: dict[str, Any])

Initialize with rendering context.

Parameters 1
context dict[str, Any]

Dict with variables (page, site, config, etc.)

__call__
Register the plugin with Mistune.
1 None
def __call__(self, md: Any) -> None

Register the plugin with Mistune.

Parameters 1
md Any
_substitute_variables
Legacy method combining preprocess and substitution. Kept for backward compatib…
1 str
def _substitute_variables(self, text: str) -> str

Legacy method combining preprocess and substitution. Kept for backward compatibility if needed, but splitting usage is preferred.

Parameters 1
text str
Returns

str

_eval_expression
Safely evaluate a simple expression like 'page.metadata.title'. Supports dot n…
1 Any
def _eval_expression(self, expr: str) -> Any

Safely evaluate a simple expression like 'page.metadata.title'.

Supports dot notation for accessing nested attributes/dict keys.

SECURITY: Blocks access to private/dunder attributes to prevent:

  • {{ page.class.bases }}
  • {{ config.init.globals }}
  • {{ page._private_field }}
Parameters 1
expr str

Expression to evaluate (e.g., 'page.metadata.title')

Returns

Any

Evaluated result