Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(news): New article and small docs tweaks

+78 -29
+1
Cargo.lock
··· 2611 2611 name = "maudit-website" 2612 2612 version = "0.1.0" 2613 2613 dependencies = [ 2614 + "chrono", 2614 2615 "maud", 2615 2616 "maudit", 2616 2617 "serde",
+1
website/Cargo.toml
··· 8 8 maudit = { workspace = true } 9 9 maud = { workspace = true } 10 10 serde = { workspace = true } 11 + chrono = {version = "0.4.42", features = ["serde"]}
+9 -2
website/assets/prin.css
··· 149 149 150 150 .prose code, 151 151 .intro-code code { 152 - font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, 153 - Consolas, "DejaVu Sans Mono", monospace; 152 + font-family: 153 + ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, 154 + "DejaVu Sans Mono", monospace; 154 155 font-size: 0.888889em; 155 156 } 156 157 ··· 204 205 border-radius: 0; 205 206 } 206 207 } 208 + 209 + .prose hr { 210 + border-color: var(--color-borders); 211 + margin-top: 2em; 212 + margin-bottom: 2em; 213 + }
+5 -5
website/content/docs/templating.md
··· 4 4 section: "core-concepts" 5 5 --- 6 6 7 - In general, if a library can return a String, you can use it to generate pages with Maudit. 7 + In general, if a library can return a `String` or a `Vec<u8>`, you can use it to generate pages with Maudit. 8 8 9 9 Through crate features, Maudit includes built-in helper methods and traits implementation for popular templating libraries. 10 10 ··· 28 28 } 29 29 ``` 30 30 31 - Maudit implements the `Render` trait for assets, such as scripts, styles, and images, allowing one to use them directly in Maud templates. 31 + Maudit implements the `Render` trait for scripts and styles, allowing one to use them directly in Maud templates. 32 32 33 33 ```rs 34 34 use maud::{html, Markup}; ··· 39 39 40 40 impl Route for Index { 41 41 fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 42 - let logo = ctx.add_image("./logo.png"); 42 + let style = ctx.add_style("style.css"); 43 43 44 44 html! { 45 - (logo) // Will generate <img src="IMAGE_PATH" width="IMAGE_WIDTH" height="IMAGE_HEIGHT" loading="lazy" decoding="async" /> 45 + (style) // Will render to a <link> tag for the CSS file 46 46 } 47 47 } 48 48 } ··· 55 55 maud = "0.27" 56 56 ``` 57 57 58 - The `maud` feature is enabled by default. If you have disabled default features, enable it manually: 58 + The `maud` feature is enabled by default. If you have disabled default features, you can enable it manually: 59 59 60 60 ```toml 61 61 maudit = { version = "0.6", features = ["maud"] }
+1 -1
website/content/news/maudit-compile-time.md
··· 71 71 72 72 This is my CMS at home, don't judge me. More seriously, you could imagine loading pages from an actual CMS, a database, a `.json` file etc. And thus, combined with runtime templating, be able to add an infinite amount of pages without ever recompiling. 73 73 74 - Maudit is a library, not a framework. Make it work for you! 74 + The posibilities are infinite here, perhaps your website will never need recompilation again, who knows.
+42
website/content/news/maudit-library.md
··· 1 + --- 2 + title: "The court's library, not its king" 3 + description: "Maudit is a library, not a framework" 4 + author: The Maudit Team 5 + date: 2025-10-09 6 + --- 7 + 8 + The average static site generator (SSG) works like this: 9 + 10 + - You install some sort of package or binary in your project (ex: `astro`, `@11ty/eleventy`, `gatsby`, etc) or globally (ex: `hugo`, `zola`) 11 + - You project may contain some sort of special folders, `src/pages`, `_includes`, etc. Or at least, has a convention for how to define your pages (which might just be a bunch of `.md` files or configuration based, that's ok) 12 + - Oftentimes, you'll have a config file (`gatsby.config.js`, `astro.config.js`, `hugo.toml` etc) to define some settings. 13 + 14 + Then, to build your website you'll run a `build` command, running an internal build pipeline of the framework, transforming your pages, configuration etc, into nice `.html` files. Put more bluntly, you provide things to the framework, it does some things with it and so it goes. Great, nice. 15 + 16 + Maudit instead provides an alternative model: **You call Maudit, it does not call you.** A Maudit project is a Rust project, you generate your website by running your normal Rust project's generated binary that uses Maudit as a library and call its methods. 17 + 18 + ## The Mantle is Thine 19 + 20 + Pages in Maudit projects are normal Rust structs, you can import them in other files, provide them properties, implement methods on them etc. To get the HTML of a single page in SSG frameworks is sometimes impossible, but in Maudit, you can import the page and call [`.build()`](https://docs.rs/maudit/latest/maudit/route/trait.FullRoute.html#method.build), it straight up just works! 21 + 22 + This philosophy applies to all of Maudit, for instance to render remote Markdown using Maudit's Markdown pipeline (providing syntax highlighting, components, shortcodes, etc), you import [`render_markdown`](https://docs.rs/maudit/latest/maudit/content/markdown/fn.render_markdown.html) and pass it some content and options. The code on the homepage is highlighted using [`highlight_code`](https://docs.rs/maudit/latest/maudit/content/fn.highlight_code.html), which is the exact same function that `render_markdown` will use for highlighting as well, and so on. 23 + 24 + It is intended for it to be possible to build your own static website generators based on all these APIs, [we even have on a guide on how to do it!](https://maudit.org/docs/library/), or if you're 99% of people, the [`coronate()`](https://docs.rs/maudit/latest/maudit/fn.coronate.html) function (the ["entrypoint"](https://maudit.org/docs/entrypoint/)) act as both the reference implementation of how to build pages, bundle assets, process images etc and also as the function the average Maudit project would call. 25 + 26 + ## For What Quest We Stand Here 27 + 28 + These needs might seem niche, but it was actually born out of real pain points we've hit in current offerings. Most notably, for our own projects we often ran into the need to have access to the structured content the framework provide, but outside of the framework's blessed paths which proved to be cumbersome or sometimes totally impossible. 29 + 30 + In Maudit, it is totally possible to use [content sources](https://maudit.org/docs/content/) outside of pages or outside the site's generation totally, it's cool! (we think!) 31 + 32 + ## Tis but a Scratch 33 + 34 + Of course, this comes with some trade-offs. Being a library probably means it’s never going to feel as easygoing as something like Eleventy or Astro. You probably won’t be able to just toss a few Markdown files into a folder and call it a day. (One day, though, perhaps) 35 + 36 + That said, we think that’s okay. We’ll do our best to make Maudit as friendly as we can, and the docs as clear and welcoming as possible. But if you end up needing something simpler to get going with, that’s fine too. No hard feelings. 37 + 38 + --- 39 + 40 + We’re very excited to see what people build with Maudit! Hopefully, the flexibility of Maudit empowers and motivate you to create websites that fits your exact and precise needs. 41 + 42 + If you have any questions, feedback, or just want to say hi, feel free to [join our Discord](https://maudit.org/discord) or [open an issue or discussion on GitHub](https://github.com/bruits/maudit)!
+2 -1
website/src/content.rs
··· 1 + use chrono::NaiveDate; 1 2 use maud::{PreEscaped, Render}; 2 3 use maudit::content::{ 3 4 ContentSources, MarkdownOptions, glob_markdown, glob_markdown_with_options, markdown_entry, ··· 36 37 pub struct NewsContent { 37 38 pub title: String, 38 39 pub description: Option<String>, 39 - pub date: Option<String>, 40 + pub date: NaiveDate, 40 41 pub author: Option<String>, 41 42 } 42 43
+17 -20
website/src/routes/news.rs
··· 1 + use chrono::Datelike; 1 2 use maud::PreEscaped; 2 3 use maud::html; 3 4 use maudit::route::prelude::*; ··· 18 19 let mut articles_by_year: BTreeMap<String, Vec<_>> = BTreeMap::new(); 19 20 20 21 for article in &content.entries { 21 - if let Some(date) = &article.data(ctx).date { 22 - // Extract year from date (format: 2025-08-16) 23 - let year = date.split('-').next().unwrap_or("Unknown").to_string(); 24 - articles_by_year 25 - .entry(year) 26 - .or_insert_with(Vec::new) 27 - .push(article); 28 - } else { 29 - // articles without dates 30 - articles_by_year 31 - .entry("Unknown".to_string()) 32 - .or_insert_with(Vec::new) 33 - .push(article); 34 - } 22 + let year = article.data(ctx).date.year().to_string(); 23 + articles_by_year 24 + .entry(year) 25 + .or_insert_with(Vec::new) 26 + .push(article); 27 + } 28 + 29 + // Sort articles within each year by date (newest first) 30 + for articles in articles_by_year.values_mut() { 31 + articles.sort_by(|a, b| { 32 + let date_a = &a.data(ctx).date; 33 + let date_b = &b.data(ctx).date; 34 + date_b.cmp(date_a) // Reverse order for newest first 35 + }); 35 36 } 36 37 37 38 layout( ··· 43 44 ul.space-y-8 { 44 45 @for article in articles { 45 46 li.border-b.border-gray-200.pb-4 { 46 - @if let Some(date) = &article.data(ctx).date { 47 - p.text-sm.font-bold { (date) } 48 - } 47 + p.text-sm.font-bold { (article.data(ctx).date) } 49 48 h3.text-5xl { 50 49 a."hover:text-brand-red" href=(article.id) { 51 50 (article.data(ctx).title) ··· 124 123 html! { 125 124 div.container.mx-auto."py-10"."pb-24"."max-w-[80ch]"."px-6"."sm:px-0" { 126 125 section.mb-4.border-b."border-[#e9e9e7]".pb-2 { 127 - @if let Some(date) = &date { 128 - p.text-sm.font-bold { (date) } 129 - } 126 + p.text-sm.font-bold { (date) } 130 127 h1."text-5xl"."sm:text-6xl".font-bold.mb-3 { (title) } 131 128 @if let Some(description) = &description { 132 129 p.text-xl."sm:text-2xl".italic { (description) }