diff --git a/README.md b/README.md
index e1f210d..d11c3b2 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,30 @@
zona
[zona](https://sr.ht/~ficd/zona) is an _opinionated_ static site generator
-written in Python. From a structured directory of Markdown content, zona builds
-a simple static website. It's designed to get out of your way and let you focus
-on writing.
+written in Python. From a structured directory of Markdown content, zona
+builds a simple static website. It's designed to get out of your way and
+let you focus on writing.
-**What do I mean by opinionated?** I built zona primarily for myself. I've tried
-making it flexible by exposing as many variables as possible to the template
-engine. However, if you're looking for something stable, complete, and fully
-configurable, zona may not be for you. If you want a minimal Markdown blog and
-are comfortable with modifying `jinja2` templates and CSS, then you're in luck.
+**What do I mean by opinionated?** I built zona primarily for myself. I've
+tried making it flexible by exposing as many variables as possible to the
+template engine. However, if you're looking for something stable,
+complete, and fully configurable, zona may not be for you. If you want a
+minimal Markdown blog and are comfortable with modifying `jinja2`
+templates and CSS, then you're in luck.
-**Note:** This project is in early development, there are no versioned releases
-yet, and breaking changes are likely. Versioned releases will be made and zona
-will be published to PyPI once it's stable. zona was previously implemented in
-Go; I decided to rewrite the project in Python. If you're interested in seeing
-the previous codebase (which is feature incomplete), visit the
-[~ficd/zona-go](https://git.sr.ht/~ficd/zona-go) repository.
+**Note:** This project is in early development, there are no versioned
+releases yet, and breaking changes are likely. Versioned releases will be
+made and zona will be published to PyPI once it's stable. zona was
+previously implemented in Go; I decided to rewrite the project in Python.
+If you're interested in seeing the previous codebase (which is feature
+incomplete), visit the [~ficd/zona-go](https://git.sr.ht/~ficd/zona-go)
+repository.
For an example of a website built with zona, please see
[ficd.ca](https://ficd.ca).
+
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
@@ -41,6 +44,7 @@ For an example of a website built with zona, please see
- [Sitemap](#sitemap)
- [Ignore List](#ignore-list)
- [Drafts](#drafts)
+
## Features
@@ -55,15 +59,16 @@ For an example of a website built with zona, please see
- Easily configurable sitemap header.
- Site footer written in Markdown.
- Smart site layout discovery.
- - Blog posts are automatically discovered and rendered accordingly (can be
- overridden in frontmatter).
+ - Blog posts are automatically discovered and rendered accordingly (can
+ be overridden in frontmatter).
- Extended Markdown renderer:
- Smart internal link resolution.
- Syntax highlighting.
- Includes Kakoune syntax and [Ashen] highlighting.
- [Image labels](#image-labels).
- - Many `python-markdown` extensions enabled, including footnotes, tables,
- abbreviations, etc.
+ - Many `python-markdown` extensions enabled, including footnotes,
+ tables, abbreviations, etc.
+ - LaTeX support.
## Installation
@@ -81,66 +86,68 @@ available options and arguments._
### Getting Started
-To set up a new website, create a new directory and run `zona init` inside of
-it. This creates the required directory structure and writes the default
-configuration file. The default templates and default stylesheet are also
-written.
+To set up a new website, create a new directory and run `zona init` inside
+of it. This creates the required directory structure and writes the
+default configuration file. The default templates and default stylesheet
+are also written.
### Building
-To build the website, run `zona build`. The project root is discovered according
-to the location of `config.yml`. By default, the output directory is called
-`public`, and saved inside the root directory.
+To build the website, run `zona build`. The project root is discovered
+according to the location of `config.yml`. By default, the output
+directory is called `public`, and saved inside the root directory.
If you don't want discovery, you can specify the project root as the first
argument to `zona build`. You may specify a path for the output using the
-`--output/-o` flag. The `--draft/-d` flag includes draft posts in the output.
+`--output/-o` flag. The `--draft/-d` flag includes draft posts in the
+output.
-_Note: the previous build is _not_ cleaned before the new site is built. If
-you've deleted some pages, you may need to remove the output directory before
-rebuilding._
+_Note: the previous build is _not_ cleaned before the new site is built.
+If you've deleted some pages, you may need to remove the output directory
+before rebuilding._
### Live Preview
-To make the writing process as frictionless as possible, zona ships with a live
-preview server. It spins up an HTTP server, meaning that internal links work
-properly (this is not the case if you simply open the `.html` files in your
-browser.)
+To make the writing process as frictionless as possible, zona ships with a
+live preview server. It spins up an HTTP server, meaning that internal
+links work properly (this is not the case if you simply open the `.html`
+files in your browser.)
-Additionally, the server watches for changes to all source files, and rebuilds
-the website when they're modified. _Note: the entire website is rebuilt — this
-ensures that links are properly resolved._
+Additionally, the server watches for changes to all source files, and
+rebuilds the website when they're modified. _Note: the entire website is
+rebuilt — this ensures that links are properly resolved._
-Optionally, live reloading of the browser is also provided. With this feature
-(enabled by default), your browser will automatically refresh open pages
-whenever the site is rebuilt. The live reloading requires JavaScript support
-from the browser — this is why the feature is optional.
+Optionally, live reloading of the browser is also provided. With this
+feature (enabled by default), your browser will automatically refresh open
+pages whenever the site is rebuilt. The live reloading requires JavaScript
+support from the browser — this is why the feature is optional.
-To start a preview server, use `zona serve`. You can specify the root directory
-as its first argument. Use the `--host` to specify a host name (`localhost` by
-default) and `--port/-p` to specify a port (default: `8000`). The `--output/-o`
-and `--draft/-d` options from `zona build` are also supported. Finally, the
-`--no-live-reload/-n` disables the live browser reloading. _Automatic site
-rebuilds are not disabled._
+To start a preview server, use `zona serve`. You can specify the root
+directory as its first argument. Use the `--host` to specify a host name
+(`localhost` by default) and `--port/-p` to specify a port (default:
+`8000`). The `--output/-o` and `--draft/-d` options from `zona build` are
+also supported. Finally, the `--no-live-reload/-n` disables the live
+browser reloading. _Automatic site rebuilds are not disabled._
-**Note**: if the live preview isn't working as expected, try restarting the
-server. If you change the configuration or any templates, the server must also
-be restarted. The live preview uses the same function as `zona build`
-internally; this means that the output is also written to disk.
+**Note**: if the live preview isn't working as expected, try restarting
+the server. If you change the configuration or any templates, the server
+must also be restarted. The live preview uses the same function as
+`zona build` internally; this means that the output is also written to
+disk.
#### How It Works
-The basic idea is this: after a rebuild, the server needs to notify your browser
-to refresh the open pages. We implement this using a small amount of JavaScript.
-The server injects a tiny script into any HTML page it serves; which causes your
-browser to open a WebSocket connection with the server. When the site is
-rebuilt, the server notifies your browser via the WebSocket, which reloads the
-page.
+The basic idea is this: after a rebuild, the server needs to notify your
+browser to refresh the open pages. We implement this using a small amount
+of JavaScript. The server injects a tiny script into any HTML page it
+serves; which causes your browser to open a WebSocket connection with the
+server. When the site is rebuilt, the server notifies your browser via the
+WebSocket, which reloads the page.
Unfortunately, there is no way to implement this feature without using
-JavaScript. **JavaScript is _only_ used for the live preview feature. The script
-is injected by the server, and never written to the HTML files in the output
-directory.**
+JavaScript. **JavaScript is _only_ used for the live preview feature. The
+script is injected by the server, and never written to the HTML files in
+the output directory.**
### Site Layout
@@ -153,30 +160,32 @@ templates/
public/
```
-The **root** of the zona **project** _must_ contain the configuration file,
-`config.yml`, and a directory called `content`. A directory called `templates`
-is optional, and prioritized if it exists. `public` is the built site output —
-it's recommended to add this path to your `.gitignore`.
+The **root** of the zona **project** _must_ contain the configuration
+file, `config.yml`, and a directory called `content`. A directory called
+`templates` is optional, and prioritized if it exists. `public` is the
+built site output — it's recommended to add this path to your
+`.gitignore`.
The `content` directory is the **root of the website**. Think of it as the
-**content root**. For example, suppose your website is hosted at `example.com`.
-`content/blog/index.md` corresponds to `example.com/blog`,
+**content root**. For example, suppose your website is hosted at
+`example.com`. `content/blog/index.md` corresponds to `example.com/blog`,
`content/blog/my-post.md` becomes `example.com/blog/my-post`, etc.
- Internal links are resolved **relative to the `content` directory.**
- Templates are resolved relative to the `template` directory.
Markdown files inside a certain directory (`content/blog` by default) are
-automatically treated as _blog posts_. This means they are rendered with the
-`page` template, and included in the `post_list`, which can be included in your
-site using the `post_list` template.
+automatically treated as _blog posts_. This means they are rendered with
+the `page` template, and included in the `post_list`, which can be
+included in your site using the `post_list` template.
### Templates
The `templates` directory may contain any `jinja2` template files. You may
-modify the existing templates or create your own. To apply a certain template to
-a page, set the `template` option in its [frontmatter](#frontmatter). The
-following public variables are made available to the template engine:
+modify the existing templates or create your own. To apply a certain
+template to a page, set the `template` option in its
+[frontmatter](#frontmatter). The following public variables are made
+available to the template engine:
| Name | Description |
| ---------- | ------------------------------------------------------ |
@@ -188,40 +197,43 @@ following public variables are made available to the template engine:
#### Markdown Footer
-The `templates` directory can contain a file called `footer.md`. If it exists,
-it's parsed and rendered into HTML, then made available to other templates as
-the `footer` variable. If `footer.md` is missing but `footer.html` exists, then
-it's used instead. **Note: links are _not_ resolved in the footer.**
+The `templates` directory can contain a file called `footer.md`. If it
+exists, it's parsed and rendered into HTML, then made available to other
+templates as the `footer` variable. If `footer.md` is missing but
+`footer.html` exists, then it's used instead. **Note: links are _not_
+resolved in the footer.**
### Internal Link Resolution
-When zona encounters links in Markdown documents, it attempts to resolve them as
-internal links. Links beginning with `/` are resolved relative to the content
-root; otherwise, they are resolved relative to the Markdown file. If the link
-resolves to an existing file that is part of the website, it's replaced with an
-appropriate web-server-friendly link. Otherwise, the link isn't changed.
+When zona encounters links in Markdown documents, it attempts to resolve
+them as internal links. Links beginning with `/` are resolved relative to
+the content root; otherwise, they are resolved relative to the Markdown
+file. If the link resolves to an existing file that is part of the
+website, it's replaced with an appropriate web-server-friendly link.
+Otherwise, the link isn't changed.
-For example, suppose the file `blog/post1.md` has a link `./post2.md`. The HTML
-output will contain the link `/blog/post2` (which corresponds to
-`/blog/post2/index.html`). Link resolution is applied to _all_ internal links,
-including those pointing to static resources like images. Links are only
-modified if they point to a real file that's not included in the ignore list.
+For example, suppose the file `blog/post1.md` has a link `./post2.md`. The
+HTML output will contain the link `/blog/post2` (which corresponds to
+`/blog/post2/index.html`). Link resolution is applied to _all_ internal
+links, including those pointing to static resources like images. Links are
+only modified if they point to a real file that's not included in the
+ignore list.
### Syntax Highlighting
-Zona uses [Pygments] to provide syntax highlighting for fenced code blocks. The
-following Pygments plugins are included:
+Zona uses [Pygments] to provide syntax highlighting for fenced code
+blocks. The following Pygments plugins are included:
- [pygments-kakoune](https://git.sr.ht/~ficd/pygments-kakoune)
- - A lexer providing for highlighting Kakoune code. Available under the `kak`
- and `kakrc` aliases.
+ - A lexer providing for highlighting Kakoune code. Available under the
+ `kak` and `kakrc` aliases.
- [pygments-ashen](https://git.sr.ht/~ficd/ashen/tree/main/item/pygments/README.md)
- - An implementation of the [Ashen](https://git.sr.ht/~ficd/ashen) theme for
- Pygments.
+ - An implementation of the [Ashen](https://git.sr.ht/~ficd/ashen) theme
+ for Pygments.
If you want to use any external Pygments styles or lexers, they must be
-available in zona's Python environment. For example, you can give zona access to
-[Catppucin](https://github.com/catppuccin/python):
+available in zona's Python environment. For example, you can give zona
+access to [Catppucin](https://github.com/catppuccin/python):
```yaml
# config.yml
@@ -236,9 +248,9 @@ Then, run zona with the following `uv` command:
uvx --with catppucin zona build
```
-Inline syntax highlighting is also provided via a `python-markdown` extension.
-If you prefix inline code with a shebang followed by the language identifier, it
-will be highlighted. For example:
+Inline syntax highlighting is also provided via a `python-markdown`
+extension. If you prefix inline code with a shebang followed by the
+language identifier, it will be highlighted. For example:
```
`#!python print(f"I love {foobar}!", end="")`
@@ -247,11 +259,27 @@ will be rendered as
(the #!lang is stripped)
```
+### Markdown Extensions
+
+- [BetterEm](https://facelessuser.github.io/pymdown-extensions/extensions/betterem/)
+- [SuperFences](https://facelessuser.github.io/pymdown-extensions/extensions/superfences/)
+ - `disable_indented_code_blocks=True`
+- [Extra](https://python-markdown.github.io/extensions/extra/)
+ - Excluding Fenced Code Blocks.
+- [Caret](https://facelessuser.github.io/pymdown-extensions/extensions/caret/)
+- [Tilde](https://facelessuser.github.io/pymdown-extensions/extensions/tilde/)
+- [Sane Lists](https://python-markdown.github.io/extensions/sane_lists/)
+- [EscapeAll](https://facelessuser.github.io/pymdown-extensions/extensions/escapeall/)
+ - `hardbreak=True`
+- [LaTeX2MathML4Markdown](https://gitlab.com/parcifal/l2m4m/-/tree/develop?ref_type=heads)
+ - Disable per-file with the `math: false` frontmatter option.
+
### Image Labels
-A feature unique to zona is **image labels**. They make it easy to annotate
-images in your Markdown documents. The alt text Markdown element is rendered as
-the label — with support for inline Markdown. Consider this example:
+A feature unique to zona is **image labels**. They make it easy to
+annotate images in your Markdown documents. The alt text Markdown element
+is rendered as the label — with support for inline Markdown. Consider this
+example:
```markdown

@@ -266,14 +294,14 @@ The above results in the following HTML:
```
The `image-container` class is provided as a convenience for styling. The
-default stylesheet centers the label under the image. Note: _links_ inside image
-captions are not currently supported. I am looking into a solution.
+default stylesheet centers the label under the image. Note: _links_ inside
+image captions are not currently supported. I am looking into a solution.
### Frontmatter
-YAML frontmatter can be used to configure the metadata of documents. All of them
-are optional. `none` is used when the option is unset. The following options are
-available:
+YAML frontmatter can be used to configure the metadata of documents. All
+of them are optional. `none` is used when the option is unset. The
+following options are available:
| Key | Type & Default | Description |
| ------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------ |
@@ -285,6 +313,7 @@ available:
| `template` | `str \| none` = `none` | Template to use for this page. Relative to `templates/`, `.html` extension optional. |
| `post` | `bool \| none` = `none` | Whether this page is a **post**. `true`/`false` is _absolute_. Leave it unset for automatic detection. |
| `draft` | `bool` = `false` | Whether this page is a draft. See [drafts](#drafts) for more. |
+| `math` | `bool` = `true` | Whether the LaTeX extension should be enabled for this page. |
**Note**: you can specify the date in any format that can be parsed by
[`python-dateutil`](https://pypi.org/project/python-dateutil/).
@@ -305,9 +334,10 @@ template: post_list
Welcome to my blog! Please find a list of my posts below.
```
-Setting `post: false` is necessary because, by default, all documents inside
-`content/blog` are considered to be posts unless explicitly disabled in the
-frontmatter. We don't want the post list to list _itself_ as a post.
+Setting `post: false` is necessary because, by default, all documents
+inside `content/blog` are considered to be posts unless explicitly
+disabled in the frontmatter. We don't want the post list to list _itself_
+as a post.
Then, you'd create `content/blog/my-post.md` and populate it:
@@ -318,22 +348,22 @@ date: July 5, 2025
---
```
-Because `my-post` is inside the `blog` directory, `post: true` is implied. If
-you wanted to put it somewhere outside `blog`, you would need to set
+Because `my-post` is inside the `blog` directory, `post: true` is implied.
+If you wanted to put it somewhere outside `blog`, you would need to set
`post: true` for it to be included in the post list.
## Configuration
-Zona is configured in YAML format. The configuration file is called `config.yml`
-and it **must** be located in the root of the project — in the same directory as
-`content` and `templates`.
+Zona is configured in YAML format. The configuration file is called
+`config.yml` and it **must** be located in the root of the project — in
+the same directory as `content` and `templates`.
-Your configuration will be merged with the defaults. `zona init` also writes a
-copy of the default configuration to the correct location. If it exists, you'll
-be prompted before overwriting it.
+Your configuration will be merged with the defaults. `zona init` also
+writes a copy of the default configuration to the correct location. If it
+exists, you'll be prompted before overwriting it.
-**Note:** Currently, not every configuration value is actually used. Only the
-useful settings are listed here.
+**Note:** Currently, not every configuration value is actually used. Only
+the useful settings are listed here.
Please see the default configuration:
@@ -365,10 +395,10 @@ blog:
### Sitemap
-You can define a sitemap in the configuration file. This is a list of links that
-will be rendered at the top of every page. The `sitemap` is a dictionary of
-`string` to `string` pairs, where each key is the displayed text of the link,
-and the value if the `href`. Consider this example:
+You can define a sitemap in the configuration file. This is a list of
+links that will be rendered at the top of every page. The `sitemap` is a
+dictionary of `string` to `string` pairs, where each key is the displayed
+text of the link, and the value if the `href`. Consider this example:
```yaml
sitemap:
@@ -380,17 +410,17 @@ sitemap:
### Ignore List
-You can set a list of glob patterns in the [configuration](#configuration) that
-should be ignored by zona. This is useful because zona makes a copy of _every_
-file it encounters inside the `content` directory, regardless of its type. The
-paths must be relative to the `content` directory.
+You can set a list of glob patterns in the [configuration](#configuration)
+that should be ignored by zona. This is useful because zona makes a copy
+of _every_ file it encounters inside the `content` directory, regardless
+of its type. The paths must be relative to the `content` directory.
### Drafts
-zona allows you to begin writing content without including it in the final build
-output. If you set `draft: true` in a page's frontmatter, it will be marked as a
-draft. Drafts are completely excluded from `zona build` and `zona serve` unless
-the `--draft` flag is specified.
+zona allows you to begin writing content without including it in the final
+build output. If you set `draft: true` in a page's frontmatter, it will be
+marked as a draft. Drafts are completely excluded from `zona build` and
+`zona serve` unless the `--draft` flag is specified.
[Ashen]: https://sr.ht/~ficd/ashen
[Pygments]: https://pygments.org/