Creative Tinkering

New Site

Past Problems

After being fed up with the limitations and unneccessary complexity of WordPress I made the decision to rebuild this site as a static html. My issue using WP to manage the site was that all the complexity prevented me from implementing some seemingly simple features redesigns or eyecandy. Another thing was that all the content is stored in a database which makes it hard to port it to any other platform should the need arise.

Clean Slate

I had a vision in my head.

Thats it, this is all the "complexity" I need.

Building the HTML

Main challenge was to somehow convert the markdown files into html files and list them in the home page.

There are some existing static site builders available, but after trying some of them out, I felt that they would lead me into the same problems with over complexity which would limit the ease of modifications again.

So I decided to use python to create a build script from scratch because I knew it could get me to the result faster than using other languages or tools.

I know python is slow at many things but the current task of building HTML and copying some files would not have to be super quick. At least not when the number of posts is below a few hunderd or thousand. Just say NO! to premature optimisation.

The build process is quite simple:

The build script is not very long at the time of writing this. Here is the full code if anyone is interested:

import marko
import os
import shutil
import re

debug_log = True

def log(msg):
    if debug_log:
        print(msg)

def extract_metadata_from_md(md_string):
    data = {} 
    for line in md_string.split("\n"):
        if not "_metadata_" in line:
            continue
        matches = re.match(r"\[_metadata_:(.+)\]:-\s+\"(.+)\"", line)
        if matches:
            key = matches.group(1)
            value = matches.group(2)
            data[key] = value
    return data

def add_line(indent, str, add_str):
    tabs = ""
    if indent > 0:
        for i in range(indent):
            tabs += "\t"
    return str + tabs + add_str + "\n"

def get_file_str(path):
    result = ""
    if os.path.exists(path):
        f = open(path, 'r')
        result = f.read()
        f.close()
    return result

class Post():
    def __init__(self, path):
        self.path = path
        self.index_path = os.path.join(self.path, "index.md")
        self.resources_dir = os.path.join(self.path, "resource")
        self.id = os.path.basename(path)
        self.title = "Untitled"
        self.is_valid = True
        self.build_dir = os.path.join("build", "posts", self.id)
        self.html_index_path = os.path.join(self.build_dir, "index.html")
        self.metadata = {}

        self.md_str = ""
        self.html_str = ""
        
        log("initializin post: %s" % (self.path)) 

        if not os.path.exists(self.index_path):
            self.is_valid = False
            log("invalid path for post")
            return

        index_file = open(self.index_path, 'r')
        self.md_str = index_file.read()
        index_file.close()

        if self.md_str == "":
            self.is_valid = False
            log("got empty markdown string")
            return

        self.html_str = add_line(0, self.html_str, get_file_str(os.path.join("templates", "header.html")))

        self.metadata = extract_metadata_from_md(self.md_str)
        self.html_str = add_line(2, self.html_str, "<div class=\"post-container\">")
        self.html_str = add_line(3, self.html_str, marko.convert(self.md_str))
        self.html_str = add_line(2, self.html_str, "</div>")

        self.html_str = add_line(0, self.html_str, get_file_str(os.path.join("templates", "footer.html")))

        self.is_valid = True

        print(self.metadata)

    def write(self):
        log("trying to write post %s" % (self.id))
        if not self.is_valid:
            log("will not write invalid post files")
            return

        os.makedirs(self.build_dir, exist_ok=True)
        
        index = open(self.html_index_path, 'w')
        index.write(self.html_str)
        index.close()

        if os.path.exists(os.path.join(self.build_dir, "resource")):
            log("copying post resources to build dir")
            shutil.copytree(self.resources_dir, os.path.join(self.build_dir, "resource"))
            log("done writing post files")
        

def build_home( posts ):
    log("building home page html file")
    html_str = ""
    html_str = add_line(0, html_str, get_file_str(os.path.join("templates", "header.html")))

    log("creating list of post links")
    html_str = add_line(2, html_str, "<div class=\"posts_list\">")
    for post in posts:
        if post.is_valid:
            html_str = add_line(3,html_str, "<a class=\"posts_list_item\" href=\"posts/%s\">" % (post.id))
            html_str = add_line(4, html_str, "<h3>%s</h3>" % (post.metadata["title"]))
            html_str = add_line(4, html_str, "<p>%s</p>" % (post.metadata["date"]))
            html_str = add_line(3, html_str, "</a>")
    
    if len(posts) < 9:
        for i in range(9-len(posts)):
            html_str = add_line(3, html_str, "<a class=\"posts_list_item placeholder\"></a>")

    html_str = add_line(2, html_str,"</div>")
    html_str = add_line(0, html_str, get_file_str(os.path.join("templates", "footer.html")))

    html_file = open(os.path.join("build", "index.html"), 'w')
    html_file.write(html_str)
    html_file.close()
    log("done building home page html file")

    log("copying home resources")
    shutil.copytree("resource", os.path.join("build", "resource"))

def main(args):
    print("removing previous build if it exists")
    shutil.rmtree("build", ignore_errors=True)

    print("scanning posts")
    posts = []
    for dirpath, dirnames, filenames in os.walk("posts"):
        for dirname in dirnames:
            posts.append(Post(os.path.join(dirpath,dirname)))
        break
    
    log("creating post files in build dir")
    for post in posts:
        post.write()

    build_home( posts )


if __name__ == "__main__":
    main(None)

Source

The posts are organized like this:

    posts
    |-post_0
      |-index.md
      |-resources
    |-post_1
      |-index.md
      |-resources
    etc...

Each post has its own resources folder for images and other assets. This makes each post an independed unit that can be handled without relying on any overarching system. This makes it easier to swap the build system in the future should I want to.

Benefits of Generating Static HTML

The best thing about building the site this way is that I am in full control, I know how everything works at every step on the way. This makes building any additional features more straightforward and most importantly fun.

Hosting a static HTML site is also very easy, it can be hosted pretty much anywhere and would even be functional when viewing it offline on a local machine.

Navigating a simple static web page feels really fast, there is just not much to load each time.

There is no unexpected behaviour, all the html that will be served is visible and not generated dynamically later on the server.

Downsides of Generating Static HTML

As with anything there are tradeoffs. Any dynamic changes to the web page in response to something can only be implemented clientside with javascript.

Some features like adding comments to posts cannot be implemented without using some 3rd party system that embeds via javascript.

Not being dynamic is kind of the point of the static HTML web site.

Personalize Your Web Page

Personal web page should be a place to have fun, create your own stuff the way you want however weird or inefficient it would be. Life is short, have fun!