Topic: tech prev

tech > Plainsite

A commandline templating tool for generating text

Plainsite

This project is an attempt at a generic templating engine. It is usually invoked from the command line and can be used for generating static websites (such as this one) and various configuration files.

Workflow

plainsite accepts two or three files as inputs. The template file, written in a new domain specific language (DSL), describes the format of the output. The data file, written in JSON for interoperability, provides the input data, either manually written or the output from another tool.

+-------------------+
|  template (DSL)   |-------+
+-------------------+       |       +-------------------+
                            +-------+   output          |
+-------------------+       |       +-------------------+
|  data (JSON)      |-------+
+-------------------+

In some applications, a large fragment of the output will have been constructed by another tool and stored in a separate plain text file. For instance, the page content in a website. For these applications, an additonal ‘content’ file can be supplied. The content of the file becomes accessible through the ‘content’ variable.

File Processing

plainsite follows control structures in the template to produce the output. There are several types of token; variables, loops, conditionals, and literals. Literals are simply anything that isn’t a control structure; these are printed verbatim to the output.

Variables are evaluated to be replaced with the content of the JSON data at the current scope. What is meant by ‘scope’ will become clear in a moment with the introductions of loops and conditionals. A variable looks like this;

Title: $=title$

In this example, the text “Title: “ is a literal. This text, followed by the content of the variable named ‘title’ at the current scope, is printed to the output.

Loops and conditionals in imperative programming have counterparts in plainsite. Loops can be used to generate, for example, lists of data in the output. Loops can be nested. A loop looks like this:

$entries$
    ...inside the loop...
$-$

Here, ‘entries’ should be a list of objects. The members of each object in ‘entries’ becomes available inside the loop.

{
    "entries": [
        ...
    ]
}

Plainsite supports scoping. When evaluating the inside of the loop, plainsite looks first at object inside the JSON array it is iterating over. If a named variable is not found, plainsite goes up to the object containing the array. Then, if the variable is still not found, the evaluation continues upwards to the top level of the document. plainsite will never look into another object in the loop array, or enter another, lower scope than any being evaluated.

Conditionals are supported. The content inside the conditional is printed if the variable evaluates to true. For instance, the conditional:

$?published$
...
$-$

Will evaluate to true, and the tokens inside be printed, if evaluated against the following object:

{
    "title": "Museum Trip",
    "published": true
}

In fact, ‘true’ is not the only value to be considered true. Any value which is not ‘false’ or ‘null’ is also considered true. If no matching variable is found, this condition is considered false.

Finally, an ‘else’ can be added, to print tokens if the conditional evaluates to false. The ‘else’ section is optional, but it is the only way to negate the condition.

$?published$
...
$:$
coming soon!
$-$

This is the entire functionality of plainsite. It’s intentionally basic. To acheive any more, it must be combined with other tools. Fortunately, other tools, not written by me, already exist. Read ‘Combining with Other Tools’ below.

Invocation

Functionality is provided by a single binary. It has three arguments, of which –json and –template are required. Any one filename may be replaced by ‘-’ to read from stdin instead.

plainsite --content content.html --json data.json --template template.html

Example Files

Here is an example for constructing the blog index of a static website.

data.json

{
    "title": "My Blog",
    "entries": [
            {
                "title": "Summer Holiday",
                "date": "2021-06-30",
                "published": false
            },
            {
                "title": "Museum Trip",
                "date": "2021-04-10",
                "tags": ["museum", "photographs"],
                "published": true
            }
    ]
}

template.html

<html>
    <head>
        <title>$=title$</title>
    </head>
    <body>
        $entries$
        $?published$
        <article>
            <h2>$=title$</h2>
            $tags$<p>Tags: <ul>$tags$<li>$=$</li>$-$</ul></p>$-$
        </article>
        $:$
        <article>New blog post coming soon!</article>
        $-$
        $-$
    </body>
</html>

Note the use of ‘$=$’ to print the string elements of the ‘tags’ lists.

Combining with Other Tools

As presented so far, plainsite is just barely useful. There are least two ways to increase its functionality: generate (or at least process the existing) JSON data file, and use a makefile to combine multiple templates and data files together to build a static website.

jq is an excellent tool for manipulating JSON data. It comes with its own programming language dedicated to transforming JSON into new JSON. Consider the simple blog example above; what happens when it comes to generating an individual article page? It might be useful to extract a single entry from the JSON data file and export that to a new file. jq will handle this task quite easily. Additionally, it can be used to insert links to the previous and next blog entries, though that’s beyond the scope of this document.

Commandline Argument Reference