Introduction 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

Create the project directory

Create a fresh directory and the files described below

mkdir mau-templates && cd mau-templates
mkdir templates

The directory structure will be

mau-templates/
  templates/
  config.yaml
  test.mau

The 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

config.yaml
visitor:
  html:
    pretty: true
  templates:
    paths:
      - templates

The document file

Create the file test.mau with the following content

test.mau
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.html

Open 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.

test.html
<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

templates/document.html
<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.html

Now the output file is

test.html
<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

test.mau
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

test.html
<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

templates/header.html
<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

test.html
<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.

templates/header.html
<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

test.mau
...

== A quotation

.url URL
.cite SOURCE
[*quote]
----
TEXT
----

where URL, SOURCE, and TEXT are replaced by appropriate values. For example:

test.mau
...

== 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.

templates/block.quote.html
<blockquote cite="{{ labels.url }}">
  {{ content }}
  <footer>
    <cite>{{ labels.cite }}</cite>
  </footer>
</blockquote>

Run Mau again, and this time the output will be

test.html
<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.

templates/block.quote.html
<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.

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.

test.mau
...

== 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.

config.yaml
visitor:
  html:
    pretty: true
    pretty: false
  templates:
    paths:
      - custom

The output of Mau now is

test.html
...

<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

templates/macro.namekbd.html
<kbd>{{ args.0 }}</kbd>

and change the Mau source code to

test.mau
...

== 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:

  1. 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.
  2. 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

templates/macro.namekbd.html
<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

templates/macro.namekbd.html
<kbd CLASS>CONTENT</kbd>
  • CONTENT is now |{{ args.0 }}|, so a call like [kbd]("Ctrl-C") results simply in |Ctrl-C|.
  • CLASS is implemented with an if statement in Jinja, whose form is {% if CONDITION %}TEXT{% endif %}.
    • The condition here is kwargs.class, that is: the macro has an argument called class.
    • The text is class="{{ kwargs.class }}".

Change the Mau source code to

test.mau
...

== 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

test.html
...

<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.