feat(docs): create sukr documentation site

Self-documenting docs site built with sukr itself (dogfooding):
- docs/site.toml with sukr.io base URL
- docs-specific templates with sidebar navigation
- Dark theme CSS, responsive layout
- Documentation: getting-started, configuration, features

Also: improved error chaining for better template debugging
This commit is contained in:
Timothy DeHerrera
2026-01-31 15:58:37 -07:00
parent 0516bfc600
commit 8c806d1654
18 changed files with 801 additions and 8 deletions

35
docs/content/_index.md Normal file
View File

@@ -0,0 +1,35 @@
---
title: sukr
description: Minimal static site compiler — suckless, Rust, zero JS
---
# Welcome to sukr
**sukr** transforms Markdown into high-performance static HTML. No bloated runtimes, no client-side JavaScript, just clean output.
## Why sukr?
- **Fast builds** — Single Rust binary, parallel processing
- **Zero JS** — Syntax highlighting at build time via Tree-sitter
- **Flexible templates** — Runtime Tera templates, no recompilation
- **Monorepo-ready** — Multiple sites via `-c` config flag
## Quick Start
```bash
# Install
cargo install sukr
# Create site structure
mkdir -p content templates static
echo 'title = "My Site"' > site.toml
echo 'author = "Me"' >> site.toml
echo 'base_url = "https://example.com"' >> site.toml
# Build
sukr
```
## Documentation
Browse the sidebar for detailed documentation on all features.

View File

@@ -0,0 +1,78 @@
---
title: Configuration
description: Complete reference for site.toml configuration
weight: 2
---
# Configuration
sukr is configured via `site.toml`. All settings have sensible defaults.
## Basic Configuration
```toml
title = "My Site"
author = "Your Name"
base_url = "https://example.com"
```
| Field | Required | Description |
| ---------- | -------- | -------------------------------- |
| `title` | Yes | Site title (used in page titles) |
| `author` | Yes | Author name (used in feeds) |
| `base_url` | Yes | Canonical URL for the site |
## Path Configuration
All paths are optional. Default values shown:
```toml
[paths]
content = "content" # Markdown source files
output = "public" # Generated HTML output
static = "static" # Static assets (copied as-is)
templates = "templates" # Tera template files
```
Paths are resolved **relative to the config file location**. This enables monorepo setups:
```bash
# Build site from subdirectory
sukr -c sites/blog/site.toml
# Paths resolve relative to sites/blog/
```
## CLI Options
```bash
sukr # Use ./site.toml
sukr -c path/to/site.toml # Custom config
sukr --config path/to/site.toml
sukr -h, --help # Show help
```
## Frontmatter
Each Markdown file can have YAML frontmatter:
```yaml
---
title: Page Title
description: Optional description
date: 2024-01-15 # For blog posts
weight: 10 # Sort order (lower = first)
nav_label: Short Name # Override nav display
section_type: blog # Override section template
template: custom # Override page template
---
```
### Section Types
The `section_type` field determines which template is used for section indexes:
- `blog``templates/section/blog.html`
- `projects``templates/section/projects.html`
- _(any other)_ → `templates/section/default.html`
If not specified, sukr uses the directory name as the section type.

View File

@@ -0,0 +1,8 @@
---
title: Features
description: Explore sukr's capabilities
section_type: features
weight: 3
---
sukr provides a focused set of features for building fast, minimal static sites.

View File

