updated readme

This commit is contained in:
Daniel Fichtinger 2025-07-06 21:19:07 -04:00
parent 3037aa88d8
commit d739ef5d95

288
README.md
View file

@ -1,27 +1,30 @@
<h1>zona</h1> <h1>zona</h1>
[zona](https://sr.ht/~ficd/zona) is an _opinionated_ static site generator [zona](https://sr.ht/~ficd/zona) is an _opinionated_ static site generator
written in Python. From a structured directory of Markdown content, zona builds written in Python. From a structured directory of Markdown content, zona
a simple static website. It's designed to get out of your way and let you focus builds a simple static website. It's designed to get out of your way and
on writing. let you focus on writing.
**What do I mean by opinionated?** I built zona primarily for myself. I've tried **What do I mean by opinionated?** I built zona primarily for myself. I've
making it flexible by exposing as many variables as possible to the template tried making it flexible by exposing as many variables as possible to the
engine. However, if you're looking for something stable, complete, and fully template engine. However, if you're looking for something stable,
configurable, zona may not be for you. If you want a minimal Markdown blog and complete, and fully configurable, zona may not be for you. If you want a
are comfortable with modifying `jinja2` templates and CSS, then you're in luck. 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 **Note:** This project is in early development, there are no versioned
yet, and breaking changes are likely. Versioned releases will be made and zona releases yet, and breaking changes are likely. Versioned releases will be
will be published to PyPI once it's stable. zona was previously implemented in made and zona will be published to PyPI once it's stable. zona was
Go; I decided to rewrite the project in Python. If you're interested in seeing previously implemented in Go; I decided to rewrite the project in Python.
the previous codebase (which is feature incomplete), visit the If you're interested in seeing the previous codebase (which is feature
[~ficd/zona-go](https://git.sr.ht/~ficd/zona-go) repository. 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 For an example of a website built with zona, please see
[ficd.ca](https://ficd.ca). [ficd.ca](https://ficd.ca).
<!--toc:start--> <!--toc:start-->
- [Features](#features) - [Features](#features)
- [Installation](#installation) - [Installation](#installation)
- [Usage](#usage) - [Usage](#usage)
@ -41,6 +44,7 @@ For an example of a website built with zona, please see
- [Sitemap](#sitemap) - [Sitemap](#sitemap)
- [Ignore List](#ignore-list) - [Ignore List](#ignore-list)
- [Drafts](#drafts) - [Drafts](#drafts)
<!--toc:end--> <!--toc:end-->
## Features ## Features
@ -55,15 +59,16 @@ For an example of a website built with zona, please see
- Easily configurable sitemap header. - Easily configurable sitemap header.
- Site footer written in Markdown. - Site footer written in Markdown.
- Smart site layout discovery. - Smart site layout discovery.
- Blog posts are automatically discovered and rendered accordingly (can be - Blog posts are automatically discovered and rendered accordingly (can
overridden in frontmatter). be overridden in frontmatter).
- Extended Markdown renderer: - Extended Markdown renderer:
- Smart internal link resolution. - Smart internal link resolution.
- Syntax highlighting. - Syntax highlighting.
- Includes Kakoune syntax and [Ashen] highlighting. - Includes Kakoune syntax and [Ashen] highlighting.
- [Image labels](#image-labels). - [Image labels](#image-labels).
- Many `python-markdown` extensions enabled, including footnotes, tables, - Many `python-markdown` extensions enabled, including footnotes,
abbreviations, etc. tables, abbreviations, etc.
- LaTeX support.
## Installation ## Installation
@ -81,66 +86,68 @@ available options and arguments._
### Getting Started ### Getting Started
To set up a new website, create a new directory and run `zona init` inside of To set up a new website, create a new directory and run `zona init` inside
it. This creates the required directory structure and writes the default of it. This creates the required directory structure and writes the
configuration file. The default templates and default stylesheet are also default configuration file. The default templates and default stylesheet
written. are also written.
### Building ### Building
To build the website, run `zona build`. The project root is discovered according To build the website, run `zona build`. The project root is discovered
to the location of `config.yml`. By default, the output directory is called according to the location of `config.yml`. By default, the output
`public`, and saved inside the root directory. 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 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 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 _Note: the previous build is _not_ cleaned before the new site is built.
you've deleted some pages, you may need to remove the output directory before If you've deleted some pages, you may need to remove the output directory
rebuilding._ before rebuilding._
### Live Preview ### Live Preview
To make the writing process as frictionless as possible, zona ships with a live To make the writing process as frictionless as possible, zona ships with a
preview server. It spins up an HTTP server, meaning that internal links work live preview server. It spins up an HTTP server, meaning that internal
properly (this is not the case if you simply open the `.html` files in your links work properly (this is not the case if you simply open the `.html`
browser.) files in your browser.)
Additionally, the server watches for changes to all source files, and rebuilds Additionally, the server watches for changes to all source files, and
the website when they're modified. _Note: the entire website is rebuilt — this rebuilds the website when they're modified. _Note: the entire website is
ensures that links are properly resolved._ rebuilt — this ensures that links are properly resolved._
Optionally, live reloading of the browser is also provided. With this feature Optionally, live reloading of the browser is also provided. With this
(enabled by default), your browser will automatically refresh open pages feature (enabled by default), your browser will automatically refresh open
whenever the site is rebuilt. The live reloading requires JavaScript support pages whenever the site is rebuilt. The live reloading requires JavaScript
from the browser — this is why the feature is optional. 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 To start a preview server, use `zona serve`. You can specify the root
as its first argument. Use the `--host` to specify a host name (`localhost` by directory as its first argument. Use the `--host` to specify a host name
default) and `--port/-p` to specify a port (default: `8000`). The `--output/-o` (`localhost` by default) and `--port/-p` to specify a port (default:
and `--draft/-d` options from `zona build` are also supported. Finally, the `8000`). The `--output/-o` and `--draft/-d` options from `zona build` are
`--no-live-reload/-n` disables the live browser reloading. _Automatic site also supported. Finally, the `--no-live-reload/-n` disables the live
rebuilds are not disabled._ browser reloading. _Automatic site rebuilds are not disabled._
**Note**: if the live preview isn't working as expected, try restarting the **Note**: if the live preview isn't working as expected, try restarting
server. If you change the configuration or any templates, the server must also the server. If you change the configuration or any templates, the server
be restarted. The live preview uses the same function as `zona build` must also be restarted. The live preview uses the same function as
internally; this means that the output is also written to disk. `zona build` internally; this means that the output is also written to
disk.
#### How It Works #### How It Works
The basic idea is this: after a rebuild, the server needs to notify your browser The basic idea is this: after a rebuild, the server needs to notify your
to refresh the open pages. We implement this using a small amount of JavaScript. browser to refresh the open pages. We implement this using a small amount
The server injects a tiny script into any HTML page it serves; which causes your of JavaScript. The server injects a tiny script into any HTML page it
browser to open a WebSocket connection with the server. When the site is serves; which causes your browser to open a WebSocket connection with the
rebuilt, the server notifies your browser via the WebSocket, which reloads the server. When the site is rebuilt, the server notifies your browser via the
page. WebSocket, which reloads the page.
Unfortunately, there is no way to implement this feature without using Unfortunately, there is no way to implement this feature without using
JavaScript. **JavaScript is _only_ used for the live preview feature. The script JavaScript. **JavaScript is _only_ used for the live preview feature. The
is injected by the server, and never written to the HTML files in the output script is injected by the server, and never written to the HTML files in
directory.** the output directory.**
### Site Layout ### Site Layout
@ -153,30 +160,32 @@ templates/
public/ public/
``` ```
The **root** of the zona **project** _must_ contain the configuration file, The **root** of the zona **project** _must_ contain the configuration
`config.yml`, and a directory called `content`. A directory called `templates` file, `config.yml`, and a directory called `content`. A directory called
is optional, and prioritized if it exists. `public` is the built site output — `templates` is optional, and prioritized if it exists. `public` is the
it's recommended to add this path to your `.gitignore`. 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 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 root**. For example, suppose your website is hosted at
`content/blog/index.md` corresponds to `example.com/blog`, `example.com`. `content/blog/index.md` corresponds to `example.com/blog`,
`content/blog/my-post.md` becomes `example.com/blog/my-post`, etc. `content/blog/my-post.md` becomes `example.com/blog/my-post`, etc.
- Internal links are resolved **relative to the `content` directory.** - Internal links are resolved **relative to the `content` directory.**
- Templates are resolved relative to the `template` directory. - Templates are resolved relative to the `template` directory.
Markdown files inside a certain directory (`content/blog` by default) are Markdown files inside a certain directory (`content/blog` by default) are
automatically treated as _blog posts_. This means they are rendered with the automatically treated as _blog posts_. This means they are rendered with
`page` template, and included in the `post_list`, which can be included in your the `page` template, and included in the `post_list`, which can be
site using the `post_list` template. included in your site using the `post_list` template.
### Templates ### Templates
The `templates` directory may contain any `jinja2` template files. You may 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 modify the existing templates or create your own. To apply a certain
a page, set the `template` option in its [frontmatter](#frontmatter). The template to a page, set the `template` option in its
following public variables are made available to the template engine: [frontmatter](#frontmatter). The following public variables are made
available to the template engine:
| Name | Description | | Name | Description |
| ---------- | ------------------------------------------------------ | | ---------- | ------------------------------------------------------ |
@ -188,40 +197,43 @@ following public variables are made available to the template engine:
#### Markdown Footer #### Markdown Footer
The `templates` directory can contain a file called `footer.md`. If it exists, The `templates` directory can contain a file called `footer.md`. If it
it's parsed and rendered into HTML, then made available to other templates as exists, it's parsed and rendered into HTML, then made available to other
the `footer` variable. If `footer.md` is missing but `footer.html` exists, then templates as the `footer` variable. If `footer.md` is missing but
it's used instead. **Note: links are _not_ resolved in the footer.** `footer.html` exists, then it's used instead. **Note: links are _not_
resolved in the footer.**
### Internal Link Resolution ### Internal Link Resolution
When zona encounters links in Markdown documents, it attempts to resolve them as When zona encounters links in Markdown documents, it attempts to resolve
internal links. Links beginning with `/` are resolved relative to the content them as internal links. Links beginning with `/` are resolved relative to
root; otherwise, they are resolved relative to the Markdown file. If the link the content root; otherwise, they are resolved relative to the Markdown
resolves to an existing file that is part of the website, it's replaced with an file. If the link resolves to an existing file that is part of the
appropriate web-server-friendly link. Otherwise, the link isn't changed. 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 For example, suppose the file `blog/post1.md` has a link `./post2.md`. The
output will contain the link `/blog/post2` (which corresponds to HTML output will contain the link `/blog/post2` (which corresponds to
`/blog/post2/index.html`). Link resolution is applied to _all_ internal links, `/blog/post2/index.html`). Link resolution is applied to _all_ internal
including those pointing to static resources like images. Links are only links, including those pointing to static resources like images. Links are
modified if they point to a real file that's not included in the ignore list. only modified if they point to a real file that's not included in the
ignore list.
### Syntax Highlighting ### Syntax Highlighting
Zona uses [Pygments] to provide syntax highlighting for fenced code blocks. The Zona uses [Pygments] to provide syntax highlighting for fenced code
following Pygments plugins are included: blocks. The following Pygments plugins are included:
- [pygments-kakoune](https://git.sr.ht/~ficd/pygments-kakoune) - [pygments-kakoune](https://git.sr.ht/~ficd/pygments-kakoune)
- A lexer providing for highlighting Kakoune code. Available under the `kak` - A lexer providing for highlighting Kakoune code. Available under the
and `kakrc` aliases. `kak` and `kakrc` aliases.
- [pygments-ashen](https://git.sr.ht/~ficd/ashen/tree/main/item/pygments/README.md) - [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 - An implementation of the [Ashen](https://git.sr.ht/~ficd/ashen) theme
Pygments. for Pygments.
If you want to use any external Pygments styles or lexers, they must be 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 available in zona's Python environment. For example, you can give zona
[Catppucin](https://github.com/catppuccin/python): access to [Catppucin](https://github.com/catppuccin/python):
```yaml ```yaml
# config.yml # config.yml
@ -236,9 +248,9 @@ Then, run zona with the following `uv` command:
uvx --with catppucin zona build uvx --with catppucin zona build
``` ```
Inline syntax highlighting is also provided via a `python-markdown` extension. Inline syntax highlighting is also provided via a `python-markdown`
If you prefix inline code with a shebang followed by the language identifier, it extension. If you prefix inline code with a shebang followed by the
will be highlighted. For example: language identifier, it will be highlighted. For example:
``` ```
`#!python print(f"I love {foobar}!", end="")` `#!python print(f"I love {foobar}!", end="")`
@ -247,11 +259,27 @@ will be rendered as
(the #!lang is stripped) (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 ### Image Labels
A feature unique to zona is **image labels**. They make it easy to annotate A feature unique to zona is **image labels**. They make it easy to
images in your Markdown documents. The alt text Markdown element is rendered as annotate images in your Markdown documents. The alt text Markdown element
the label — with support for inline Markdown. Consider this example: is rendered as the label — with support for inline Markdown. Consider this
example:
```markdown ```markdown
![This **image** has _markup_.](static/markdown.png) ![This **image** has _markup_.](static/markdown.png)
@ -266,14 +294,14 @@ The above results in the following HTML:
``` ```
The `image-container` class is provided as a convenience for styling. The The `image-container` class is provided as a convenience for styling. The
default stylesheet centers the label under the image. Note: _links_ inside image default stylesheet centers the label under the image. Note: _links_ inside
captions are not currently supported. I am looking into a solution. image captions are not currently supported. I am looking into a solution.
### Frontmatter ### Frontmatter
YAML frontmatter can be used to configure the metadata of documents. All of them YAML frontmatter can be used to configure the metadata of documents. All
are optional. `none` is used when the option is unset. The following options are of them are optional. `none` is used when the option is unset. The
available: following options are available:
| Key | Type & Default | Description | | 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. | | `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. | | `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. | | `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 **Note**: you can specify the date in any format that can be parsed by
[`python-dateutil`](https://pypi.org/project/python-dateutil/). [`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. Welcome to my blog! Please find a list of my posts below.
``` ```
Setting `post: false` is necessary because, by default, all documents inside Setting `post: false` is necessary because, by default, all documents
`content/blog` are considered to be posts unless explicitly disabled in the inside `content/blog` are considered to be posts unless explicitly
frontmatter. We don't want the post list to list _itself_ as a post. 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: 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 Because `my-post` is inside the `blog` directory, `post: true` is implied.
you wanted to put it somewhere outside `blog`, you would need to set 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. `post: true` for it to be included in the post list.
## Configuration ## Configuration
Zona is configured in YAML format. The configuration file is called `config.yml` Zona is configured in YAML format. The configuration file is called
and it **must** be located in the root of the project — in the same directory as `config.yml` and it **must** be located in the root of the project — in
`content` and `templates`. the same directory as `content` and `templates`.
Your configuration will be merged with the defaults. `zona init` also writes a Your configuration will be merged with the defaults. `zona init` also
copy of the default configuration to the correct location. If it exists, you'll writes a copy of the default configuration to the correct location. If it
be prompted before overwriting it. exists, you'll be prompted before overwriting it.
**Note:** Currently, not every configuration value is actually used. Only the **Note:** Currently, not every configuration value is actually used. Only
useful settings are listed here. the useful settings are listed here.
Please see the default configuration: Please see the default configuration:
@ -365,10 +395,10 @@ blog:
### Sitemap ### Sitemap
You can define a sitemap in the configuration file. This is a list of links that You can define a sitemap in the configuration file. This is a list of
will be rendered at the top of every page. The `sitemap` is a dictionary of links that will be rendered at the top of every page. The `sitemap` is a
`string` to `string` pairs, where each key is the displayed text of the link, dictionary of `string` to `string` pairs, where each key is the displayed
and the value if the `href`. Consider this example: text of the link, and the value if the `href`. Consider this example:
```yaml ```yaml
sitemap: sitemap:
@ -380,17 +410,17 @@ sitemap:
### Ignore List ### Ignore List
You can set a list of glob patterns in the [configuration](#configuration) that You can set a list of glob patterns in the [configuration](#configuration)
should be ignored by zona. This is useful because zona makes a copy of _every_ that should be ignored by zona. This is useful because zona makes a copy
file it encounters inside the `content` directory, regardless of its type. The of _every_ file it encounters inside the `content` directory, regardless
paths must be relative to the `content` directory. of its type. The paths must be relative to the `content` directory.
### Drafts ### Drafts
zona allows you to begin writing content without including it in the final build zona allows you to begin writing content without including it in the final
output. If you set `draft: true` in a page's frontmatter, it will be marked as a build output. If you set `draft: true` in a page's frontmatter, it will be
draft. Drafts are completely excluded from `zona build` and `zona serve` unless marked as a draft. Drafts are completely excluded from `zona build` and
the `--draft` flag is specified. `zona serve` unless the `--draft` flag is specified.
[Ashen]: https://sr.ht/~ficd/ashen [Ashen]: https://sr.ht/~ficd/ashen
[Pygments]: https://pygments.org/ [Pygments]: https://pygments.org/