Rendering Pipeline
Accent CMS processes markdown through an event-based pipeline rather than a simple parse-and-render approach. The pulldown-cmark parser produces a stream of events (headings, paragraphs, emphasis, links, code blocks, etc.) that flow through a chain of event processors before being rendered to HTML. Each processor can inspect, transform, add, or remove events, and accumulate metadata into a shared result.
How It Works
The pipeline follows three stages:
- Parse: The markdown source is parsed into a stream of events using pulldown-cmark with all extensions enabled by default (tables, footnotes, strikethrough, task lists, heading attributes, wikilinks, smart punctuation, math, definition lists, GFM admonitions, superscript, subscript, and metadata blocks). Each extension can be toggled individually via the
markdown:section inconfig.yaml. - Process: The event stream passes through each configured processor in order. The output of one processor becomes the input of the next. Processors share a
RenderMetadatastruct where they accumulate extracted data. - Render: The final event stream is rendered to HTML.
When no processors are configured, the pipeline produces identical output to direct rendering with no measurable overhead.
Built-in Processors
Accent CMS includes two processors that run on every page by default:
Table of Contents Processor
Walks the event stream to find headings, extracts their text content (including inline code and formatted spans), and generates slug-based anchor IDs. Each heading in the rendered HTML receives an id attribute and a clickable anchor link for navigation.
What it does:
- Extracts heading text from
TextandCodeevents within headings - Generates URL-safe slugs (e.g., “Getting Started” becomes
getting-started) - Disambiguates duplicate headings with numeric suffixes (
introduction,introduction-1,introduction-2) - Adds
idattributes to heading HTML elements - Injects an empty
<a class="heading-anchor">element on each heading; the visible marker (chain icon,#, pilcrow, or nothing) is supplied by the active theme’s CSS based on itsheading_anchor_stylesetting - Records entries in
page.tocfor template-driven navigation
Pages that should look like marketing copy rather than docs can suppress the
anchor element entirely by setting anchors: false in frontmatter. The
heading id and page.toc entry are still produced, so external links to
fragments and template-driven sidebars keep working – only the visible
per-heading affordance disappears.
--- title: About anchors: false # default is true ---
The four shipped anchor presets (selected per-theme via heading_anchor_style
in theme.yaml):
copy(default) – chain icon in the left gutter on hover; clicking copies the canonical URL to the clipboard and shows a transient toast.gutter– same gutter chain icon, but click navigates to the fragment as a normal link (no JS dependency).hash– legacy appended#revealed on hover.pilcrow– appended pilcrow revealed on hover.
Syntax Highlight Processor
Intercepts fenced code blocks in the event stream and replaces them with syntax-highlighted HTML using the syntect library. Highlighting happens server-side, so no client-side JavaScript is needed.
Behavior:
- Fenced code blocks with a recognized language get inline-styled
<span>elements - Code blocks without a language pass through as plain
<pre><code> - Unrecognized languages fall back to plain rendering
- The highlight theme is configurable in
config.yaml(see the Basic Formatting code blocks section)
WikiLink Processor
Resolves wiki-style links ([[Page Name]]) to actual page URLs using the content index. This lets you link between pages by title instead of remembering URL paths.
Page links:
[[About]] <!-- resolves to /about by title match --> [[About|Read more about us]] <!-- custom display text --> [[About#team]] <!-- link with anchor fragment --> [[/contact]] <!-- absolute path, passed through -->
Image links:
![[photo.jpg]] <!-- page-local image --> ![[photo.jpg|A nice photo]] <!-- image with alt text -->
Resolution order:
- Case-insensitive title match against the content index
- Slug match (e.g., “Getting Started” matches
/getting-started) - Direct URL match (e.g.,
blog/my-postmatches/blog/my-post) - Unresolved: renders with
class="wikilink-broken"for theme styling
When multiple pages share the same title, sibling pages (same parent directory) are preferred, then shallower pages, then alphabetical order.
Transclusion syntax (![[Page Name]] without an image extension) renders as a styled link with class="wikilink-embed". Full transclusion support is planned for a future release.
Pipeline Metadata
The pipeline extracts metadata during rendering and makes it available to templates. The following fields are populated by processors:
| Template Variable | Type | Description |
|---|---|---|
page.toc | list | Heading entries with level, text, and id |
page.wikilinks | list | Wikilink references with raw_name, resolved_url, has_custom_text, is_image |
page.word_count | int | Word count of the markdown content |
page.reading_time | int | Estimated reading time in minutes (200 wpm) |
The page.toc entries correspond to the id attributes on headings in the rendered HTML, enabling in-page anchor links. See the Templating Guide for template examples using page.toc.
Heading Anchors and Deep Linking
Every heading (H1-H6) automatically becomes a navigation anchor point. The TOC processor adds id attributes and a clickable # link that appears when you hover over any heading. This lets readers copy a permalink to a specific section, and the “On this page” sidebar uses these anchors for in-page navigation.
You can also link directly to any section from other pages using fragment URLs:
See the [installation instructions](/docs/getting-started#installation)
The anchor ID is the slugified heading text: lowercase, non-alphanumeric characters replaced with hyphens, consecutive hyphens collapsed. For example:
| Heading | Anchor ID |
|---|---|
## Getting Started | #getting-started |
## Using \render()` function` | #using-render-function |
## Rust & MiniJinja! | #rust-minijinja |
Duplicate headings get numeric suffixes to remain unique:
| Heading | Anchor ID |
|---|---|
First ## Introduction | #introduction |
Second ## Introduction | #introduction-1 |
Third ## Introduction | #introduction-2 |
Table of Contents in Templates
The page.toc list is available in every template. Each entry has level (1-6), text (plain heading text), and id (the anchor slug). Here is a minimal example:
{% if page.toc %}
<nav class="toc">
<h3>On this page</h3>
<ul>
{% for entry in page.toc %}
<li class="toc-level-{{ entry.level }}">
<a href="#{{ entry.id }}">{{ entry.text }}</a>
</li>
{% endfor %}
</ul>
</nav>
{% endif %}
Use entry.level to apply indentation via CSS classes (e.g., toc-level-2, toc-level-3) for a nested table of contents.