@@ -0,0 +1,71 @@
---
title: Sections
description: Automatic section discovery and processing
weight: 2
---
# Sections
sukr automatically discovers sections from your content directory structure.
## What is a Section?
A section is any directory under `content/` that contains an `_index.md` file:
```
content/
├── _index.md # Homepage (not a section)
├── about.md # Standalone page
├── blog/ # ← This is a section
│ ├── _index.md # Section index
│ └── my-post.md # Section content
└── projects/ # ← This is also a section
├── _index.md
└── project-a.md
```
## Section Discovery
sukr automatically:
1. Scans `content/` for directories with `_index.md`
2. Collects all `.md` files in that directory (excluding `_index.md`)
3. Renders the section index template with the items
4. Renders individual content pages (for blog-type sections)
## Section Types
The section type determines which template is used. It's resolved in order:
1. **Frontmatter override**: `section_type: blog` in `_index.md`
2. **Directory name**: `content/blog/` → type `blog`
### Built-in Section Types
| Type | Behavior |
| ---------- | ------------------------------------------------------ |
| `blog` | Sorts by date (newest first), renders individual posts |
| `projects` | Sorts by weight, card-style listing |
| _(other)_ | Sorts by weight, uses default template |
## Section Frontmatter
In `_index.md`:
```yaml
---
title: My Blog
description: Thoughts and tutorials
section_type: blog # Optional, defaults to directory name
weight: 1 # Nav order
---
```
## Adding a New Section
1. Create directory: `content/recipes/`
2. Create index: `content/recipes/_index.md`
3. Add content: `content/recipes/pasta.md`
4. Optionally create template: `templates/section/recipes.html`
That's it. sukr handles the rest.

View File

@@ -0,0 +1,87 @@
---
title: Syntax Highlighting
description: Build-time code highlighting with Tree-sitter
weight: 3
---
# Syntax Highlighting
sukr highlights code blocks at build time using Tree-sitter. No client-side JavaScript required.
## Usage
Use fenced code blocks with a language identifier:
````markdown
```rust
fn main() {
println!("Hello, world!");
}
```
````
## Supported Languages
| Language | Identifier |
| ---------- | --------------------- |
| Rust | `rust`, `rs` |
| Python | `python`, `py` |
| JavaScript | `javascript`, `js` |
| TypeScript | `typescript`, `ts` |
| Go | `go`, `golang` |
| Bash | `bash`, `sh`, `shell` |
| Nix | `nix` |
| TOML | `toml` |
| YAML | `yaml`, `yml` |
| JSON | `json` |
| HTML | `html` |
| CSS | `css` |
| Markdown | `markdown`, `md` |
## How It Works
1. During Markdown parsing, code blocks are intercepted
2. Tree-sitter parses the code and generates a syntax tree
3. Spans are generated with semantic CSS classes
4. All work happens at build time
## Styling
Highlighted code uses semantic CSS classes:
```css
.keyword {
color: #ff79c6;
}
.string {
color: #f1fa8c;
}
.function {
color: #50fa7b;
}
.comment {
color: #6272a4;
}
.number {
color: #bd93f9;
}
```
The exact classes depend on the language grammar.
## Nix Language Support
sukr includes full Nix highlighting with injection support. Bash code inside `buildPhase` and similar attributes is highlighted correctly:
```nix
stdenv.mkDerivation {
buildPhase = ''
echo "Building..."
make -j$NIX_BUILD_CORES
'';
}
```
## Fallback
Unknown languages fall back to plain `<code>` blocks without highlighting.

View File

