Mismiy is an exercise in finding how little we can do to create a site generator from scratch. It’s implemented in Python, so very little code is needed to iterate over files, stuff them though Markdown and Mustache, and write the results in to a directory. Modern CSS and HTML5 between them mean there is little need for preprocessing CSS or elaborate JavaScript.
Status: We have the bare minimum to be a blog:
- an index page with reverse-chronological list of posts,
- post pages with a link back to the index,
- non-post pages like an about page,
- very basic navigation using tags, and
- an Atom feed so it can be read in an RSS reader.
Mismiy is pronounced /ˈmɪsmɪj/ ‘Miss me?’
For now the way to use this package is a bit primitive:
- You need to have installed Python and Python Poetry.
- Copy the Mismiy project in to a directory, say
~/src/mismiy. - Create a separate folder, say
~/my-bloggy-blog, copy~/src/mismiy/templatesin to it, and create directoriesposts,static, andpub. - Create posts in the
postsdirectory (in the format described below). - Copy stylesheets and the files they need to the
staticdirectory. - In
~/src/mismiyruneval $(poetry env activate)to activate the virtual environment, thencdto~/my-bloggy-blogand run the commandmismiy -w. - In a separate terminal window, visit
~/my-bloggy-blog/pub. There should be some HTML files here now. Run the commandpython -mhttp.server. - Open http://localhost:8000/ and you should have an index page.
- Edit the templates or the style sheets to change the appearance of your blog.
Future versions may automate away some of the above steps.
A post is a file that looks a like this:
title: Greetings from Blefuscu
author: Lalcom
**Lilliput** and **Blefuscu** are two fictional island nations that appear
in the first part of the 1726 novel _Gulliver's Travels_ by [Jonathan Swift].
[Jonathan Swift]: https://en.wikipedia.org/wiki/Jonathan_SwiftThe first section defines metadata like the title and author name. It ends with the first blank line. The rest of the file is Markdown text.
Within the Markdown code a paragraph containing only an image tag is used to
insert a figure. For example,  looks up the figure with
id value fig1 and renders the partial template figure.html to produce the
HTML representation of the figure and its caption.
The file name is used in the URL of the post, with the .md or .markdown suffix
replaced by .html.
The index pages list the posts in alphabetical order by file name. The usual convention
is to start the file name with the date in ISO format. For example, 2025-07-22-british-pi-day.md,
or by creating subdirectories so your posts have file names like 2025/07/22/british-pi-day.md.
The title field is required, and supplies the heading for the post, also used to link to it in the index.
The author field can be a string, or can be a nested object with name (required),
uri and email fields (both optional). Nested objects are shown indented below author: key, like this:
author:
name: Quizzog the Magnificent
email: quiz@snaggleheim.example
url: https://snaggleheim.example/bibliocrats/quizThe id field is a URI uniquely identifying this post. It is used in the Atom feed.
It should be guaranteed
unique, and persistent. If not supplied then one will be
generated based on the blog’s id. Common choices for id fields are as follows:
- the URL of the post itself, as in
https://snaggleheim.example/blog/2025-07-22-pi-day.html - a UUID, as in
urn:uuid:955d36c7-8ec0-49f7-be73-c455daadb97a - a tag (RFC 4151), as in
tag:snaggleheim.example,2025:blog:2025-07-22-pi-day
The published field gives the date and optionally the time the post is intended
to be publicly available. If the time is omitted, then midnight at the start of
the day is assumed (so 2025-07-22 is interpreted as 2025-07-22T00:00:00+00:01
if the local time zone is Europe/London). If omitted then Mismiy guesses a date
based on the name of the file.
The updated field the date and optionally the time the post was last modified
in a meaningful way, such as adding a correction or more information. There is no need to set
the updated field after merely adjusting whitespace or correcting a simple misspelling.
An optional summary field should be a plain-text summary of the post. It will be
made available in the Atom feed. It can also be used in page templates.
You can provide tags field to classify posts by subject in some way. Extra index
pages will be generated listing pages associated with a given tag. The tags are provided
as a list of strings, using dashes or asterisks as bullets:
tags:
- recipe
- pie
- old recipe bookYou can also include metadata about figures (images included in the post).
figures:
- id: foo
src: pics/foo.png
width: 560
height: 315
caption: How the foo is bar.Figures have mandatory field src, which can be either a string (URL reference)
or a map from pixel density to URL reference, like so:
figures:
- id: foo
src:
1x: pics/foo.png
2x: pics/foo.1120.png
width: 560
height: 315
caption: How the foo is bar.The id field for the figure is optional, and will be default to fig1, fig2, and so on.
The caption field is optional; it supplies text that goes next to the figure.
An optional description field supplies a long description of the appearance of
the figure.
An arbitrary structured data blob may be supplied as data. This can be used to
provide machine-readable data about the subject of the page (for example, a
review might have a summary of some data about the item being reviewed). It
can be embedded in the page via the page templates.
All of the fields are optional except title. The metadata is parsed using Strict Yaml, a less-confusing subset
of the full YAML language.
A field from a post can be supplied as a parallel file. The file name is the same
as the post, minus the .md suffix, plus the field name and .yaml. For example,
given a post 2026/08/05/cats.md, the value for the figures field could go in
a file 2026/08/05/cats.figures.yaml. This can be useful for fields that are
automatically generated.
The posts directory can contain a file named META.yaml containing metadata
about the posts collectively. For example:
title: Lilliput Tourism Guide
url: https://tourism.lilliput.example/blog/
id: https://tourism.lilliput.example/blog/
tz: Lilliput/Mildendo
icon: /logo-square-144.png
logo: /logo-wide-80.pngThe following fields are all optional.
The title field is the title for the blog as a whole, in the form to be used in the Atom feed.
The url field is used to construct absolute URLs for posts. Generally if it includes
a path component it should end with a slash.
The id field is a unique, persistent identifier for the blog as a whole, in the form
of a URL (like the post ids). Be sure to use different ids for different blogs.
If posts do not specify id fields, then the blog id will be used to generate the post ids.
If the url field is known, it is often a reasonable choice for id.
Specify the time zone to be used for dates with the tz field. This uses the
uniform naming convention of the tz database, which looks like Europe/Paris,
America/New_York, and so on. It is used when writing timestamps in to the Atom feed.
The icon and logo fields are both optional URLs of images to use in the Atom
feed to identify the blog. The icon image is expected to be square.
There are two sorts of page in the Mismiy system: regular pages and posts.
The convention is that Markdown files in the posts directory are posts, and
other pages are regular pages. The field kind can be used to override this.
Its value is one of the values post or page.
The fields title, url, and id really only matter for blogs (where kind is post).
The difference between posts and regular pages is that posts are included in the blog index. They also use a different template, though the two templates may be very similar.
Pages are generated from Markdown files in a separate directory from the posts
directory. For example, you can create a directory called pages and add
a file about.md. This will generate a page about.html in the blog.
Note that even though the source files come from different directories, they all
go together on the finished web site. If you want to have all the about
pages’ URLs to have a common prefix, create a subdirectory within the pages
directory.
The templates directory contains Mustache templates. All files in that
directory are templates, so there is no need for .mustache suffixes.
When rendering a page the template named for the kind of page is used:
post.htmlfor posts;page.htmlfor other pages;index.htmlfor the index page; andtagged.htmlfor the index page for a set of tags.
The other files, like header.html and inline.css are partial templates
(partials) referenced from the other templates.
The special templates figure.html and atom_figure.html are used for rendering figures.
The context for a page or post template includes the following:
| Key | Value |
|---|---|
author |
An object with fields name, uri, and email; the latter two may be null |
body_html |
The text of the page, converted to HTML fragments |
dotdotslash |
Relative URL to the root of the blog: a sequence zero or more repetitions of ../ that can be prepended to a relative URL |
href |
URL of the page, relative to the base URL of the blog |
name |
Name of the page, formed from the file name without .md or .markdown suffix |
published |
A date object, as described below |
tags |
If this page has tags, then a list of tag objects with label, href, and count fields |
updated |
A date object, as described below, or null |
summary |
Plain-text summary of the page, if supplied. |
data |
Structured data for the page, if supplied. |
data_json |
The data field, formatted as JSON. |
links |
List of objects with rel, href, optional title and optional type |
links_by_rel |
The same link objects, but indexed by their rel value |
figures |
Normalized metadata about figures in the page |
figures_by_id |
The figures data, indexed by id value. |
ordinal |
The position of this post within the sequence of posts. |
as_date |
A lambda that interprets the context as a date and adds a date object to the context (as table below). |
as_duration |
A lambda that interprets the context as an ISO duration literal and adds an object to the context. |
Date objects are a halfway house to proper localization of dates. They contain the following fields:
| Key | Value | Example |
|---|---|---|
day_2digits |
Day of month as two digit number | 27 |
day |
Day of month (1 or 2 digits) | 27 |
iso_date |
Numeric date in ISO order | 2025-07-27 |
iso_datetime |
Numeric date and time in ISO order | 2025-07-27T18:02:55.672711+01:00 |
month_2digits |
Month as a two-digit number | 07 |
month_name |
Full name of month | July |
month |
Month as a number (1 or 2 digits) | 7 |
year |
Year as a four-digit number | 2025 |
The objects representing figures have the following fields:
| Key | Value |
|---|---|
id |
Shorthand used to reference the image in the Markdown code |
src |
URL of image data |
srcset |
URLs of image data for high-density displays, or blank. |
width |
Width on page in CSS units |
height |
Height on page on CSS units |
caption_html |
Optional caption for the figure |
description_html |
Optional long description for the figure |
When rendering the figure.html template, the above fields comprise the context
for the template. There is an additional field
alt, the alternative text provided in the image reference.
Index pages have the following context:
| Key | Value |
|---|---|
is_index |
Always true |
links |
List of objects with rel, href, optional title and optional type |
links_by_rel |
The same link objects, but indexed by their rel value |
reverse_chronological |
List of objects with the same fields as pages except without the body and tags |
The index page is the root of the site, so dotdotslash is always empty.
Pages for combinations of tags are like index pages
| Key | Value |
|---|---|
dotdotslash |
Relative URL to the root of the blog: a sequence zero or more repetitions of ../ that can be prepended to a relative URL |
links |
List of objects with rel, href, optional title and optional type |
links_by_rel |
The same link objects, but indexed by their rel value |
reverse_chronological |
List of objects with the same fields as pages except without the body and tags |
tags |
List of tag objects |
widenings |
List of tag objects with few tags (and therefore linking to more pages) |
narrowings |
List of tag objects with one more tag (and therefore linking to fewer pages) |
So far the command does one thing: generate the site. It does this by
reading pages from posts, generating HTML with templates in templates,
and writing files in a directory called pub. Posts whose published date is in
the future are omitted.
This behaviour can be adjusted with options:
| Option | Effect |
|---|---|
--as-of date |
Change the cut-off date for unpublished articles. |
--drafts, -d |
Include unpublished articles. |
--locale locale |
Override the default locale. Must be a locale specifier like en_GB.UTF-8. |
--omit-dot-html |
If set, strip the .html from internal hrefs. You need a server that adds the .html suffix. |
--out-dir, -o path |
Root of generated HTML tree. Default is pub. |
--static-dir, -s path |
Root of static files. Default is static. |
--templates-dir, -t path |
Directory containing mustache templates. Default is templates. |
--watch, -w |
Watch files & rerun when they change. Implies --draft. |
Directories of pages to include in addition to posts can be specified on the command line.
A convenient way to work on a post is to have one terminal window running mismiy -w,
and another running python -mhttp.server from the pub directory. Then when you have saved edits to your
posts or templates, refresh the web browser window to see the updated HTML.