Andrea Corsini https://andreacorsini.xyz

A simple static website generator (sswg)

Why bother creating another static website generator? There are so many awesome projects out in the open source community, such as Jekyll, Hugo, just to name few of them. Well, I wanted to have something dead simple, but also flexible enough to be easily customized. So I wrote my own simple static website generator (sswg) using SHell scripting.

This is possible because I don't need many features yet. In the future, I could end up in switching to a proper generator. Anyway, for the time being I can just use mine and share it here. Hopefully, it could be useful for other folks that want to practice shell scripting, and/or want to implement their website generator.

Here, you will find the original version and its rationale. Most likely, the generator will change to cope with my website needs. You can find the current version in my git repository.

Features

At the time of writing, I am a simple person. I just want to:

Anything more than that is not necessary at the moment. For example, I don't need Markdown or other languages to write pages, plain HTML is good enough. In the future, it would be nice to have some extra features, like automatic generation of RSS and Table of Content (TOC).

Folder structure

My sswg is just a tiny shell script to include in the website root. In this way, it can be versioned along all the other website code. The script expect to find a folder tree similar to

├── _assets
│    ├── style.css
│    ├── favicon.ico
│    ├── images
│    │     └── picture.png
│    └── rss.xml
├── _footer.t.html
├── _header.t.html
├── _pages
│     ├── email.html
│     ├── index.html
│     ├── my-notes.html
│     ├── notes
│     │     └── a-simple-static-website-generator.html
│     └── privacy-policy.html
└── sswg.sh

Only the elements in bold are mandatory. The other files and folders are there to showcase. Once the script is invoked with ./sswg.sh, sswg will regenerate the website to the output folder, named _static. Any content of the direcorty _asset will be copied to the root of _static. The content of template files _header.t.html and _footer.t.html are preposed and appended to each HTML page, respectively. The sswg will copy to the output folder all the HTML files contained in _page, as well as sub-directories and images.

The output folder _static for this example will result in

├── style.css
├── favicon.ico
├── images
│     └── picture.png
├── rss.xml
├── email.html
├── index.html
├── my-notes.html
├── notes
│     └── a-simple-static-website-generator.html
└── privacy-policy.html

Add a webpage

Each HTML page in the directory _page only contains the main content, while header and footer are shared in every page. Thus we don't need to copy them every time.

So, adding a page is very easy. Save every new page within the folder _page, or in one of its subfolders. Start with the first two lines by defining the title and description for this page, by using the macro T‍ITLE and D‍ESCRIPTION from sswg custom syntax. Then separate the macro from the rest of the page with 3 dashes "---". For example:

T‍ITLE="My new web page"
D‍ESCRIPTION="This page contains information about..."
---
<h2>TI‍TLE</h2>
# This is a sswg comment.
<p>A page can contain whatever valid HTML code.</p>

The example also shows comments syntaxt. Every line which starts with a hash mark # is considered as a source comment. Hence, it won't be copied in the final static HTML page.

Header and Footer templates

Inside _header.t.html write the page content from the DOCTYPE tag, to the navigation of your page, until the beginning of your main content. Use the macros TI‍TLE and D‍ESCRIPTION for HTML title and meta description. The generator will overwrite them with the value specified within the HTML page. Here an example of header templete:

<!DOCTYPE html>
<html>
  <head>
    <title>TI‍TLE</title>
    <meta name="description" content="DE‍SCRIPTION">
    <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
  </head>

  <body>
    <header>
      <h1>Generic website title</h1>
      <div>
        <aside>I believe in personal digital freedom</aside>
        <nav>
          <a href="/index.html">Home</a> |
          <a href="/my-notes.html">My notes</a> |
          <a href="/email.html">Email</a>
        </nav>
      </div>
    </header>
    <main>

