BaseVisitor (Python dict)
├── YamlVisitor (YAML output)
└── JinjaVisitor (Jinja template rendering)
├── HtmlVisitor
└── TexVisitorExisting visitors
The visitor hierarchy
Mau provides a hierarchy of visitor classes. At the top sits BaseVisitor, which visits each node and returns a Python dictionary with the node's data. On top of it, two core visitors are built: the YamlVisitor and the JinjaVisitor. The plugin visitors HtmlVisitor and TexVisitor are in turn built on top of JinjaVisitor.
The BaseVisitor
The BaseVisitor is the foundation of all visitors. It implements the visitor pattern: for each node type, a method _visit_TYPE returns a dictionary of key/value pairs that describe the node. These dictionaries are the raw material that other visitors build upon.
The BaseVisitor provides these core methods:
process(node)— the entry point. Calls_preprocess, thenvisit, then_postprocess. Subclasses can override the pre/post hooks.visit(node)— visits a node and returns its data dictionary. If the node has the internal tagdebug(#mau:debug), the visitor emits a debug message through the message handler.visit_data(node)— likevisit, but always returns the raw dictionary, bypassing any template rendering that subclasses might add.visitlist(current_node, nodes_list)— visits each node in a list and returns a list of results.visitdict(current_node, nodes_dict)— visits each node in a dictionary and returns a dictionary of results.
The BaseVisitor can be used directly through the format code python. In this case the output is a Python dictionary.
The YAML visitor
The YAML visitor can be selected through the name core:YamlVisitor. Its purpose is to return the AST of the document, where each node is the dictionary of values that would be given to a template to render the node itself.
YAML was chosen because it can easily represent native Python data types like strings, booleans, dictionaries, and lists, but without the syntactic complexity of JSON. However, the choice of the structured format is secondary, as the purpose is to provide access to the AST.
The YAML visitor is the simplest type of visitor built on top of BaseVisitor. All the class YamlVisitor does is override _postprocess to convert the Python dictionary output into a YAML string.
The YAML visitor is primarily useful for debugging. When you need to understand what data a node produces, rendering with core:YamlVisitor shows you the exact dictionary that a Jinja template would receive. This is an alternative to the #mau:debug tag (described in the chapter about templates), which targets a single node.
The Jinja visitor
The Jinja visitor is also created on top of BaseVisitor and is tasked to render each node through a Jinja template. The template selection mechanism through specificity, the template naming conventions, and the loading order of template sources all come from JinjaVisitor. In the future, Mau might support other templating engines, potentially sharing the template managing logic (naming, discovery) or introducing a completely different system.
The JinjaVisitor is the ancestor of both the HtmlVisitor and the TexVisitor, so the functionalities it provides are available to them as well.
The purpose of JinjaVisitor is to provide a rich base class for any other specialised visitor that wants to use Jinja templates. It provides the internal machinery to discover, choose, and render templates, and it is often sufficient to override only some methods to have a fully functional template-based visitor. The visitor can be used directly when processing Mau documents if the output is pure text without any specific needs for preprocessing or escaping. The extension used by this visitor for its templates is .j2.
Overriding visit and visitlist
The JinjaVisitor overrides visit and visitlist from BaseVisitor.
The method visit first calls BaseVisitor.visit to obtain the data dictionary, then finds a matching template through the specificity system, and renders it. The return value is therefore a string instead of a dictionary.
The method visitlist visits all nodes in the list and joins the results into a single string. The join character depends on the type of the parent node and is controlled by the class attribute join_with. The defaults are:
join_with = {
"document": "\n",
"raw-content": "\n",
"source": "\n",
"paragraph": " ",
}
join_with_default = ""This means that paragraph lines are joined with a space, source lines and raw lines with a newline, and all other node types are concatenated without any separator. Subclasses can override join_with to change this behaviour.
Template loading
During initialisation, JinjaVisitor collects templates from four sources, always in this order (later sources override earlier ones):
- Templates from the
default_templatesclass attribute. This is anEnvironmentobject that subclasses populate with their built-in templates. - Templates from template providers listed in
mau.visitor.templates.providers. - Templates from the filesystem paths listed in
mau.visitor.templates.paths. - Templates from the dictionary
mau.visitor.templates.custom.
Each template file must have the extension matching the visitor (.html for HTML, .tex for TeX, .j2 for Jinja). The visitor parses the template filename to determine the node type, subtype, custom fields, and other specificity components.
Template preprocessing
The class attribute templates_preprocess is an optional callable that transforms each template's source text before it is compiled by Jinja. The HTML visitor, for example, uses this to strip indentation and blank lines from HTML templates, so that templates can be written in a readable multi-line format without introducing unwanted whitespace in the output.
A subclass can set this attribute to any function with the signature (str) -> str.
Jinja environment options
The class attribute jinja_environment_options is a dictionary of keyword arguments passed directly to jinja2.Environment(). This allows subclasses to configure Jinja features like autoescape, undefined behaviour, or custom extensions.
Template context
Every Jinja template receives the data dictionary produced by BaseVisitor as keyword arguments, plus a variable called config that contains the entire Mau environment as a nested dictionary. See the chapter about configuration for more details on this variable.
The HTML visitor
The HTML visitor (mau_html_visitor:HtmlVisitor) renders Mau documents into HTML. It extends JinjaVisitor, using .html as its template extension and html as its format code.
Installation
The HTML visitor is distributed as a separate package.
pip install mau-html-visitorOnce installed, it registers itself through a Python entry point and becomes available to the Mau command line tool.
Text escaping
The HTML visitor automatically escapes HTML special characters (<, >, &, ") in text and verbatim nodes. This is done by overriding _visit_text and _visit_verbatim and calling html.escape() on the node's value. This means that writing <tag> in a Mau paragraph will produce <tag> in the output. If you need to insert raw HTML, use the macro raw or a raw block.
Template preprocessing
The HTML visitor uses a template preprocessor called filter_html that strips leading whitespace from each line of a template and collapses it into a single line of text before it is compiled by Jinja. This allows templates to be written in a readable, indented format without introducing unwanted spaces or newlines in the output. For example, the template
<div class="code">
{% if labels.title %}
<div class="title">{{ labels.title }}</div>
{% endif %}
<div class="content">
<pre>{{ content }}</pre>
</div>
</div>is preprocessed into a single line <div class="code">{% if labels.title %}<div class="title">{{ labels.title }}</div>... before Jinja compiles it, so no indentation leaks into the rendered output.
Pretty printing
The HTML visitor supports a postprocessing step that reformats the output HTML with proper indentation, controlled by the configuration variable mau.visitor.html.pretty (defaults to false). When enabled, the visitor uses the Python library Beautiful Soup to prettify the output.
visitor:
html:
pretty: trueThis can be useful for debugging, but it can also introduce unwanted whitespace inside <code> and <pre> tags. Keep it disabled for production output.
Source code highlighting
The HTML visitor provides a custom implementation of _visit_source that integrates with Pygments for syntax highlighting. The process works as follows:
- The visitor deep-copies the source node to avoid modifying the original. This is important because source nodes might be visited multiple times (e.g. when a source block appears inside a footnote).
- Markers are temporarily removed from the source lines, since Pygments would otherwise try to highlight them as code.
- The source lines are concatenated and passed to Pygments for highlighting.
- After highlighting, the result is split back into lines, and the markers are restored.
The highlighter produces one CSS class per highlight style. For example, a line marked with :@add: will be wrapped in <span class="hll-add">, and :@remove: produces <span class="hll-remove">. The standard Pygments class for default highlighting is hll, so Mau's convention of hll-STYLE integrates naturally with Pygments-based stylesheets.
Configuration
The highlighter can be configured through the environment:
visitor:
html:
highlighter: pygments
pygments:
nowrap: true
cssclass: highlightThe highlighter key selects the highlighting engine (currently only pygments is supported). It can be overridden per block with the argument highlighter.
The pygments key is a dictionary passed directly to Pygments' HtmlFormatter. The default is nowrap: true, which tells Pygments not to wrap the output in <div> and <pre> tags, as those are provided by the source.html template.
Default templates
The HTML visitor provides 38 default templates. The following sections describe the most notable ones. Templates not mentioned here either pass through their content unchanged (e.g. text.html outputs {{ value }}) or are trivially simple (e.g. horizontal-rule.html outputs <hr>).
Document structure
The document.html template wraps the entire output in a minimal <html><head></head><body>...</body></html> structure. In practice, you will almost certainly override this template to include your own CSS, JavaScript, and metadata. When using Mau through Pelican or another static site generator, the document template is typically not used at all as Pelican has its own templates to wrap the content of a post or a page.
Headers
The header.html template generates an <hN> tag with the header's internal_id as the anchor:
<h{{ lev }} id="{{ internal_id }}">{{ content }}</h{{ lev }}>Levels above 6 are capped to <h6>. This documentation overrides the default to add anchor links and styling.
Paragraphs
A paragraph is rendered as <p>{{ content }}</p>. Each paragraph line is rendered through paragraph-line.html, which outputs {{ content }} — the rendered inline elements. The JinjaVisitor joins paragraph lines with a space.
Blocks
The block.html template wraps content in a <div>. If the block has a subtype, it becomes the CSS class. If the block has a label with role title, it is rendered in a <div class="title"> element.
<div class="quote">
<div class="title">Chapter 1</div>
<div class="content">...</div>
</div>The default template is intentionally generic. The typical pattern for styling specific block types is to create subtype-specific templates like block.quote.html (for blocks with [*quote]) or tag-specific templates like block.tg_warning.html.
Lists
The list.html template generates either <ol> or <ul> depending on the ordered flag. Ordered lists support the start attribute. Each item is rendered as <li>{{ content }}</li>.
Source blocks
The source.html template wraps the highlighted code in a <div class="code"> with an optional title (from the title label) and a <pre> element.
Each source line is rendered through source-line.html, which outputs the highlighted code followed by an optional marker. Markers are rendered through source-marker.html, which simply outputs {{ value }}.
Macros
Each macro type has its own template:
macro-link.html—<a href="{{ target }}">{{ content }}</a>macro-image.html—<img>wrapped in<span class="image">macro-class.html—<span class="{{ classes | join(' ') }}">{{ content }}</span>macro-header.html— anchor link to the header'sinternal_idmacro-footnote.html— superscript link with bidirectional anchoringmacro-unicode.html—&#x{{ value }};macro-raw.html—{{ value }}(no escaping)
Footnotes
The default footnote system uses bidirectional links. The macro [footnote](name) renders a superscript number linking to the footnote body (#f-ID), while the footnote body contains a backlink to the mention (#m-ID).
The footnotes.html template wraps all footnotes in <div id="_footnotes"><ul>...</ul></div>.
Includes
include-image.html— renders an<img>inside a<div class="imageblock">with an optional title caption.include-mau.html— simply outputs{{ content }}, the rendered included document.include-raw.html— outputs{{ content }}verbatim.include.html— the generic include template, used for custom content types.
Table of contents
The toc.html template creates a <div> with a nested <ul>. Each entry is a <li> with an anchor link to the header and optionally nested sub-entries.
Customising templates
As described in the chapter about templates, you can override any default template by placing a file with the same name in one of your template paths. The HTML visitor's templates are intentionally minimal to make customisation straightforward.
This documentation itself provides good examples of custom templates. The source block template adds a title bar, the header template adds anchor links, the quote block template uses Bootstrap's <blockquote> component, and the block group templates create tabbed panels showing Mau source alongside rendered output.
The TeX visitor
The TeX visitor (mau_tex_visitor:TexVisitor) renders Mau documents into LaTeX source code. It extends JinjaVisitor, using .tex as its template extension and tex as its format code.
Installation
The TeX visitor is distributed as a separate package.
pip install mau-tex-visitorText escaping
LaTeX has many special characters that must be escaped. The TeX visitor overrides _visit_text and _visit_verbatim to escape the following characters:
& → \&
% → \%
$ → \$
# → \#
_ → \_
{ → \{
} → \}
~ → \textasciitilde{}
^ → \^{}
\ → \textbackslash{}
< → \textless{}
> → \textgreater{}This escaping is applied to all text and verbatim nodes before they reach the template, so templates do not need to worry about special characters in the document content.
Headers
The TeX visitor overrides _visit_header to map Mau header levels to LaTeX sectioning commands:
Level 1 → \chapter
Level 2 → \section
Level 3 → \subsection
Level 4 → \subsubsection
Level 5 → \paragraph
Level 6 → \subparagraphLevels above 6 are capped at \subparagraph. The header.tex template uses the variable command provided by the visitor:
\{{ command }}{ {{-content-}} }Source code highlighting
The TeX visitor overrides _visit_source to collect the list of highlighted line numbers. The result dictionary contains a highlights key with a list of line numbers that have a highlight style. The actual rendering of highlights is left to the template.
The default source.tex template simply outputs the content without any special highlight formatting. Real-world usage typically requires a custom template that integrates with a TeX code listing package.
Default templates
The TeX visitor provides 34 default templates. Many of them are intentionally minimal because LaTeX output depends heavily on the document class, the packages loaded in the preamble, and the specific conventions of each project. The visitor aims to be generic and does not enforce any particular package.
The following sections describe the most notable default templates.
Styles
The four text styles map to standard LaTeX commands:
style__star.tex—\textbf{content}(bold)style__underscore.tex—\textit{content}(italic)style__caret.tex—\textsuperscript{content}style__tilde.tex—\textsubscript{content}
Verbatim text is rendered with \texttt{content}.
Paragraphs
The paragraph.tex template appends \n\n after the content, which is the standard LaTeX way to separate paragraphs.
Lists
The list.tex template wraps content in \begin{enumerate}...\end{enumerate} for ordered lists and \begin{itemize}...\end{itemize} for unordered lists. Each item is rendered with \item.
Macros
macro-link.tex—\href{target}{content}(requires thehyperrefpackage).macro-footnote.tex—\footnote{content}. Note that LaTeX handles footnote placement natively, so thefootnotes.texandfootnotes-item.textemplates are empty.macro-image.tex—\includegraphics{uri}(requiresgraphicx).macro-class.tex— outputs{{ content }}since LaTeX has no direct equivalent of CSS classes.
Includes
The include-image.tex template wraps the image in a \begin{figure}[H]...\end{figure} environment with \centering and an optional \caption from the title label. The [H] placement requires the float package.
Empty templates
Several templates are intentionally empty:
footnotes.texandfootnotes-item.tex— because LaTeX handles footnotes natively through\footnote{}.toc.texandtoc-item.tex— because LaTeX generates tables of contents with\tableofcontents.macro.tex— the generic macro fallback.blockgroup-item.tex— a placeholder.
Customising templates for real-world use
The default templates are a starting point. In practice, producing a polished PDF requires custom templates that integrate with your chosen LaTeX packages. This section shows examples from a real book project to illustrate common patterns.
Document structure
A typical book project wraps the entire document in a custom block.document.tex template that contains the LaTeX preamble:
\documentclass[a4paper]{memoir}
\usepackage[utf8]{inputenc}
\usepackage{hyperref}
\usepackage{minted}
\usepackage{tcolorbox}
% ... more packages and definitions ...
\begin{document}
{{ content }}
\end{document}The Mau source then uses a block with [*document] subtype to wrap the entire content. Other subtypes like [*frontmatter], [*mainmatter], and [*backmatter] can be used to create templates that emit \frontmatter, \mainmatter, and \backmatter.
Headers with options
A custom header.tex can support named arguments like nobreak, unnumbered, or toc for finer control:
{% if kwargs.nobreak %}\needspace{5\baselineskip}{% endif %}
{% if kwargs.unnumbered %}\{{ command }}*{ {{-content-}} }{% else %}\{{ command }}{ {{-content-}} }{% endif %}
\label{ {{-internal_id-}} }This allows you to write [nobreak=true, unnumbered=true] before a header in Mau to prevent page breaks and suppress numbering.
Source code with minted
The default source.tex template does not perform syntax highlighting. A custom template can integrate with the minted package:
{% if labels.title %}\begin{code}[title={{ labels.title }}]{% endif %}
\begin{minted}[
highlightlines={ {{-highlights | join(',')-}} },
linenos
]{ {{-language-}} }
{{ content }}
\end{minted}
{% if labels.title %}\end{code}{% endif %}The highlights variable provided by the TeX visitor contains the list of highlighted line numbers, which maps directly to minted's highlightlines option.
Verbatim text with colour
A custom verbatim.tex can add visual distinction to inline code:
{\color{codetext}\footnotesize\texttt{ {{-value-}} }}Custom block types
Block subtypes and named arguments make it possible to create specialised environments. For example, an aside block:
\begin{aside}[title={{ labels.title }}]
{{ content }}
\end{aside}requires a corresponding tcolorbox environment definition in the preamble. The same pattern works for warning, tip, or any other admonition-like block.
Custom macros
Custom macro templates can generate links to specific documentation sites. For example, a macro/name__docs-rust.tex template targets the macro [docs-rust](path, "text"):
{% if args.1 %}\href{https://doc.rust-lang.org/{{ args.0 }}}{ {{-args.1-}} }{% else %}\href{https://doc.rust-lang.org/{{ args.0 }}}{Rust documentation}{% endif %}This demonstrates how custom template fields (in this case name) enable project-specific macros without any changes to Mau's core.