@@ -0,0 +1,91 @@
---
title: Tera Templates
description: Customizable templates without recompilation
weight: 1
---
# Tera Templates
sukr uses [Tera](https://tera.netlify.app/), a Jinja2-like templating engine. Templates are loaded at runtime, so you can modify them without recompiling sukr.
## Template Directory Structure
```
templates/
├── base.html # Shared layout (required)
├── page.html # Standalone pages
├── homepage.html # Site homepage
├── section/
│ ├── default.html # Fallback section index
│ ├── blog.html # Blog section index
│ └── projects.html # Projects section index
└── content/
├── default.html # Fallback content page
└── post.html # Blog post
```
## Template Inheritance
All templates extend `base.html`:
```html
{% extends "base.html" %} {% block content %}
<article>
<h1>{{ page.title }}</h1>
{{ content | safe }}
</article>
{% endblock content %}
```
## Available Context Variables
### All Templates
| Variable | Description |
| --------------- | ------------------------------- |
| `config.title` | Site title |
| `config.author` | Site author |
| `nav` | Navigation items |
| `page_path` | Current page path |
| `prefix` | Relative path prefix for assets |
| `base_url` | Canonical base URL |
| `title` | Current page title |
### Page Templates
| Variable | Description |
| ------------------ | --------------------- |
| `page.title` | Page title |
| `page.description` | Page description |
| `content` | Rendered HTML content |
### Section Templates
| Variable | Description |
| --------------------- | --------------------------------- |
| `section.title` | Section title |
| `section.description` | Section description |
| `items` | Array of content items in section |
### Content Item Fields (in `items`)
| Variable | Description |
| ------------------ | ------------------- |
| `item.title` | Content title |
| `item.description` | Content description |
| `item.date` | Publication date |
| `item.path` | URL path |
| `item.slug` | URL slug |
## Template Override
Set `template` in frontmatter to use a custom template:
```yaml
---
title: Special Page
template: special
---
```
This uses `templates/page/special.html` instead of the default.

View File

@@ -0,0 +1,77 @@
---
title: Getting Started
description: Install sukr and build your first site
weight: 1
---
# Getting Started
This guide walks you through installing sukr and creating your first static site.
## Installation
### From source (recommended)
```bash
git clone https://github.com/nrdxp/sukr
cd sukr
cargo install --path .
```
### With Nix
```bash
nix build github:nrdxp/sukr
./result/bin/sukr --help
```
## Create Your First Site
### 1. Create directory structure
```bash
mkdir my-site && cd my-site
mkdir -p content templates static
```
### 2. Create configuration
Create `site.toml`:
```toml
title = "My Site"
author = "Your Name"
base_url = "https://example.com"
```
### 3. Create homepage
Create `content/_index.md`:
```markdown
---
title: Welcome
description: My awesome site
---
# Hello, World!
This is my site built with sukr.
```
### 4. Create templates
Copy the default templates from the sukr repository, or create your own Tera templates.
### 5. Build
```bash
sukr
```
Your site is now in `public/`.
## Next Steps
- Learn about [Configuration](configuration.html)
- Explore [Features](features/index.html)

9
docs/site.toml Normal file
View File

@@ -0,0 +1,9 @@
author = "Sukr Contributors"
base_url = "https://sukr.io"
title = "Sukr Documentation"
[paths]
content = "content"
output = "public"
static = "static"
templates = "templates"

229
docs/static/style.css vendored Normal file
View File

@@ -0,0 +1,229 @@
/* sukr docs — clean documentation theme */
:root {
--bg: #0d1117;
--bg-sidebar: #161b22;
--fg: #c9d1d9;
--fg-muted: #8b949e;
--accent: #58a6ff;
--accent-hover: #79b8ff;
--border: #30363d;
--code-bg: #1f2428;
--success: #3fb950;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
line-height: 1.6;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--fg);
display: grid;
grid-template-columns: 260px 1fr;
min-height: 100vh;
}
/* Sidebar */
.sidebar {
background: var(--bg-sidebar);
border-right: 1px solid var(--border);
padding: 2rem 1.5rem;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
}
.sidebar header {
margin-bottom: 2rem;
}
.sidebar .logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent);
text-decoration: none;
display: block;
}
.sidebar .tagline {
display: block;
font-size: 0.875rem;
color: var(--fg-muted);
margin-top: 0.25rem;
}
.sidebar nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.sidebar nav a {
color: var(--fg);
text-decoration: none;
padding: 0.5rem 0.75rem;
border-radius: 6px;
transition: background 0.15s, color 0.15s;
}
.sidebar nav a:hover {
background: var(--border);
}
.sidebar nav a.active {
background: var(--accent);
color: var(--bg);
}
/* Main content */
main {
padding: 3rem 4rem;
max-width: 900px;
}
h1 {
font-size: 2.25rem;
margin-bottom: 1rem;
color: var(--fg);
}
h2 {
font-size: 1.5rem;
margin: 2rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
h3 {
font-size: 1.25rem;
margin: 1.5rem 0 0.75rem;
}
p {
margin-bottom: 1rem;
}
.lead {
font-size: 1.125rem;
color: var(--fg-muted);
margin-bottom: 2rem;
}
a {
color: var(--accent);
}
a:hover {
color: var(--accent-hover);
}
/* Code */
code {
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, monospace;
font-size: 0.875em;
background: var(--code-bg);
padding: 0.2em 0.4em;
border-radius: 4px;
}
pre {
background: var(--code-bg);
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
margin: 1rem 0;
border: 1px solid var(--border);
}
pre code {
background: none;
padding: 0;
}
/* Section navigation */
.section-nav {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 1.5rem;
}
.section-link {
display: block;
padding: 1rem;
background: var(--bg-sidebar);
border: 1px solid var(--border);
border-radius: 8px;
text-decoration: none;
transition: border-color 0.15s;
}
.section-link:hover {
border-color: var(--accent);
}
.section-link strong {
display: block;
color: var(--fg);
margin-bottom: 0.25rem;
}
.section-link span {
font-size: 0.875rem;
color: var(--fg-muted);
}
/* Lists */
ul, ol {
margin: 1rem 0;
padding-left: 1.5rem;
}
li {
margin-bottom: 0.5rem;
}
/* Tables */
table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
th, td {
padding: 0.75rem;
text-align: left;
border: 1px solid var(--border);
}
th {
background: var(--bg-sidebar);
}
/* Responsive */
@media (max-width: 768px) {
body {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
height: auto;
border-right: none;
border-bottom: 1px solid var(--border);
}
main {
padding: 2rem 1.5rem;
}
}

37
docs/templates/base.html vendored Normal file
View File

@@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ title }} | {{ config.title }}</title>
<link rel="canonical" href="{{ base_url }}{{ page_path }}" />
<link rel="stylesheet" href="{{ prefix }}/style.css" />
</head>
<body>
<aside class="sidebar">
<header>
<a href="{{ prefix }}/index.html" class="logo">sukr</a>
<span class="tagline">suckless static sites</span>
</header>
<nav>
{% for item in nav %}
<a
href="{{ prefix }}{{ item.path }}"
{%
if
page_path=""
="item.path"
%}
class="active"
{%
endif
%}
>{{ item.label }}</a
>
{% endfor %}
</nav>
</aside>
<main>{% block content %}{% endblock content %}</main>
</body>
</html>

