
Simple SSG
March 2025 | by Dani
Tools & Resources
I think there's something a little wrong with me because when I realised my website could stand to benefit from using a static site generator, instead of learning how to use one of the pre-existing, well-designed generators that are already out there — a skill that would surely benefit me in years to come — I decided to code my own in python...
# copies everything inside the source directory to the config directory, and
# processes any .3sg files it comes across
def compile(source, destination, config):
for source_path in source.iterdir():
destination_path = destination / source_path.name
if source_path.is_dir():
os.mkdir(destination_path)
compile(source_path, destination_path, config)
else:
if source_path.suffix == ".3sg":
compile_file(source_path, destination_path.with_suffix(''), config)
else:
shutil.copyfile(source_path, destination_path)
This is just a sample; the full code is at present over 200 lines long, which I'll leave you to read at your leisure (or disleisure).
There were only really a couple things I wanted to do, so rather than learn Next.js or something I just made it myself.
Documentation
simpleSSG.py
must be run in the same directory as a config.cfg
file. config.cfg
is a JSON file which specifies and customises the compilation processes. Properties include:
-
"source"
: specifies the path to the directory to compile the site from (eg.src/
). -
"destination"
: specifies the path to the directory to compile the site to (eg.site/
). -
"parent"
: specifies where the compiled site will be located relative to the domain name (eg. if you're going to put this site underexample.com/blog/
, this property should be/blog/
). -
"empty_destination"
: boolean which specifies whether the contents of the destination directory should be deleted before compilation. - the properties
"indexify"
and"remove_href"
(booleans), and"broken_class"
(string), which will be explained later.
Now, I like to break down simpleSSG into five parts:
- Concision
- Indexification
- Includes
- Markdown
- Broken Links
- (Bonus) Customisation with Python
Concision
HTML files can have a lot of boilerplate, and the aim is to remove that. SimpleSSG will copy over all files and directories as they appear in the source directory, except for .3sg
files. Thus, to use the features of simpleSSG, you must create .3sg
files.
A .3sg
file is read as XML, with the root element being any HTML element. If the root is a <body>
tag, the .3sg
file will be copmiled to a .html
file with the same name, the simpleSSG compiler automatically inserting the <head>
and <html>
tags.
To specify information that would normally be put inside the <head>
tag, we can put attributes on the <body>
tag in the .3sg
file:
-
title
: specifies the title of the web page. -
icon
: specifies the path to the file to be used as the favicon. -
stylesheet
: specifies the paths to any css files to use. Multiple files should be seperated by spaces. -
script
: specifies the paths to any javascript files to use. Multiple files should be seperated by spaces.
When linking to files created from .3sg
files, you must use the .html
file extension. As a general rule, never link to a .3sg
file, as the compiler will never create one in the output directory.
Indexification
If the "indexify"
property is set to true
in config.cfg
, then every .3sg
file, rather than being compiled to a .html
file with the same name, will instead create a directory bearing the same name, and dump its contents into an index.html
inside that directory. So, page.3sg
will get compiled to page/index.3sg
. Relative links inside the file are repaired automatically.
The only exception to this rule is if your file is named index.3sg
, in which case it just compiles to index.html
.
When this option is chosen, a link to /page.3sg
can be written /page
, /page/
, or /page/index.html
.
Includes
.3sg
files which do not start with a <body>
tag will not be compiled to HTML directly, but can be included on other pages using an <include>
tag. This helps prevent redundancy, if, say, you want a header that is the same on all pages.
An <include>
should always have a src
attribute which points to a .3sg
file. The contents of this file will replace the <include>
tag in the resulting file. All links are repaired unless they have a stay_rel="true"
attribute.
Markdown
Anything inside a <md>
gets treated as markdown and is automatically converted to HTML. The <md>
tag gets turned into a <div>
and given the markdown
class.
Broken Links
SimpleSSG automatically looks for broken <a>
links where possible (ie. any link to another page inside the source files which doesn't exist). What it does when it finds these depends on config.cfg
. If the "remove_href"
property is set to true
, the href
attribute of the tag is removed. If a non-empty string is provided by the "broken_class"
property, that string is given as a class to any broken links.
This is why the parent
attribute is necessary.
The compiler automatically assumes all links to external sites or outside the source directory are working.
(Bonus) Customisation with Python!
.3sg
files can also include <py>
tags which contain python code! The code is executed during compilation, and whatever is contained within the variable pyout
by the end of it is parsed as HTML and inserted inside the tag, which is turned into a <div>
and given the python
class.
You can also specify parameters to these! Basically, whenever you have an <include>
tag, you can give it attributes which specify parameters to be given to all <py>
tags in the included file. For example, if you specify the attribute para-foo="bar"
when you include script.3sg
, then any <py>
tags inside script.3sg
can access the local variable foo
, which will hold the value "bar"
as a string. The advantage of this is that you can specify different parameters for each include.