jfhovinne

gnrt - static site generator

2020-06-13

Although there are already plenty of static site generators available, it is quite hard to find one which is fast, lightweight, minimalist, easy to use and which simply generates HTML files from structured content, using templates.

Enter gnrt, a static site generator written in Python 3.

Using gnrt

By default, gnrt does not need any configuration.

You write Markdown content files in the content folder, execute gnrt, and it will output HTML files in the public folder.

$ cat content/index.md
The body content here.
$ gnrt
Generated 0 includes
Generated 1 targets
$ cat public/index.html
<p>The body content here.</p>

Let's add some structure by creating a Jinja2 template in the templates folder and pointing to it in the content frontmatter.

$ cat templates/page.j2
<html>
<body>
{{ body }}
</body>
</html>
$ cat content/index.md
---
template: page.j2
---
The body content here.
$ gnrt
Generated 0 includes
Generated 1 targets
$ cat public/index.html
<html>
<body>
<p>The body content here.</p>

</body>
</html>

Since it is not very convenient to point to the same template each time you create new content, let's create a configuration file and add some defaults.

$ cat config.yml
defaults:
  template: page.j2

We can then remove the template key from the frontmatter, and re-generate.

$ gnrt
Generated 0 includes
Generated 1 targets
$ cat public/index.html
<html>
<body>
<p>The body content here.</p>

</body>
</html>

Lists

Menus can be generated using the concept of lists.

$ cat config.yml
defaults:
  template: page.j2

lists:
  menu:
    template: nav.j2
$ cat templates/nav.j2
<ul>
{% for key, value in items %}
  <li><a href="{{ value.link }}">{{ value.title }}</a></li>
{% endfor %}
</ul>

Let's say that we created an About page (content/about.md), and re-generate.

$ gnrt
Generated 1 includes
Generated 2 targets

The menu has been generated as includes/menu.html.

$ cat includes/menu.html
<ul>

  <li><a href="/about.html">about</a></li>

  <li><a href="/index.html">index</a></li>

</ul>

This menu can then be included in the generated output, by adding {{ menu }} somewhere in the page template, and by pointing to it either in the frontmatter, or easier, in the configuration file.

$ cat config.yml
defaults:
  template: page.j2
  menu: includes/menu.html

lists:
  menu:
    template: nav.j2
$ cat templates/page.j2
<html>
<body>
{{ menu }}
{{ body }}
</body>
</html>

Lists can be filtered (e.g. by category of content), sorted (e.g. by publication date) and limited to a certain number of items.

Example:

nav-latest-articles:
    template: nav-article.j2
    filter:
      key: category
      value: article
    sort: published
    reverse: true
    limit: 2

The target key allows to specify where the generated list will be saved; this makes it possible to generate a RSS feed for instance:

rss:
  filter:
    key: category
    value: article
  sort: published
  reverse: true
  template: rss.j2
  target: public/rss.xml

Data access

The whole website dataset and configuration is available in the templates and markdown content through the {{ data }} and {{ config }} keys.

$ cat content/index.md
---
id: home
title: Home
link: /
---

Welcome to the homepage.

Check the [about]({{ data.about.link }}) page or read the [articles]({{ data.articles.link }}) !

Fast and lightweight

gnrt 0.1.1 contains around 100 lines of Python code and is feature complete, at least for my needs - I use it to generate this website.

On a decent computer, it generates 1000 files (containing random text) in less than 1 second.

$ find content/ -type f | wc -l
1000

$ find content/ -type d | wc -l
51

$ time gnrt
Generated 0 includes
Generated 1000 targets

real    0m0,612s
user    0m0,574s
sys 0m0,032s

Open source

gnrt is available on GitHub under the MIT license and can be installed via:

pip install gnrt

There is an example website implementation in the repository.

Last updated: 2022-03-20