[![pipeline status](https://gitlab.com/eguiraud/kobold-ssg/badges/main/pipeline.svg)](https://gitlab.com/eguiraud/kobold-ssg/-/commits/main)
[![crates.io](https://img.shields.io/crates/v/kobold-ssg.svg)](https://crates.io/crates/kobold-ssg)

# Kobold

Kobold is a static site generator with a friendly CLI.

```
Usage: kobold <COMMAND>

Commands:
  init   Create a new Kobold project in the specified directory
  build  Generate website from the Kobold project at the specified directory
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version
```

## Installation

You can install Kobold via [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html):

```bash
cargo install kobold-ssg
```

As long as Cargo's bin directory (`$HOME/.cargo/bin` by default) is in your `PATH`, you are set.

Alternatively you can clone [the repository](https://gitlab.com/eguiraud/kobold-ssg) and then use:

```bash
cargo run --release -- [kobold commands, options]
```

## Quick start

Create a new Kobold project:

```bash
kobold init koboldsite.com
```

This creates the `koboldsite.com/` directory and initializes it as a git repository with the directory structure that Kobold requires:

```
koboldsite.com/
├── content/
│   └── index.md
├── layouts/
│   └── default.html
├── macros/
│   └── ...
└── css/
    └── ...
```

The project can contain more sub-directories with other names, but sub-directories `site/` and `site.old/` are reserved for usage by Kobold.

If you have used any other static site generator, you can probably guess how Kobold works based on this directory structure.

The `index.md` file is the main page of the website, and starts with the following contents:

```yaml
---
title: Home page
---

# Hello Kobold!
```

You can already generate a website:

```bash
kobold build koboldsite.com
```

That command creates a `site/` sub-directory in the `koboldsite.com/` directory with the generated website.
You can visit the website with your favorite web browser:

```bash
firefox koboldsite.com/site/index.html
```

or start an HTTP server, e.g.:

```bash
cd koboldsite.com/site && python -m http.server
```

You can then work on your website by adding more files under `content/`.

Files with the `.md` extension are processed to:

1. evaluate [macros](#generating-content-with-macros) (if any)
2. convert Markdown into HTML
3. apply the desired page layout (by default, `default.html`)

All other files under `content/` are copied into `site/` as they are, with the same directory structure. The `css/` directory is also copied into `site/` as-is.

Custom page layouts can be added to the `layouts` directory: they are [Tera](https://tera.netlify.app/docs) templates and they must have the `.html` extension.
Placeholders such as `{{title}}` and `{{body}}` in the layouts are substituted with appropriate values during the website generation (see [context variables](#tera-context-variables)).

To select a custom page layout add `layout: filename` to the page's [frontmatter](#page-frontmatter) (omitting the `.html` extension).


## Page frontmatter

Markdown content pages (that is, text files with the `.md` extension in the `content/` directory) may start with a YAML frontmatter.
This is a snippet of YAML enclosed between two lines that only contain `---`.
If present, it contains metadata that will inform the page processing.

The following keys in the frontmatter have a special meaning for Kobold:
- **`title`**: the page title
- **`layout`**: the layout to use for this page. In order to use layout `blog_post.html`, use `layout: blog_post` (omitting the extension)
- **`tags`**: an array of tags that will be attached to this page, e.g. `[c++, war-stories]`

Any other keys will be made available as [context variables](#tera-context-variables) during site generation.


## Generating content with macros

[Tera macros](https://tera.netlify.app/docs#macros) can be used to automate the generation of page content.
In a Kobold project they are files that live in the `macros/` directory (no sub-directories are allowed) and
can have any extension (e.g. `.md`, `.html` or `.tera`).

### Usage from Markdown content pages

Macros are the only Tera feature that can be used in content pages.
They are automatically made available under the `macros` namespace (no need to `{% import %}` them) and can be invoked as:

```tera
{{ macros::macroname(parameter="value") }}
```

Remember that Tera macros require the usage of keyword arguments.
The macro invocation is replaced by the output of its evaluation before the Markdown is converted to HTML.

### Usage from layouts

Differently from Markdown content pages, layouts are full-blown Tera templates,
so macros work as they usually do in Tera (and they are not automagically imported):

```tera
{% import '../macros/mymacrofile.html' as somemacros %}

{{ somemacros::macroname(parameter="value") }}
```

For both layouts and content pages, [context variables](#tera-context-variables) can be passed to macros as parameters.


## Tera context variables

The following variables are made available to the templates in `layouts/` as well as when
[evaluating macros](#generating-content-with-macros) in Markdown content pages:
- **`title`**: the page title as stated in the YAML frontmatter
- **`path`**: the page URL relative to the website root `/`
- **`tags`**: the tags attached to the page
- **`data`**: a hashmap containing any extra variables that were set in the page's frontmatter
- **`pages`**: a hashmap `title => {path, tags, layout, data}` that contains the corresponding info for _all_ the content pages of the site
- **`body`** (only in layouts, not in content pages): the page body, with macros already evaluated and converted to HTML

## Roadmap

- [X] human-friendly error messages that are prescriptive rather than descriptive whenever possible
- [X] support for programmatic generation of page content via [Tera macros](https://tera.netlify.app/docs#macros)
- [X] automatic syntax highlighting for code blocks
- [ ] automatic generation of a `/tags` page that categorizes website contents by tag
- [ ] nice autocompletion for the [fish shell](https://github.com/fish-shell/fish-shell)
- [ ] automatic generation of a search bar using [Stork](https://stork-search.net)
- [ ] make the body of pages selected via the `needs` key in the frontmatter available as part of the Tera context


## Why?

I wanted a personal website and I wanted to practice my Rust.


## Related work

Kobold was written from scratch, but it has many similarities to the more mature
[Zola](https://www.getzola.org) and [Cobalt](http://cobalt-org.github.io/docs/front).
Kobold lacks many of their features, but in exchange it tries to be so easy to use that you
can start working on your website without reading any docs, just playing with the `kobold` command.

I also appreciate [Hugo](https://gohugo.io)'s focus on speed and its no-nonsense workflow, and shortcodes make a lot of
sense so Kobold's macros try to replicate some of that usefulness.

Not a static site generator per se, but Sylvain Kerkour's
[Building a static site generator in 100 lines of Rust](https://kerkour.com/rust-static-site-generator)
was a great resource to get me started.


## Contributing

If you happen to try out Kobold, any feedback is more than welcome.

Contributions in the form of issues or patches are even more appreciated.
