The Static Site Solution In Modern Python
It's simp with 3 s!
A simple tool to generate a static website while being able to use powerful HTML templates (Jinja2), Markdown files converted to HTML, and other preprocessors.
I wanted a simple way to generate static websites and I like Jinja2. I had previous experiences working with Jekyll but it seemed like too much work to setup everytime and overkill for the job as it supports many features I don't necessarily use.
One of the main goals with sssimp is being able to generate a usable website without any configuration file or dependency. You only install the sssimp package and run it.
Requirement: Python 3.8 or later
pip install --user sssimp
Create a folder called input
, it will hold the data to generate the site.
Running sssimp
will generate content in the output
folder.
Input and output destination can be changed:
sssimp --input ../some-other/input-dir ~/some-other/output-dir
Use python -m sssimp --help
for more details.
Files placed in input/content
will be directly copied to the output
folder
Example:
input/content/favicon.png
-> output/favicon.png
HTML files with the suffix .html placed in input/content
will be parsed as
Jinja2 templates, they can use templates defined in input/templates
.
See the Jinja2 documentation
Example:
input/content/index.html
-> output/index.html
Starting with content
{% extends "base.html" %}
...
Will use the template input/templates/base.html
CSS files in input/css
will be merged together in a single file
output/bundle.css
Markdown files with the suffix .md placed in input/content
will be parsed to
HTML and passed to a template with the same name as their parent folder as the
parameter markdown
Example:
./input/content/post/hello-world.md
-> ./output/post/hello-world.html
Using the template ./input/templates/post.html
Generated with context {'markdown': 'the markdown file converted to HTML'}
The template name can be overriden using the markdown meta argument "template"
Example:
./input/content/post/special.md
-> ./output/post/special.html
Starting with content
---
template: special.html
---
...
Will use the template ./input/templates/special.html
instead of post.html
Files placed in input/data
will exposes their content in templates inside
the data
variable. They can be in either YAML, JSON, or Markdown. The path
is part of their position in the data structure tree.
Example:
./input/data/categories/tech.yml
With content
description: Nerdy stuff
color: #121212
related:
- computers
- dev
Will populate the data
variable in templates as so:
{
"categories": [
{
"tech": {
"description": "Nerdy stuff",
"color": "#121212",
"related": ["computers", "dev"]
}
}
]
}
See the examples
directory for per-feature examples, or my
personal website https://github.com/Tina-otoge/tina-simp for a real-world
example.
|a
makes any relative path point to the top of the output folder.
Example:
input/content/blog/post/tech/2021/11/some-post.html
-> output/blog/post/tech/2021/11/some-post.html
With content
<link rel="stylesheet" href="{{ "style.css"|a }}">
Will be rendered as "../../../../style.css"
See also the <base>
element
|markdown
transforms Markdown content to HTML using the same process that
for Markdown files.
|json
transforms any object to a formatted JSON.
page
is a sssimp.generators.html.Page
, it contains many information about
the current document. Markdown files are an instance of
sssimp.generators.markdown.MarkdownPage
instead, which inherits from Page
This variable itself contains many useful variables:
page.modified_at
and page.created_at
(modified_at
forcibly set to None
if same as created_at
)page.href
: The path to the file relative to the output folderpage.src
: A pathlib.Path
object of the source file in the input folderpage.target
A pathlib.Path
object of the target file in the outputfolder
page.name
: Shortcut for page.target.name
, the filename of the outputed
file
page.parent
: Shortcut for page.target.parent
, the name of the parent
directory in the output folder
file
page.meta
: The Markdown meta variables, prefixing a var with =
will
interpret it as raw JSON
Example
---
some_var: some value
something_else: 42
some_tags:= ["tag1", "tag2"]
---
My cool blog post
...
The meta variable will always contain a template
which will resolve to the
parent directory name with .html appended if none is set in the meta fields.
The page.meta
variable is None
for raw HTML pages, this avoids KeyErrors
when trying to filter pages by a specific meta variable.
plain_text
[^md]: A plain text representation of the Markdown file
markdown
[^md]: The Markdown content converted to HTML
meta
[^md]: A shortcut for page.meta
title
[^md]: Returns page.meta.title
if it exists, else the filename with
the characters -
and _
replaced by whitespaces, the suffix removed and the
first letter capitalized.
Example:
input/content/some-cool-page.md
's title is "Some cool page"
BUNDLE_FILE
always evaluates to "bundle.css"
for now
BUNDLE_TIME
modification time of the latest updated file in input/css
,
very useful to make the browser refresh the file only if any of the CSS files
changed.
Example:
<link rel="stylesheet" href="{{ BUNDLE_FILE}}?{{ BUNDLE_TIME }}">
PAGES
a list of sssimp.generators.html.Page
objects containing every HTML
and Markdown files sourced by the site. You can loop over it to generate an
index. In conjunction with looking up meta values it can be used to filter by
content type.
Example:
{% for page in PAGES if page.meta.template == 'post.html' %}
<a href="{{ page.href }}">{{ page.title }}</a>
<div class="tags">
{% for tag in page.meta.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
Posted on <time>{{ page.created_at }}</time>
{% endfor %}
[^md]: Markdown only