From 8c806d165452dc5c88d1ff62aa0823b561e54cb6 Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Sat, 31 Jan 2026 15:58:37 -0700 Subject: [PATCH] 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 --- docs/content/_index.md | 35 +++ docs/content/configuration.md | 78 +++++++ docs/content/features/_index.md | 8 + docs/content/features/sections.md | 71 ++++++ docs/content/features/syntax-highlighting.md | 87 +++++++ docs/content/features/templates.md | 91 ++++++++ docs/content/getting-started.md | 77 +++++++ docs/site.toml | 9 + docs/static/style.css | 229 +++++++++++++++++++ docs/templates/base.html | 37 +++ docs/templates/content/default.html | 9 + docs/templates/homepage.html | 9 + docs/templates/page.html | 9 + docs/templates/section/default.html | 18 ++ docs/templates/section/features.html | 18 ++ src/error.rs | 12 +- src/main.rs | 6 + src/template_engine.rs | 6 +- 18 files changed, 801 insertions(+), 8 deletions(-) create mode 100644 docs/content/_index.md create mode 100644 docs/content/configuration.md create mode 100644 docs/content/features/_index.md create mode 100644 docs/content/features/sections.md create mode 100644 docs/content/features/syntax-highlighting.md create mode 100644 docs/content/features/templates.md create mode 100644 docs/content/getting-started.md create mode 100644 docs/site.toml create mode 100644 docs/static/style.css create mode 100644 docs/templates/base.html create mode 100644 docs/templates/content/default.html create mode 100644 docs/templates/homepage.html create mode 100644 docs/templates/page.html create mode 100644 docs/templates/section/default.html create mode 100644 docs/templates/section/features.html diff --git a/docs/content/_index.md b/docs/content/_index.md new file mode 100644 index 0000000..66a9204 --- /dev/null +++ b/docs/content/_index.md @@ -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. diff --git a/docs/content/configuration.md b/docs/content/configuration.md new file mode 100644 index 0000000..d05fa18 --- /dev/null +++ b/docs/content/configuration.md @@ -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. diff --git a/docs/content/features/_index.md b/docs/content/features/_index.md new file mode 100644 index 0000000..bd5234f --- /dev/null +++ b/docs/content/features/_index.md @@ -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. diff --git a/docs/content/features/sections.md b/docs/content/features/sections.md new file mode 100644 index 0000000..0b6c72b --- /dev/null +++ b/docs/content/features/sections.md @@ -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. diff --git a/docs/content/features/syntax-highlighting.md b/docs/content/features/syntax-highlighting.md new file mode 100644 index 0000000..6dcc821 --- /dev/null +++ b/docs/content/features/syntax-highlighting.md @@ -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 `` blocks without highlighting. diff --git a/docs/content/features/templates.md b/docs/content/features/templates.md new file mode 100644 index 0000000..2ff3b1b --- /dev/null +++ b/docs/content/features/templates.md @@ -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 %} +
+

{{ page.title }}

+ {{ content | safe }} +
+{% 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. diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md new file mode 100644 index 0000000..b41ee96 --- /dev/null +++ b/docs/content/getting-started.md @@ -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) diff --git a/docs/site.toml b/docs/site.toml new file mode 100644 index 0000000..ef6ebfe --- /dev/null +++ b/docs/site.toml @@ -0,0 +1,9 @@ +author = "Sukr Contributors" +base_url = "https://sukr.io" +title = "Sukr Documentation" + +[paths] +content = "content" +output = "public" +static = "static" +templates = "templates" diff --git a/docs/static/style.css b/docs/static/style.css new file mode 100644 index 0000000..f233f6c --- /dev/null +++ b/docs/static/style.css @@ -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; + } +} diff --git a/docs/templates/base.html b/docs/templates/base.html new file mode 100644 index 0000000..6936083 --- /dev/null +++ b/docs/templates/base.html @@ -0,0 +1,37 @@ + + + + + + {{ title }} | {{ config.title }} + + + + + + +
{% block content %}{% endblock content %}
+ + diff --git a/docs/templates/content/default.html b/docs/templates/content/default.html new file mode 100644 index 0000000..d1f7ea6 --- /dev/null +++ b/docs/templates/content/default.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} {% block content %} +
+

{{ page.title }}

+ {% if page.description %} +

{{ page.description }}

+ {% endif %} +
{{ content | safe }}
+
+{% endblock content %} diff --git a/docs/templates/homepage.html b/docs/templates/homepage.html new file mode 100644 index 0000000..ef9277c --- /dev/null +++ b/docs/templates/homepage.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} {% block content %} +
+

{{ page.title }}

+ {% if page.description %} +

{{ page.description }}

+ {% endif %} +
{{ content | safe }}
+
+{% endblock content %} diff --git a/docs/templates/page.html b/docs/templates/page.html new file mode 100644 index 0000000..8eb1b9a --- /dev/null +++ b/docs/templates/page.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} {% block content %} +
+

{{ page.title }}

+ {% if page.description %} +

{{ page.description }}

+ {% endif %} +
{{ content | safe }}
+
+{% endblock content %} diff --git a/docs/templates/section/default.html b/docs/templates/section/default.html new file mode 100644 index 0000000..2d9753b --- /dev/null +++ b/docs/templates/section/default.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} {% block content %} + +{% endblock content %} diff --git a/docs/templates/section/features.html b/docs/templates/section/features.html new file mode 100644 index 0000000..2d9753b --- /dev/null +++ b/docs/templates/section/features.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} {% block content %} + +{% endblock content %} diff --git a/src/error.rs b/src/error.rs index c1c6537..a7cbbc6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,12 +42,16 @@ pub enum Error { Config { path: PathBuf, message: String }, /// Failed to load templates. - #[error("failed to load templates: {message}")] - TemplateLoad { message: String }, + #[error("failed to load templates: {0}")] + TemplateLoad(#[source] tera::Error), /// Failed to render template. - #[error("failed to render template '{template}': {message}")] - TemplateRender { template: String, message: String }, + #[error("failed to render template '{template}'")] + TemplateRender { + template: String, + #[source] + source: tera::Error, + }, } /// Result type alias for compiler operations. diff --git a/src/main.rs b/src/main.rs index 243de52..277f41a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,12 @@ fn main() { Ok(Some(config_path)) => { if let Err(e) = run(&config_path) { eprintln!("error: {e}"); + // Print full error chain + let mut source = std::error::Error::source(&e); + while let Some(cause) = source { + eprintln!(" caused by: {cause}"); + source = std::error::Error::source(cause); + } std::process::exit(1); } } diff --git a/src/template_engine.rs b/src/template_engine.rs index 2e9df81..499f2e8 100644 --- a/src/template_engine.rs +++ b/src/template_engine.rs @@ -18,9 +18,7 @@ impl TemplateEngine { /// Load templates from a directory (glob pattern: `templates/**/*`). pub fn new(template_dir: &Path) -> Result { let pattern = template_dir.join("**/*").display().to_string(); - let tera = Tera::new(&pattern).map_err(|e| Error::TemplateLoad { - message: e.to_string(), - })?; + let tera = Tera::new(&pattern).map_err(Error::TemplateLoad)?; Ok(Self { tera }) } @@ -30,7 +28,7 @@ impl TemplateEngine { .render(template_name, context) .map_err(|e| Error::TemplateRender { template: template_name.to_string(), - message: e.to_string(), + source: e, }) }