GitHub - giannitedesco/neostatic: A small neocities static blog generator

GitHub

3 min read Original article ↗

A small static site generator for blogs, written in Python.

TOML config → Markdown posts (Markdown-metadata frontmatter) → Jinja2 templates → static output directory → upload.

Kind of a love-letter to neocities.org.

Why this exists

Most static site generators (Hugo, Zola, Jekyll) are general-purpose templating systems that happen to be good at blogs. neostatic does the opposite: it only does one shape of site (a blog, plus static passthrough content), and stays small enough to read end to end in one sitting.

A few things that follow from that:

  • Upload is part of the tool, not bolted on afterward. neostatic renders to a local site/ directory and then pushes it straight to Neocities in the same invocation (--offline skips the upload step). Most generators render only and leave publishing to a separate script.
  • It refuses to destroy a source tree. If the output directory itself contains .git or the site's own neostatic.toml, neostatic treats that as a sign it's pointed at the wrong place and stops rather than deleting it.
  • TOML for config, Markdown metadata for posts. Site config is TOML; post frontmatter uses Python-Markdown's meta extension rather than a second data format embedded in the post.

Quick start

python -m neostatic <site-dir> [-v] [--offline] [--dry-run]

A site directory looks like:

site-dir/
  neostatic.toml
  static/            # copied through as-is (allow-listed extensions)
  templates/
    index.html
    blogpost.html    # default post template; override per-post
  posts/
    2024-01-01-hello.md

Config (neostatic.toml)

[blog]
 [blog.paths]
  static = "static"
  templates = "templates"
  posts = "posts"
  site = "site"
 [blog.meta]
  title = "example"
  strapline = "a blog"

[neocities]
 proxy = "socks5://127.0.0.1:9150"   # optional, e.g. for publishing over Tor
 user = "..."
 password = "path/to/password-file"  # contents read at load time, not inline
 api_key = "path/to/api-key-file"    # preferred over user/password if present

[blog.paths] and [neocities] are both optional; paths default to the names shown above, and Neocities upload just isn't available without that section. If api_key isn't set yet but a path is given, neostatic will fetch a key on first run (using user/password) and write it there.

Posts

Markdown-extension frontmatter, no delimiters — a block of key: value lines followed by a blank line:

title: Hello
date: 2024-01-01T12:00:00+00:00
published: true
summary: An optional one-line summary.
slug: hello
template: blogpost

Body in Markdown.

Only title and date are required. published defaults to false; slug defaults to a slugified title; template defaults to blogpost. Only published: true posts get a generated page and appear in the index; everything else is loaded but skipped at render time.