Metadata-Version: 2.4
Name: neostatic
Version: 0.1.0
Summary: Simple static site generator for neocities
Author-email: Gianni Tedesco <gianni@scaramanga.co.uk>
License: GPLv3
Classifier: Programming Language :: Python
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: COPYING
Requires-Dist: jinja2
Requires-Dist: markdown
Requires-Dist: python-dateutil
Requires-Dist: python-slugify
Requires-Dist: requests[socks]
Provides-Extra: build
Requires-Dist: setuptools; extra == "build"
Requires-Dist: wheel; extra == "build"
Requires-Dist: build; extra == "build"
Requires-Dist: twine; extra == "build"
Provides-Extra: dev
Requires-Dist: mypy; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Requires-Dist: flake8-pyproject; extra == "dev"
Requires-Dist: types-Markdown; extra == "dev"
Requires-Dist: types-PySocks; extra == "dev"
Requires-Dist: types-python-dateutil; extra == "dev"
Requires-Dist: types-python-slugify; extra == "dev"
Requires-Dist: types-requests; extra == "dev"
Dynamic: license-file

# neostatic

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](https://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][meta] rather
  than a second data format embedded in the post.

[meta]: https://python-markdown.github.io/extensions/meta_data/

## Quick start

```sh
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`)

```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.