9
docs/templates/content/default.html vendored Normal file
View File

@@ -0,0 +1,9 @@
{% extends "base.html" %} {% block content %}
<article class="doc-page">
<h1>{{ page.title }}</h1>
{% if page.description %}
<p class="lead">{{ page.description }}</p>
{% endif %}
<section class="content">{{ content | safe }}</section>
</article>
{% endblock content %}

9
docs/templates/homepage.html vendored Normal file
View File

@@ -0,0 +1,9 @@
{% extends "base.html" %} {% block content %}
<article class="homepage">
<h1>{{ page.title }}</h1>
{% if page.description %}
<p class="lead">{{ page.description }}</p>
{% endif %}
<section class="content">{{ content | safe }}</section>
</article>
{% endblock content %}

9
docs/templates/page.html vendored Normal file
View File

@@ -0,0 +1,9 @@
{% extends "base.html" %} {% block content %}
<article class="page">
<h1>{{ page.title }}</h1>
{% if page.description %}
<p class="lead">{{ page.description }}</p>
{% endif %}
<section class="content">{{ content | safe }}</section>
</article>
{% endblock content %}

18
docs/templates/section/default.html vendored Normal file
View File

@@ -0,0 +1,18 @@
{% extends "base.html" %} {% block content %}
<article class="section-index">
<h1>{{ section.title }}</h1>
{% if section.description %}
<p class="lead">{{ section.description }}</p>
{% endif %}
<nav class="section-nav">
{% for item in items %}
<a href="{{ prefix }}{{ item.path }}" class="section-link">
<strong>{{ item.frontmatter.title }}</strong>
{% if item.frontmatter.description %}
<span>{{ item.frontmatter.description }}</span>
{% endif %}
</a>
{% endfor %}
</nav>
</article>
{% endblock content %}

18
docs/templates/section/features.html vendored Normal file
View File

@@ -0,0 +1,18 @@
{% extends "base.html" %} {% block content %}
<article class="section-index">
<h1>{{ section.title }}</h1>
{% if section.description %}
<p class="lead">{{ section.description }}</p>
{% endif %}
<nav class="section-nav">
{% for item in items %}
<a href="{{ prefix }}{{ item.path }}" class="section-link">
<strong>{{ item.frontmatter.title }}</strong>
{% if item.frontmatter.description %}
<span>{{ item.frontmatter.description }}</span>
{% endif %}
</a>
{% endfor %}
</nav>
</article>
{% endblock content %}