How I made this blog

As you might know, the structure of my site is pretty simple. This is because, my priority was to make a site that is robust in the sense, that it is easy to add content. For this, I looked at so called static site generators. I often found that the existing ones are quite opinionated and I had the urge to make something I can call my own. At the moment I’m in the process of studying Lua as a tool to make games. In order to get more involved I decided to make my website also with Lua.

Idea

The process of static site creation is simple. You put all files of your website in a directory, then if you execute a script a new folder is created and files from your project folder get copied there. But if it was that easy, we wouldn’t need to code anything. A website in plain HTML is hard to read and comprehend. There is lot of stuff that is responsible for the formatting of the site and then there is the content. What if you could write your blog posts and data for page components in a format that’s only a bit more complicated than plain text? Well, all my blog posts are now located in a directory on my computer. I press enter and voila - the site is created and uploaded to Neocities.

Folder structure

There are only two HTML files yet. They contain mustache annotations, i.e. we can insert data programatically into the document. The documents compile into HTML without annotations. The files \(\texttt{changelog.lua}\), \(\texttt{games.lua}\), \(\texttt{emoji_map.lua}\) provide table data and shortcuts to insert emojis. In the content folder we find actual posts like this one and certain sections for the front page (game are also here).

.
|--- build.lua
|--- index_template.html
|--- blog_template.html
|--- changelog.lua
|--- games.lua
|--- emoji_map.lua
|--- content
|    |
|    |--- about.djot
|    |--- blog
|    |    |
|    |    |--- 1
|    |    |    |--- article.djot
|    |    |    |--- description.djot
|    |    |    |--- meta.lua
|    |    |
|    |    |--- 2
|    |         |--- article.djot
|    |         |--- description.djot
|    |         |--- meta.lua
|    |         
|    |--- games
|         |--- snake
|         |--- game.data
|
|--- css
     |--- custom.css
     |--- w3.css

Build script

The script makes use of three libraries. Lustache is an implementation of Mustache for Lua. The Djot library is the reference implementation of the same-named markup language, that was released just this year. Luafilesystem we come in handy for handling iteration over our folder structure. We import all three.

-- import libraries
local lustache = require "lustache"
local djot = require "djot"
local lfs = require "lfs"

The table \(\texttt{blog}\) will contain everything that has to do with blog posts. Same with the remaining three. We will need them as input for the Mustache compilations.

local blog = {}
local changelog = {}
local games = {}
local emojis = {}

The functions \(\texttt{read_file}\), \(\texttt{write_file}\), \(\texttt{djot_to_html}\) are utility functions. We only need the most simple functionalities, e.g. \(\texttt{read_file}\) takes a path of a file and returns its content as a string and \(\texttt{write_file}\) works in the other direction. The function \(\texttt{djot_to_html}\) converts a string in the Djot format into HTML. For the rest the libraries will take care of. The most complicated function in the entire script is \(\texttt{copy_emojis}\) because it mainly consists of side effects. This will be improved in the future. It copies all emojis into the \(\texttt{site}\) folder, then it returns the paths of those emojis as a table.

-- reads a file into a string
local function read_file(src)
    local file = io.open(src)
    local str = file:read("a")
    file:close()
    return str
end

local function write_file(src, str)
    lfs.touch(src)
    local file = io.open(src, "w")
    file:write(str)
    file:close()
end

-- converts a djot formatted string into html
local function djot_to_html(input)
    local parser = djot.Parser:new(input)
    parser:parse()
    return parser:render_html()
end

local function copy_emojis()
    local emoji = require "emoji_map"
    for shrt, pth in pairs(emoji) do
        os.execute("cp " .. pth .. " site/assets/emojis/" .. shrt .. ".png")
        emoji[shrt] = "assets/emojis/" .. shrt .. ".png"
    end
    return emoji
end

For the next lines we could have used LFS, but some unix commands also do the job. Note that we remove the complete site directory to begin with.

os.execute("rm -rf ./site")
os.execute("mkdir site")
os.execute("mkdir site/blog")
os.execute("mkdir site/css")
os.execute("mkdir site/assets")
os.execute("mkdir site/assets/emojis")
os.execute("cp css/custom.css site/css/custom.css")
os.execute("cp -r content/games site/games")

The next line is somehow unique and McGuyer-ish. It enables the use of emojis on the main page. I like those from the mutant standard and I would like to use them in future. A better method would be nice though.

-- parse about page (side effects, careful)
local about = djot_to_html(lustache:render(read_file("content/about.djot"), {emoji = copy_emojis()}))

In this loop I iterate through the blog directory, parse all of the data and convert the Djot on the fly. Every blog post has a description as well, but the stylsheet does not support it. It also cares for the routing to every post and back.

-- parse blog entries with content
for entry in lfs.dir("./content/blog") do
    if entry ~= "." and entry ~= ".." then
        local ind = tonumber(entry)
        for file in lfs.dir("./content/blog/" .. entry) do
            if file ~= "." and file ~= ".." then       
                blog[ind] = require("./content/blog/" .. entry .. "/meta")
                blog[ind].article = djot_to_html(read_file(
                    "./content/blog/" .. entry ..
                        "/article.djot"))
                blog[ind].desc = djot_to_html(read_file(
                    "./content/blog/" .. entry ..
                        "/description.djot"))
                blog[ind].route = ind .. ".html"
            end
        end
    end
end

In the last step, every page is created by feeding the won data to the templates. Finally, we save them in the foreseen directories.

changelog = require "changelog"
games = require "games"

-- feed template with blog entries

local page = read_file("./index_template.html")
local bpage = read_file("./blog_template.html")

write_file("./site/index.html", lustache:render(page, {about = about, blog = blog, changelog = changelog, games = games}))

for i = 1, #blog do
    write_file("./site/blog/" .. blog[i].route, lustache:render(bpage, {article = blog[i].article}))
end