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:
- Invoke the content generation with a simple command, such as
./sswg.sh
. - The generation should be idempotent.
- Copy the header and footer templates in every page.
- Support some simple macro substitution, to allow different title and description for each page.
- Support the insertion of verbatim source code.
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
├── 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 TITLE and DESCRIPTION from sswg custom syntax. Then
separate the macro from the rest of the page with 3 dashes "---
".
For example:
TITLE="My new web page" DESCRIPTION="This page contains information about..." --- <h2>TITLE</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 TITLE
and DESCRIPTION
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>TITLE</title> <meta name="description" content="DESCRIPTION"> <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 © 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 TITLE and DESCRIPTION. Secondly, each macro can be substituted in the whole document.
eval `cat "$page" | awk 'FNR<3'` sed -i'' "s@TITLE@$TITLE@g" "$filename" sed -i'' "s@DESCRIPTION@$DESCRIPTION@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.