Imported from rochacbruno/marmite (
AGENTS.md). Install upstream withnpx skills add rochacbruno/marmite. Copyright stays with the author.
Marmite Development Guide
This document is for AI agents and contributors working on the marmite codebase itself. For building sites with marmite, see the embedded skill (marmite --skill).
About
Marmite (Markdown Makes Sites) is a minimal, fast static site generator written in Rust. It takes a folder of Markdown files, combines them with Tera templates, and produces a static HTML website. Single binary, zero runtime dependencies, zero-config by default.
- Repository: https://github.com/rochacbruno/marmite
- Site: https://marmite.blog
Tech Stack
- Language: Rust
- Templating: Tera (Jinja2-like syntax)
- Markdown: Comrak (CommonMark + GFM extensions)
- CLI: Clap (derive macros)
- Syntax Highlighting: Arborium (tree-sitter based, build-time)
- Dev Server: tiny_http with live reload via tungstenite (WebSocket)
- Task Runner: Mask (tasks defined in
maskfile.md) - Image Processing: image crate with rayon for parallel resizing
- Embedded Assets: rust_embed (templates, static files, shortcodes, agent skills compiled into binary)
Project Structure
src/
main.rs Entry point, CLI command routing
cli.rs Clap argument definitions
config.rs Marmite struct (marmite.yaml deserialization)
content.rs Content struct, frontmatter parsing, slug generation
embedded.rs Embedded assets (templates, static, shortcodes, agent skills)
site.rs Core site generation logic (~2000 lines)
templates.rs Template initialization and theme setup
tera_functions.rs Custom Tera functions (url_for, group, get_posts, etc.)
tera_filter.rs Custom Tera filters (default_date_format, remove_draft)
shortcodes.rs Shortcode processing with regex pattern matching
parser.rs Markdown to HTML conversion with comrak
feed.rs RSS feed generation
gallery.rs Image gallery processing
highlight.rs Build-time syntax highlighting
image_provider.rs Automatic banner image download (picsum)
image_resize.rs Parallel image resizing with incremental builds
server.rs Built-in HTTP server with WebSocket live reload
theme_manager.rs Remote theme download and installation
re.rs Shared regex patterns
tests/ Unit tests (one file per module, calls code directly)
tests/ Integration tests (runs marmite as a subprocess via process::Command)
example/ Complete working example site - primary dev/test target
marmite.yaml Example configuration
content/ Markdown files (posts, pages, _ prefixed fragments)
templates/ Tera HTML templates
static/ CSS, JS, fonts, colorschemes
shortcodes/ Built-in shortcode definitions (Tera macros)
theme_template/ Default theme scaffold used by --start-theme
ai/llms.txt LLM-readable documentation index
.agents/ Embedded agent skill files (compiled into binary via rust_embed)
skills/marmite/
SKILL.md Main skill document with workflows
references/ Detailed reference files (config, CLI, templates, etc.)
Development Workflow
First-time setup
# Install mask task runner (if not already installed)
cargo install mask
# Install pre-commit hook (runs mask pedantic before every commit)
mask install_hook
# Build and serve the example site with full trace logging and live reload
mask serve
The site runs at http://localhost:8000 with auto-rebuild on file changes. Edit source in src/ or content in example/ and it rebuilds automatically.
Everyday commands
| Command | What it does |
|---|---|
mask serve |
Build and serve the example site with live reload and full trace logging |
mask watch |
Watch for changes, rebuild without serving |
mask fmt |
Format code with cargo fmt |
mask check |
Check formatting + run clippy |
mask test |
Run all tests (unit + integration) |
mask test_unit |
Run unit tests only (cargo test --bin marmite) |
mask test_integration |
Run integration tests only (cargo test --test '*') |
mask build |
Build release binary |
mask pedantic |
Run clippy with pedantic warnings |
mask install_hook |
Install pre-commit hook that runs mask pedantic |
mask fix |
Auto-apply clippy fixes |
mask pedantic_fix |
Auto-apply clippy pedantic fixes |
Running specific tests
# A specific unit test by name
cargo test --bin marmite test_embedded_agent_skills
# A specific integration test file
cargo test --test basic_functionality
# All tests with output
cargo test -- --nocapture
Serving with a theme
# Serve with the theme_template theme
mask serve_theme
# Serve the actual marmite.blog site locally
mask serve_site
Code Conventions
- Follow
rustfmtdefaults. Always runmask fmtbefore committing. - Follow
clippydefaults. Always runmask checkto verify. - Use
ResultandOptionfor error handling. Avoidunwrap()in non-test code. - Use
logmacros (info!,error!,warn!) for user-facing output. - Standard Rust naming:
snake_casefor functions/variables,PascalCasefor types. - CLI flags use
#[arg(long)]with doc comments that become help text. - Configuration fields go in the
Marmitestruct inconfig.rswith#[serde(default)]and a default function. - New embedded assets follow the
rust_embedderive pattern inembedded.rs.
Checklists
After any code change
mask fmtmask checkmask test
After implementing a new feature
- Format and lint:
mask fmtandmask check. - Add tests: Unit tests in
src/tests/for module logic, integration tests intests/for CLI/end-to-end behavior. - Update example content: If the feature adds or changes user-facing behavior, update or add the relevant docs in
example/content/*.md. Follow existing patterns - dated files for feature guides, undated files for reference pages. - Update the CLI docs: If CLI flags were added or changed, update
example/content/2024-11-26-marmite-command-line-interface.md- both the feature section and the--helpoutput block at the bottom. - Update llms.txt: If the feature is significant (new CLI flag, new config option, new content capability), add an entry to
example/ai/llms.txtunder the appropriate section. - Update the draft release notes: Find the latest release notes file with
stream: draftinexample/content/and add the feature. If no draft exists, create one following the patternYYYY-MM-DD-HH-MM-SS-marmite-X-Y-Z-release-notes.md. - Update embedded skill references: If the feature changes config options, CLI flags, frontmatter fields, template variables, or shortcodes, update the corresponding reference file in
.agents/skills/marmite/references/. These are compiled into the binary.
After implementing a bug fix
mask fmtandmask check.- Add or update tests that cover the fixed behavior.
- Update the draft release notes with a description of what was broken and how it's fixed.
Before opening a PR
mask fmtandmask check- must pass clean.mask test- all tests must pass.mask pedantic- runs clippy with pedantic warnings. Ask the user if they want to fix pedantic issues before proceeding. Do not auto-fix pedantic issues without confirmation.- Verify the example site builds and looks correct:
mask serve, then check in a browser. - Ensure no unrelated changes are staged.
Key Architectural Patterns
CLI command routing
main() parses args with cli::Cli::parse(), then run_cli() checks flags in order:
--skill(no input folder needed, print and exit)--skill-install/--skill-install-claude(defaults to CWD)- Resolve
input_folder(required for all remaining commands) --init-site,--new,--init-templates,--start-theme,--set-theme,--generate-config,--shortcodes,--show-urls- Default:
site::generate()with optional--serveand--watch
Each handler returns early after completing its task.
Content processing pipeline
- Walk the content directory (
walkdir) - For each
.mdfile, callContent::from_markdown():- Extract frontmatter (
frontmatter_gen- supports YAML/TOML/JSON) - Determine post vs page (date presence)
- Detect stream from frontmatter or filename prefix
- Generate slug from frontmatter, title, or filename
- Convert markdown to HTML via comrak
- Process shortcodes if enabled
- Extract frontmatter (
- Build taxonomy indexes (tags, authors, archive, streams, series)
- Resolve backlinks and related content
- Render Tera templates with content and site data
- Write HTML output, copy static/media, resize images
Embedded assets pattern
Templates, static files, shortcodes, and agent skills are compiled into the binary via rust_embed:
#[derive(Embed, Debug)]
#[folder = "$CARGO_MANIFEST_DIR/path/to/folder/"]
pub struct MyAssets;
pub static EMBEDDED_MY_ASSETS: LazyLock<Vec<(String, Vec<u8>)>> = LazyLock::new(|| {
let mut files: Vec<(String, Vec<u8>)> = Vec::new();
for name in MyAssets::iter() {
let file = MyAssets::get(name.as_ref())
.expect("Failed to get embedded asset - this is a build-time error");
files.push((name.clone().to_string(), file.data.clone().to_vec()));
}
files
});
Adding a new config field
- Add the field to
Marmitestruct inconfig.rswith#[serde(default)](and a default function if the default is not the type'sDefault). - Add a CLI override flag in the
Configurationstruct incli.rs. - Add the override mapping in
Marmite::override_from_cli_args()inconfig.rs. - Use the field in the relevant module.
- Update
example/marmite.yamlwith a commented-out example. - Update
.agents/skills/marmite/references/config-reference.md.
Adding a new CLI command
- Add the flag to
Clistruct incli.rswith#[arg(long)]. - Add the handler in
run_cli()inmain.rsat the appropriate position in the chain. - If the command doesn't need
input_folder, handle it before theinput_folderresolution. - Update
determine_verbosity()if the command should auto-bump verbosity. - Update the CLI docs in
example/content/2024-11-26-marmite-command-line-interface.md.
Adding a new template function
- Implement the function struct in
tera_functions.rs(implementtera::Function). - Register it in
site.rswhere other functions are registered. - Update
.agents/skills/marmite/references/tera-templates.md.
Version Management
# Bump to release version
mask bumpversion 0.3.2
# Tag and push
mask pushtag 0.3.2
# Or do both
mask publish 0.3.2
# Then bump to next dev version
mask bumpversion 0.3.3-dev
Code Coverage
# Generate HTML coverage report (requires cargo-llvm-cov)
mask coverage_llvm
# Generate cobertura.xml (requires cargo-tarpaulin on nightly)
mask coverage
Python Package
Marmite is also published on PyPI via maturin:
mask build_python # Build wheel
mask python_dev_install # Install in dev mode