Similarly to the header, _footer.t.html contains all the rest of the website to be inserted at the end of each page. No macro substitution is needed in this file. Here an example:

    </main>
    <footer>
      <p><a href="/rss.xml"><img src="/images/feed.svg">Feed RSS</a>.
      <p>Copyright &copy; 2020-2021 Acme -
      <a href="/privacy-policy.html">Privacy policy</a></p>
    </footer>
  </body>
</html>

Inserting verbatim source code

I would like the HTML code assembled by sswg to maintain a proper indentation. Therefore, the script takes care about indenting the page contents between the header and footer templates. However, this is an issue for the verbatim source code.

Indeed, any sort of white-spaces indentation added to the content of the code block tags (<pre>...</pre>) is interpreted by browsers as white-space characters of the verbatim code. Therefore, the white-space characters will appear in the code blocks, resulting in unwanted spaces.

To solve this issue, I decided to prepose every code block with a pipe character |. The sswg script will take care in removing the pipe and white-space characters. For example, the code

    |<pre>
    |cd
    |ls -la
    |</pre>

will be transformed into the HTML source code

<pre>
cd
ls -la
</pre>

Code step by step

By the time you will read this article, the code might have changed. So you will find the current version in the repository git.andreacorsini.xyz/sswg.

For what concern the early static website generator script, it starts by setting constants for folders and template elements:

#!/bin/sh

SSWG_OUTPUT_DIR="_static"
SSWG_ASSETS_DIR="_assets"
SSWG_PAGES_DIR="_pages"
SSWG_HEADER_TEMPLATE="_header.t.html"
SSWG_FOOTER_TEMPLATE="_footer.t.html"

Second, the output folder _static is cleaned:

rm -rf "$SSWG_OUTPUT_DIR"
mkdir "$SSWG_OUTPUT_DIR"

So, be careful if you copied something inside it. It is wiser to copy external files into _assets, as they will be automatically copied back into _static:

cp -r "$SSWG_ASSETS_DIR"/* "$SSWG_OUTPUT_DIR"/.

Then, we can finally generate each HTML page:

for page in $(find "$SSWG_PAGES_DIR" -iname '*.html' -o \
                   -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png');
do
    filename="$SSWG_OUTPUT_DIR/${page##$SSWG_PAGES_DIR/}"
    mkdir -p "`dirname $filename`"

    if [ "${filename##*.}" = "html" ]; then

Prepose the header template:

        cat "$SSWG_HEADER_TEMPLATE" >> "$filename"

Indentation in the page content to match the header level:

        cat "$page" | awk '
        BEGIN {print ""}
        FNR>3 {print "      " $0}
        END {print ""}' >> "$filename"

Append the footer template:

        cat "$SSWG_FOOTER_TEMPLATE" >> "$filename"

To perform the macro substitution, firstly shell-evaluate the first two lines of the page. They are supposed to contain a shell-like declaration of the variables TI‍TLE and DES‍CRIPTION. Secondly, each macro can be substituted in the whole document.

        eval `cat "$page" | awk 'FNR<3'`
        sed -i'' "s@TI‍TLE@$TIT‍LE@g" "$filename"
        sed -i'' "s@DES‍CRIPTION@$DES‍CRIPTION@g" "$filename"

We can now remove comments and pipe + white-spaces:

        sed -i'' "/^[ \t]*#/d" "$filename"
        sed -i'' "s/^[ \t]*|//g" "$filename"

Continue with the rest of the script. If we are not reading an HTML file, it could be either an image or a folder. Just copy it to its destination in _static:

    else
        cp $page $filename
    fi;
done;

Conclusion

Static website generators are valid lightweight alternatives to more complicate and larger content management systems (CMS), such as Wordpress. They are normally simple to use, requires no databases, neither complicated install procedures. Users are in control throught text files.

This post showed how to write a simple static website generator (sswg) to get our web pages started. So far, the generator script has only bare-bones functionalities, but it is simple enough to be extended with additional features.

I hope you found this post useful and interesting. Don't hesitate to contact me for any questions or comments.