mkdir mau-templates && cd mau-templates
mkdir templatesIntroduction to templates
What are templates?
So far, Mau provides features similar to Markdown, Asciidoc, and other markup languages. The real power of Mau, however, relies in its use of templates to render the output of the parsed syntax.
Everything you saw in the previous chapters is rendered into HTML by the default Jinja templates provided by the visitor or by some custom templates written for this documentation.
This is a hands-on chapter, where we will go through some simple customisations of the Mau output. There won't be many in-depth explanations of what happens; the next chapter will reveal all that happens behind the scenes in detail.
Prepare the environment
This setup mirrors the one explained in Chapter 03, so the steps won't be discussed in detail here.
Create the project directory
Create a fresh directory and the files described below
The directory structure will be
mau-templates/
templates/
config.yaml
test.mauThe directory templates is empty for now. Please note that when you set up the environment mau-playground the directory contained a template, but it doesn't in this case.
The configuration file
Create the file config.yaml with the following content
visitor:
html:
pretty: true
templates:
paths:
- templatesThe document file
Create the file test.mau with the following content
This is an example written in Mau.Compile and view
To render any source file you create in this directory, run
mau -c config.yaml -i test.mau -t mau_html_visitor:HtmlVisitor -o test.htmlOpen test.html with your browser and leave it open, we will reload it often. You will notice that the rendering is the classic bare-bones HTML without any style.
Also, make sure you open the output file test.html in your editor or in the browser developer tools. It's useful to see what Mau does to the source code of the document.
<html>
<head>
</head>
<body>
<p>
This is an example written in Mau.
</p>
</body>
</html>Customise the document
The output file looks very minimal at the moment. Let's convert it into a more beautiful HTML document. To do this we can do what we did in the playground template, that is to add the beautiful CSS styles provided by Water.css.
The instructions of the CSS style tell us to add the following line to the '<head>` tag
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">To do that through mau, we can create the following file
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
</head>
<body>{{ content }}</body>
</html>Please note that this is a Jinja template. The syntax {{ content }} is going to inject the content of the document, while everything else is going to be copied verbatim.
Run Mau again with the same command line as before
mau -c config.yaml -i test.mau -t mau_html_visitor:HtmlVisitor -o test.htmlNow the output file is
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css" rel="stylesheet"/>
</head>
<body>
<p>
This is an example written in Mau.
</p>
</body>
</html>Reload the file in your browser and enjoy a much better rendering.
Add interesting elements
Let's add some elements to the document to make it more interesting. Change the content of the file to
This is an example written in Mau.
== Styled text
Stars identify *strong* text.
Underscores for _emphasized_ text.
Carets for ^superscript^ and tildes for ~subscript~.
Backticks are used for `verbatim` text.
== Lists
Things we need to do!
# Add interesting Mau syntax.
# Learn how to create templates.
# Enjoy the results.
== Code
This paragraph contains some code taken from the Mau source code. This is definitely meta.
[@source]
----
def _process_eof(self) -> list[Token] | None:
# If we are not at the end of
# the buffer just return.
if not self.text_buffer.eof:
return None
# Build the EOF token.
tokens = [self._create_token_and_skip(TokenType.EOF)]
return tokens
----Customise headers
We might want to add to each header a link to itself, so that we can easily create permanent links. To do this, we want to have the following output
<h2 id="SOMEID">TITLE <a href="#SOMEID" title="Permanent link">¶</a><div></div></h2>where TITLE is the text of the header itself and SOMEID is a unique ID. For example, we might want to have
<h2 id="styled-text">Styled text <a href="#styled-text" title="Permanent link">¶</a><div></div></h2>This will add a small character ¶ next to the header that contains a permanent link.
To achieve this, we can create the following template
<h{{ level }} id="{{ internal_id }}">
{{ content }} <a class="headerlink" href="#{{ internal_id }}" title="Permanent link">¶</a>
</h{{ level }}>Run Mau again, and the output will be
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/gh/richleland/pygments-css@master/monokai.css" rel="stylesheet"/>
</head>
<body>
<p>
This is an example written in Mau.
</p>
<h2 id="styled-text-d87d">
Styled text
<a class="headerlink" href="#styled-text-d87d" title="Permanent link">
¶
</a>
</h2>
...
</body>
</html>The Jinja variables {{ level }}, {{ internal_id }}, and {{ content }} are provided by Mau for each header. As you can see, the ID styled-text-d87d is calculated using the header content (and a hash to avoid collisions), while level contains a digit that reflects the header nesting level (== corresponds to 2, === to 3, and so on). Last, content is the rendered content of the header.
Advanced example
Jinja is a very powerful templating engine, and it contains a rich set of filters and commands that you can use freely in your Mau templates.
Let's add some commands to show the permanent link only for headers of level 1 and 2.
<h{{ level }} id="{{ internal_id }}">
{{ content }}{% if level < 3 %} <a class="headerlink" href="#{{ internal_id }}" title="Permanent link">¶</a>{% endif %}
</h{{ level }}>Now, the anchor with the permanent link will be shows only when the level of the header is less than 3 (that is = and ==)
Add a custom block
The documentation of Water.css contains a nice example of typography. In particular, it shows how a quotation is rendered. Behind the scenes, the HTML code is
<blockquote cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote">
"The HTML blockquote Element (or HTML Block Quotation Element) indicates that the enclosed
text is an extended quotation. Usually, this is rendered visually by [...] cite element."
<footer><cite>MDN, "The Block Quotation element"</cite></footer>
</blockquote>which can be reduced to
<blockquote cite="URL">TEXT<footer><cite>SOURCE</cite></footer></blockquote>We can achieve the same effect in Mau starting from the following syntax
...
== A quotation
.url URL
.cite SOURCE
[*quote]
----
TEXT
----where URL, SOURCE, and TEXT are replaced by appropriate values. For example:
...
== A quotation
.url https://www.goodreads.com/work/quotes/3634639-dune
.cite Frank Herbert, Dune
[*quote]
----
What do you despise? By this are you truly known.
----Please note that .url and .cite are labels, but their names are arbitrary and not part of Mau's syntax. The same is true for the subtype *quote.
If we add the example above to the file test.mau the result is pretty disappointing
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/gh/richleland/pygments-css@master/monokai.css" rel="stylesheet"/>
</head>
<body>
...
<div class="quote">
<div class="content">
<p>
What do you despise? By this are you truly known.
</p>
</div>
</div>
</body>
</html>This is the default way the Mau HTML visitor renders a block with a given subtype. Mau doesn't know anything specific about the subtype quote, so we need to create a template for it.
<blockquote cite="{{ labels.url }}">
{{ content }}
<footer>
<cite>{{ labels.cite }}</cite>
</footer>
</blockquote>Run Mau again, and this time the output will be
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/gh/richleland/pygments-css@master/monokai.css" rel="stylesheet"/>
</head>
<body>
...
<blockquote cite="https://www.goodreads.com/work/quotes/3634639-dune">
<p>
What do you despise? By this are you truly known.
</p>
<footer>
<cite>
Frank Herbert, Dune
</cite>
</footer>
</blockquote>
</body>
</html>Advanced example
As labels are optional values that you can add to Mau elements, both the label url and the label cite might not be present in the block. Let's make sure the Jinja template deal with that properly.
<blockquote{% if labels.url %} cite="{{ labels.url }}"{% endif %}>
{{ content }}
{% if labels.cite %}
<footer>
<cite>{{ labels.cite }}</cite>
</footer>
{% endif %}
</blockquote>The conditional if statements added around the parameter cite and the whole element <footer> ensure that the output of the template is correct even when labels are missing.
HTML is extremely forgiving when it comes to spaces, missing values, and empty tags. Other output formats might not be that flexible, so make sure you design your templates properly.
A custom macro
The CSS template of Water.css contains a beautiful rendering for the HTML element <kbd> that "represents a span of inline text denoting textual user input from a keyboard" (see the docs). Unfortunately, Mau provides no native way to include such a tag.
We might use the macro raw to inject HTML code directly, but that works only if the output is always HTML.
...
== Keyboard input
You can copy and paste code with [raw]("<kbd>Ctrl-C</kbd>") and [raw]("<kbd>Ctrl-V</kbd>").To visualise this properly, we need to deactivate the pretty output option for the Mau HTML visitor. The library adds spaces to the content of <kbd> that interfere with the correct rendering.
visitor:
html:
pretty: true
pretty: false
templates:
paths:
- customThe output of Mau now is
...
<p>You can copy and paste code with <kbd>Ctrl-C</kbd> and <kbd>Ctrl-V</kbd>.</p>
...As mentioned before, this is not a good solution. It works only for HTML, and it doesn't allow us to style or control the content of the tag.
A much better way to implement it is to create a custom macro. Create the following file
<kbd>{{ args.0 }}</kbd>and change the Mau source code to
...
== Keyboard input
You can copy and paste code with [kbd]("Ctrl-C") and [kbd]("Ctrl-V").This will achieve the same output as before, but with two advantages:
- The output is not hard coded. If you render this in TeX or another output format you will provide a different template for the same input.
- The behaviour can be fully controlled.
To showcase the second point, let's add some advanced options to our new macro.
Change the template to
<kbd {% if kwargs.class %}class="{{ kwargs.class }}"{% endif %}>|{{ args.0 }}|</kbd>If you are not familiar with Jinja templates this might look intimidating. Let's break this into smaller pieces
The core idea of the template is
<kbd CLASS>CONTENT</kbd>CONTENTis now|{{ args.0 }}|, so a call like[kbd]("Ctrl-C")results simply in|Ctrl-C|.CLASSis implemented with anifstatement in Jinja, whose form is{% if CONDITION %}TEXT{% endif %}.- The condition here is
kwargs.class, that is: the macro has an argument calledclass. - The text is
class="{{ kwargs.class }}".
Change the Mau source code to
...
== Keyboard input
You can copy and paste code with [kbd]("Ctrl-C", class="important") and [kbd]("Ctrl-V", class="dangerous").Run Mau again and the output will be
...
<p>You can copy and paste code with <kbd class="important">|Ctrl-C|</kbd> and <kbd class="dangerous">|Ctrl-V|</kbd>.</p></body></html>
...The two classes important and dangerous are made up and are not part of the Water.css style, but it would be a breeze to add their definitions to the CSS file and customise the two outputs of the custom macro kbd.