Rust library to generate static websites
5
fork

Configure Feed

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

feat: more benchmarks

+1629 -76
+21
Cargo.lock
··· 859 859 "iana-time-zone", 860 860 "js-sys", 861 861 "num-traits", 862 + "serde", 862 863 "wasm-bindgen", 863 864 "windows-link 0.2.0", 864 865 ] ··· 2925 2926 checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" 2926 2927 2927 2928 [[package]] 2929 + name = "overhead-benchmark" 2930 + version = "0.1.0" 2931 + dependencies = [ 2932 + "codspeed-divan-compat", 2933 + "itoa", 2934 + "maudit", 2935 + ] 2936 + 2937 + [[package]] 2928 2938 name = "overload" 2929 2939 version = "0.1.1" 2930 2940 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4000 4010 dependencies = [ 4001 4011 "crossbeam-deque", 4002 4012 "crossbeam-utils", 4013 + ] 4014 + 4015 + [[package]] 4016 + name = "realistic-blog-benchmark" 4017 + version = "0.1.0" 4018 + dependencies = [ 4019 + "chrono", 4020 + "codspeed-divan-compat", 4021 + "maud", 4022 + "maudit", 4023 + "serde", 4003 4024 ] 4004 4025 4005 4026 [[package]]
+6
benchmarks/README.md
··· 1 1 # Benchmarks 2 2 3 3 This directory contains various benchmarks for Maudit. 4 + 5 + ## On compile times 6 + 7 + All the numbers in these benchmarks only include the **running time** of the benchmark. [Maudit operates on the idea that your content and assets change way more often than any parts that would require re-compilation](https://maudit.org/docs/philosophy/#your-website-changes-less-often-than-its-content) (static templates, pretty much anything in a `*.rs` file) and as such expect that the vast majority of your builds won't require compilation. 8 + 9 + This is not a gotcha moment or anything we're trying to hide: **With compilation times included, Maudit is slower than most static site generators.**
+9 -31
benchmarks/md-benchmark/README.md
··· 9 9 To run the benchmark, execute the following command: 10 10 11 11 ```sh 12 - cargo run --release 13 - ``` 14 - 15 - By default, this will build 1000 pages. You can change the number of pages to build by using the `MARKDOWN_COUNT` environment variable: 16 - 17 - ```sh 18 - MARKDOWN_COUNT=4000 cargo run --release 19 - ``` 20 - 21 - Valid values for `MARKDOWN_COUNT` are 250, 500, 1000, 2000, and 4000. 22 - 23 - Note that `cargo run` has a certain overhead, as such if checking the total time, it's more useful to run the compiled binary (`target/release/maudit-benchmark`) directly 24 - 25 - ## `cargo bench` 26 - 27 - All 5 benchmarks can be run at once using the `cargo bench` command: 28 - 29 - ```sh 30 12 cargo bench 31 13 ``` 14 + 15 + 5 benchmarks with different number of pages (250, 500, 1000, 2000, 4000) will be run and the time for each benchmark will be printed to the console. 32 16 33 17 ## Results 34 18 35 19 The following results were obtained on 2025-08-27 using a MacBook Pro (13-inch, M1, 2020) with 16 GB of RAM: 36 20 37 - | Pages | Full Build Time (ms) | 38 - | ----- | -------------------- | 39 - | 250 | 37 | 40 - | 500 | 75 | 41 - | 1000 | 151 | 42 - | 2000 | 319 | 43 - | 4000 | 676 | 21 + | Pages | Median Full Build Time (ms) | 22 + | ----- | --------------------------- | 23 + | 250 | 37 | 24 + | 500 | 75 | 25 + | 1000 | 151 | 26 + | 2000 | 319 | 27 + | 4000 | 676 | 44 28 45 29 These numbers are not scientific and only serve as a rough estimate of the performance of Maudit. Your mileage may vary. 46 - 47 - ## On compile times 48 - 49 - All the numbers in this document only include the **running time** of the benchmark. [Maudit operates on the idea that your content and assets change way more often than any parts that would require re-compilation](https://maudit.org/docs/philosophy/#your-website-changes-less-often-than-its-content) (static templates, pretty much anything in a `*.rs` file) and as such expect that the vast majority of your builds won't require compilation. 50 - 51 - This is not a gotcha moment or anything we're trying to hide: **With compilation times included, Maudit is slower than most static site generators.**
+1 -1
benchmarks/md-benchmark/src/lib.rs
··· 7 7 pub fn build_website(markdown_count: u32) { 8 8 let _ = coronate( 9 9 routes![page::Article], 10 - content_sources!["articles" => glob_markdown::<UntypedMarkdownContent>(&format!("content/{}/*.md", markdown_count), None)], 10 + content_sources!["articles" => glob_markdown::<UntypedMarkdownContent>(&format!("content/{}/*.md", markdown_count))], 11 11 BuildOptions::default(), 12 12 ); 13 13 }
+16
benchmarks/overhead/Cargo.toml
··· 1 + [package] 2 + name = "overhead-benchmark" 3 + version = "0.1.0" 4 + edition = "2021" 5 + publish = false 6 + 7 + [dependencies] 8 + maudit = { workspace = true } 9 + itoa = "1.0" 10 + 11 + [dev-dependencies] 12 + divan = { version = "3.0.5", package = "codspeed-divan-compat" } 13 + 14 + [[bench]] 15 + name = "build" 16 + harness = false
+11
benchmarks/overhead/README.md
··· 1 + # overhead-benchmark 2 + 3 + This crate contains a Maudit website that generates 10000 pages with no content to benchmark the overhead of Maudit itself. 4 + 5 + ## Running the benchmark 6 + 7 + To run the benchmark, execute the following command: 8 + 9 + ```sh 10 + cargo bench 11 + ```
+30
benchmarks/overhead/benches/build.rs
··· 1 + use std::env; 2 + use std::fs; 3 + use std::path::Path; 4 + 5 + use divan::Bencher; 6 + use overhead_benchmark::build_website; 7 + 8 + fn main() { 9 + unsafe { 10 + env::set_var("MAUDIT_QUIET", "TRUE"); 11 + } 12 + divan::main(); 13 + } 14 + 15 + #[divan::bench(sample_count = 3)] 16 + fn full_build(bencher: Bencher) { 17 + bencher 18 + .with_inputs(|| { 19 + // Clear dist directory before each sample, otherwise later samples will either be very quick if we don't clean 20 + // or very slow if we do. It's better to measure only the actual work being done. It's also closer to how it'd look like 21 + // on platforms like Netlify or Vercel where the output directory is always cleaned before each build. 22 + let dist_dir = Path::new("dist"); 23 + if dist_dir.exists() { 24 + let _ = fs::remove_dir_all(dist_dir); 25 + } 26 + }) 27 + .bench_values(|()| { 28 + build_website(); 29 + }); 30 + }
+10
benchmarks/overhead/src/lib.rs
··· 1 + use maudit::{content_sources, coronate, routes, BuildOptions}; 2 + mod page; 3 + 4 + pub fn build_website() { 5 + let _ = coronate( 6 + routes![page::Article], 7 + content_sources![], 8 + BuildOptions::default(), 9 + ); 10 + }
+5
benchmarks/overhead/src/main.rs
··· 1 + use overhead_benchmark::build_website; 2 + 3 + fn main() { 4 + build_website(); 5 + }
+26
benchmarks/overhead/src/page.rs
··· 1 + use maudit::route::prelude::*; 2 + 3 + #[route("/[page]")] 4 + pub struct Article; 5 + 6 + #[derive(Params, Clone)] 7 + struct Params { 8 + page: u16, 9 + } 10 + 11 + impl Route<Params> for Article { 12 + fn pages(&self, _: &mut DynamicRouteContext) -> Vec<Page<Params>> { 13 + (0..10000) 14 + .map(|i| Page::new(Params { page: i }, ())) 15 + .collect() 16 + } 17 + 18 + fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 19 + let params = ctx.params::<Params>(); 20 + 21 + let mut buffer = itoa::Buffer::new(); 22 + let page_str = buffer.format(params.page); 23 + 24 + page_str.to_string() 25 + } 26 + }
+4
benchmarks/realistic-blog/.gitignore
··· 1 + target 2 + dist 3 + node_modules 4 + .DS_Store
+18
benchmarks/realistic-blog/Cargo.toml
··· 1 + [package] 2 + name = "realistic-blog-benchmark" 3 + version = "0.1.0" 4 + edition = "2021" 5 + publish = false 6 + 7 + [dependencies] 8 + maudit = { workspace = true } 9 + maud = "0.27.0" 10 + serde = { version = "1.0.216" } 11 + chrono = { version = "0.4.42", features = ["serde"] } 12 + 13 + [dev-dependencies] 14 + divan = { version = "3.0.5", package = "codspeed-divan-compat" } 15 + 16 + [[bench]] 17 + name = "build" 18 + harness = false
+11
benchmarks/realistic-blog/README.md
··· 1 + # realistic-blog-benchmark 2 + 3 + This crate contains a Maudit website that represents a realistic average blog with 36 posts, a few images, code block, a shortcode, paginated article list, etc. 4 + 5 + ## Running the benchmark 6 + 7 + To run the benchmark, execute the following command: 8 + 9 + ```sh 10 + cargo bench 11 + ```
+30
benchmarks/realistic-blog/benches/build.rs
··· 1 + use std::env; 2 + use std::fs; 3 + use std::path::Path; 4 + 5 + use divan::Bencher; 6 + use realistic_blog_benchmark::build_website; 7 + 8 + fn main() { 9 + unsafe { 10 + env::set_var("MAUDIT_QUIET", "TRUE"); 11 + } 12 + divan::main(); 13 + } 14 + 15 + #[divan::bench(sample_count = 3)] 16 + fn full_build(bencher: Bencher) { 17 + bencher 18 + .with_inputs(|| { 19 + // Clear dist directory before each sample, otherwise later samples will either be very quick if we don't clean 20 + // or very slow if we do. It's better to measure only the actual work being done. It's also closer to how it'd look like 21 + // on platforms like Netlify or Vercel where the output directory is always cleaned before each build. 22 + let dist_dir = Path::new("dist"); 23 + if dist_dir.exists() { 24 + let _ = fs::remove_dir_all(dist_dir); 25 + } 26 + }) 27 + .bench_values(|()| { 28 + build_website(); 29 + }); 30 + }
benchmarks/realistic-blog/content/articles/assets/other-walruses.jpeg

This is a binary file and will not be displayed.

benchmarks/realistic-blog/content/articles/assets/vertical-walruses.jpeg

This is a binary file and will not be displayed.

benchmarks/realistic-blog/content/articles/assets/walrus.jpg

This is a binary file and will not be displayed.

benchmarks/realistic-blog/content/articles/assets/walruses.jpeg

This is a binary file and will not be displayed.

+34
benchmarks/realistic-blog/content/articles/eighteenth-post.md
··· 1 + --- 2 + title: Eighteenth Post 3 + description: This is the eighteenth post on the blog. 4 + date: 2025-09-18 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Images 33 + 34 + ![Walruses](./assets/other-walruses.jpeg)
+34
benchmarks/realistic-blog/content/articles/eighth-post.md
··· 1 + --- 2 + title: Eighth Post 3 + description: This is the eighth post on the blog. 4 + date: 2025-09-08 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Images 33 + 34 + ![Walruses](./assets/vertical-walruses.jpeg)
+30
benchmarks/realistic-blog/content/articles/eleventh-post.md
··· 1 + --- 2 + title: Eleventh Post 3 + description: This is the eleventh post on the blog. 4 + date: 2025-09-11 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+30
benchmarks/realistic-blog/content/articles/fifteenth-post.md
··· 1 + --- 2 + title: Fifteenth Post 3 + description: This is the fifteenth post on the blog. 4 + date: 2025-09-15 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+30
benchmarks/realistic-blog/content/articles/fifth-post.md
··· 1 + --- 2 + title: Fifth Post 3 + description: This is the fifth post on the blog. 4 + date: 2025-09-05 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+30
benchmarks/realistic-blog/content/articles/first-post.md
··· 1 + --- 2 + title: First Post 3 + description: This is the first post on the blog. 4 + date: 2025-09-01 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+44
benchmarks/realistic-blog/content/articles/fourteenth-post.md
··· 1 + --- 2 + title: Fourteenth Post 3 + description: This is the fourteenth post on the blog. 4 + date: 2025-09-14 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Code 33 + 34 + You can include inline code like this: `let x = 10;`. 35 + 36 + You can also include code blocks: 37 + 38 + ```rust 39 + fn main() { 40 + println!("Hello, world!"); 41 + } 42 + ``` 43 + 44 + These are highlighted automatically based on the language specified.
+30
benchmarks/realistic-blog/content/articles/fourth-post.md
··· 1 + --- 2 + title: Fourth Post 3 + description: This is the fourth post on the blog. 4 + date: 2025-09-04 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+30
benchmarks/realistic-blog/content/articles/nineteenth-post.md
··· 1 + --- 2 + title: Nineteenth Post 3 + description: This is the nineteenth post on the blog. 4 + date: 2025-09-19 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+30
benchmarks/realistic-blog/content/articles/ninth-post.md
··· 1 + --- 2 + title: Ninth Post 3 + description: This is the ninth post on the blog. 4 + date: 2025-09-09 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+7
benchmarks/realistic-blog/content/articles/second-post.md
··· 1 + --- 2 + title: Second Post 3 + description: This is the second post on the blog. 4 + date: 2025-09-10 5 + --- 6 + 7 + This is another post on the blog!
+42
benchmarks/realistic-blog/content/articles/seventeenth-post.md
··· 1 + --- 2 + title: Seventeenth Post 3 + description: This is the seventeenth post on the blog. 4 + date: 2025-09-17 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Code 33 + 34 + Here’s how you can include code snippets: 35 + 36 + ```rust 37 + fn main() { 38 + println!("Hello, world!"); 39 + } 40 + ``` 41 + 42 + Its syntax is automatically highlighted based on the language specified.
+24
benchmarks/realistic-blog/content/articles/seventh-post.md
··· 1 + --- 2 + title: Seventh Post 3 + description: This is the seventh post on the blog. 4 + date: 2025-09-07 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Combining All Features 21 + 22 + Here’s a sentence that combines everything: 23 + 24 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+30
benchmarks/realistic-blog/content/articles/sixteenth-post.md
··· 1 + --- 2 + title: Sixteenth Post 3 + description: This is the sixteenth post on the blog. 4 + date: 2025-09-16 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+41
benchmarks/realistic-blog/content/articles/sixth-post.md
··· 1 + --- 2 + title: Sixth Post 3 + description: This is the sixth post on the blog. 4 + date: 2025-09-06 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Code 33 + 34 + Here's a code block of Python: 35 + 36 + ```python 37 + def greet(name): 38 + return f"Hello, {name}!" 39 + ``` 40 + 41 + It is syntax highlighted!
+34
benchmarks/realistic-blog/content/articles/tenth-post.md
··· 1 + --- 2 + title: Tenth Post 3 + description: This is the tenth post on the blog. 4 + date: 2025-09-10 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Images 33 + 34 + ![Walruses](./assets/walruses.jpeg)
+7
benchmarks/realistic-blog/content/articles/third-post.md
··· 1 + --- 2 + title: Third Post 3 + description: This is the third post on the blog. 4 + date: 2025-09-20 5 + --- 6 + 7 + Well, another post is here.
+30
benchmarks/realistic-blog/content/articles/thirteenth-post.md
··· 1 + --- 2 + title: Thirteenth Post 3 + description: This is the thirteenth post on the blog. 4 + date: 2025-09-13 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+21
benchmarks/realistic-blog/content/articles/thirtieth-post.md
··· 1 + --- 2 + title: Thirtieth Post 3 + description: This is the thirtieth post on the blog. 4 + date: 2025-09-30 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** and _italic_ are easy to use. 10 + - Plain text is also important. 11 + 12 + ## Section 2: Links 13 + 14 + - [Markdown Guide](https://www.markdownguide.org/) 15 + 16 + ## Section 3: Table Example 17 + 18 + | Feature | Use | 19 + | ------- | -------- | 20 + | Bold | Emphasis | 21 + | Italic | Subtlety |
+17
benchmarks/realistic-blog/content/articles/thirty-fifth-post.md
··· 1 + --- 2 + title: Thirty-Fifth Post 3 + description: This is the thirty-fifth post on the blog. 4 + date: 2025-10-05 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - _Italic_ and **bold** can be combined. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Combining All Features 16 + 17 + - **_Try mixing styles and links for more impact!_**
+17
benchmarks/realistic-blog/content/articles/thirty-first-post.md
··· 1 + --- 2 + title: Thirty-First Post 3 + description: This is the thirty-first post on the blog. 4 + date: 2025-10-01 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - _Italic_ and **bold** can be combined. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Combining All Features 16 + 17 + - **_Try mixing styles and links for more impact!_**
+20
benchmarks/realistic-blog/content/articles/thirty-fourth-post.md
··· 1 + --- 2 + title: Thirty-Fourth Post 3 + description: This is the thirty-fourth post on the blog. 4 + date: 2025-10-04 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** and _italic_ are easy to use. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Table Example 16 + 17 + | Feature | Use | 18 + | ------- | -------- | 19 + | Bold | Emphasis | 20 + | Italic | Subtlety |
+31
benchmarks/realistic-blog/content/articles/thirty-second-post.md
··· 1 + --- 2 + title: Thirty-Second Post 3 + description: This is the thirty-second post on the blog. 4 + date: 2025-10-02 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** text stands out. 10 + - _Italic_ text gives emphasis. 11 + 12 + ## Section 2: Links 13 + 14 + - [Markdown Guide](https://www.markdownguide.org/) 15 + 16 + ## Section 3: Table Example 17 + 18 + | Syntax | Description | 19 + | --------- | ----------- | 20 + | Header | Title | 21 + | Paragraph | Text | 22 + 23 + ## Youtube Shortcode Example 24 + 25 + Here is an example of embedding a YouTube video using a shortcode: 26 + 27 + ```markdown 28 + \{{ youtube id="ekr2nIex040" /}} 29 + ``` 30 + 31 + {{ youtube id="ekr2nIex040" /}}
+14
benchmarks/realistic-blog/content/articles/thirty-sixth-post.md
··· 1 + --- 2 + title: Thirty-Sixth Post 3 + description: This is the thirty-sixth post on the blog. 4 + date: 2025-10-06 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** text stands out. 10 + - _Italic_ text gives emphasis. 11 + 12 + ## Section 2: Links 13 + 14 + - [Markdown Guide](https://www.markdownguide.org/)
+17
benchmarks/realistic-blog/content/articles/thirty-third-post.md
··· 1 + --- 2 + title: Thirty-Third Post 3 + description: This is the thirty-third post on the blog. 4 + date: 2025-10-03 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - _Italic_ and **bold** are easy in Markdown. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Combining All Features 16 + 17 + - **_Try mixing styles and links for more impact!_**
+30
benchmarks/realistic-blog/content/articles/twelfth-post.md
··· 1 + --- 2 + title: Twelfth Post 3 + description: This is the twelfth post on the blog. 4 + date: 2025-09-12 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+42
benchmarks/realistic-blog/content/articles/twentieth-post.md
··· 1 + --- 2 + title: Twentieth Post 3 + description: This is the twentieth post on the blog. 4 + date: 2025-09-20 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**. 31 + 32 + ## Section 4: Code 33 + 34 + Here's a code block of TypeScript: 35 + 36 + ```typescript 37 + function greet(name: string): string { 38 + return `Hello, ${name}!`; 39 + } 40 + ``` 41 + 42 + It is syntax highlighted!
+29
benchmarks/realistic-blog/content/articles/twenty-eighth-post.md
··· 1 + --- 2 + title: Twenty-Eighth Post 3 + description: This is the twenty-eighth post on the blog. 4 + date: 2025-09-28 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** text stands out. 10 + - _Italic_ text gives emphasis. 11 + 12 + ## Section 2: Links 13 + 14 + - [Markdown Guide](https://www.markdownguide.org/) 15 + 16 + ## Section 3: Table Example 17 + 18 + | Syntax | Description | 19 + | --------- | ----------- | 20 + | Header | Title | 21 + | Paragraph | Text | 22 + 23 + ## Fun Fact 24 + 25 + Many static site generators use Markdown for content. 26 + 27 + ## Section 4: Images 28 + 29 + ![Walrus](./assets/walrus.jpg)
+21
benchmarks/realistic-blog/content/articles/twenty-fifth-post.md
··· 1 + --- 2 + title: Twenty-Fifth Post 3 + description: This is the twenty-fifth post on the blog. 4 + date: 2025-09-25 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - _Italic_ and **bold** are easy in Markdown. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Combining All Features 16 + 17 + - **_Try mixing styles and links for more impact!_** 18 + 19 + ## Fun Fact 20 + 21 + The word "Markdown" is a play on "markup language."
+30
benchmarks/realistic-blog/content/articles/twenty-first-post.md
··· 1 + --- 2 + title: Twenty-First Post 3 + description: This is the twenty-first post on the blog. 4 + date: 2025-09-21 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** makes things stand out. 12 + - _Italic Text_ gives emphasis to words. 13 + - **_Bold and Italic_** combined for maximum emphasis. 14 + 15 + ### Mixed Formatting 16 + 17 + - **This is bold and _italic_** to combine styles. 18 + - _This is italic and **bold**_ as well. 19 + 20 + ## Section 2: Links 21 + 22 + You can include links like this: 23 + 24 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 25 + 26 + ## Section 3: Combining All Features 27 + 28 + Here’s a sentence that combines everything: 29 + 30 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+20
benchmarks/realistic-blog/content/articles/twenty-fourth-post.md
··· 1 + --- 2 + title: Twenty-Fourth Post 3 + description: This is the twenty-fourth post on the blog. 4 + date: 2025-09-24 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** and _italic_ can be mixed. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Table Example 16 + 17 + | Syntax | Description | 18 + | --------- | ----------- | 19 + | Header | Title | 20 + | Paragraph | Text |
+21
benchmarks/realistic-blog/content/articles/twenty-ninth-post.md
··· 1 + --- 2 + title: Twenty-Ninth Post 3 + description: This is the twenty-ninth post on the blog. 4 + date: 2025-09-29 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - _Italic_ and **bold** are easy in Markdown. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Combining All Features 16 + 17 + - **_Try mixing styles and links for more impact!_** 18 + 19 + ## Fun Fact 20 + 21 + Markdown is supported by many editors and platforms.
+20
benchmarks/realistic-blog/content/articles/twenty-second-post.md
··· 1 + --- 2 + title: Twenty-Second Post 3 + description: This is the twenty-second post on the blog. 4 + date: 2025-09-22 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold Text** and _italic text_ are useful for emphasis. 10 + - Sometimes you just need plain text. 11 + 12 + ## Section 2: Links 13 + 14 + Here’s a useful resource: 15 + 16 + - [Markdown Guide](https://www.markdownguide.org/) 17 + 18 + ## Fun Fact 19 + 20 + Did you know? Markdown was created in 2004 by John Gruber and Aaron Swartz.
+21
benchmarks/realistic-blog/content/articles/twenty-seventh-post.md
··· 1 + --- 2 + title: Twenty-Seventh Post 3 + description: This is the twenty-seventh post on the blog. 4 + date: 2025-09-27 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - _Italic_ and **bold** can be combined. 10 + 11 + ## Section 2: Links 12 + 13 + - [Markdown Guide](https://www.markdownguide.org/) 14 + 15 + ## Section 3: Combining All Features 16 + 17 + - **_Try mixing styles and links for more impact!_** 18 + 19 + ## Fun Fact 20 + 21 + You can use Markdown for notes, documentation, and more.
+25
benchmarks/realistic-blog/content/articles/twenty-sixth-post.md
··· 1 + --- 2 + title: Twenty-Sixth Post 3 + description: This is the twenty-sixth post on the blog. 4 + date: 2025-09-26 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + - **Bold** and _italic_ are easy to use. 10 + - Plain text is also important. 11 + 12 + ## Section 2: Links 13 + 14 + - [Markdown Guide](https://www.markdownguide.org/) 15 + 16 + ## Section 3: Table Example 17 + 18 + | Feature | Use | 19 + | ------- | -------- | 20 + | Bold | Emphasis | 21 + | Italic | Subtlety | 22 + 23 + ## Fun Fact 24 + 25 + Markdown files are just plain text!
+18
benchmarks/realistic-blog/content/articles/twenty-third-post.md
··· 1 + --- 2 + title: Twenty-Third Post 3 + description: This is the twenty-third post on the blog. 4 + date: 2025-09-23 5 + --- 6 + 7 + ## Section 1: Formatting Text 8 + 9 + ### Bold and Italic 10 + 11 + - **Bold Text** stands out. 12 + - _Italic Text_ is subtle. 13 + 14 + ## Section 2: Combining All Features 15 + 16 + Try combining styles: 17 + 18 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+9
benchmarks/realistic-blog/src/content.rs
··· 1 + use chrono::NaiveDate; 2 + use maudit::content::markdown_entry; 3 + 4 + #[markdown_entry] 5 + pub struct ArticleContent { 6 + pub title: String, 7 + pub description: String, 8 + pub date: NaiveDate, 9 + }
+27
benchmarks/realistic-blog/src/layout.rs
··· 1 + use maud::{html, Markup, PreEscaped}; 2 + use maudit::route::PageContext; 3 + 4 + pub fn layout(ctx: &mut PageContext, content: String) -> Markup { 5 + ctx.assets.include_style("src/style.css"); 6 + 7 + html! { 8 + html { 9 + head { 10 + meta charset="utf-8"; 11 + meta name="viewport" content="width=device-width, initial-scale=1"; 12 + title { "Erika's Super Blog" } 13 + } 14 + body { 15 + header { 16 + h1 { a href="/" { "Erika's Super Blog" } } 17 + } 18 + main { 19 + (PreEscaped(content)) 20 + } 21 + footer { 22 + p { "© 2024 My Super Blog" } 23 + } 24 + } 25 + } 26 + } 27 + }
+40
benchmarks/realistic-blog/src/lib.rs
··· 1 + mod content; 2 + mod layout; 3 + 4 + use content::ArticleContent; 5 + use maudit::{ 6 + content::{glob_markdown_with_options, shortcodes::MarkdownShortcodes, MarkdownOptions}, 7 + content_sources, coronate, routes, BuildOptions, 8 + }; 9 + 10 + mod routes { 11 + mod article; 12 + mod index; 13 + pub use article::{Article, Articles}; 14 + pub use index::Index; 15 + } 16 + 17 + pub fn build_website() { 18 + let _ = coronate( 19 + routes![routes::Index, routes::Articles, routes::Article], 20 + content_sources![ 21 + "articles" => glob_markdown_with_options::<ArticleContent>("content/articles/*.md", MarkdownOptions { 22 + shortcodes: { 23 + let mut shortcodes = MarkdownShortcodes::default(); 24 + 25 + shortcodes.register("youtube", |attrs, _| { 26 + if let Some(id) = attrs.get::<String>("id") { 27 + format!(r#"<iframe width="560" height="315" src="https://www.youtube.com/embed/{}" frameborder="0" allowfullscreen></iframe>"#, id) 28 + } else { 29 + panic!("YouTube shortcode requires an 'id' attribute"); 30 + } 31 + }); 32 + 33 + shortcodes 34 + }, 35 + ..Default::default() 36 + }) 37 + ], 38 + BuildOptions::default(), 39 + ); 40 + }
+5
benchmarks/realistic-blog/src/main.rs
··· 1 + use realistic_blog_benchmark::build_website; 2 + 3 + fn main() { 4 + build_website(); 5 + }
+91
benchmarks/realistic-blog/src/routes/article.rs
··· 1 + use maud::html; 2 + use maudit::route::prelude::*; 3 + 4 + use crate::{content::ArticleContent, layout::layout}; 5 + 6 + #[route("/articles/[page]")] 7 + pub struct Articles; 8 + 9 + #[derive(Params, Clone)] 10 + pub struct ArticlesParams { 11 + pub page: Option<usize>, 12 + } 13 + 14 + impl Route<ArticlesParams, PaginatedContentPage<ArticleContent>> for Articles { 15 + fn pages( 16 + &self, 17 + ctx: &mut DynamicRouteContext, 18 + ) -> Pages<ArticlesParams, PaginatedContentPage<ArticleContent>> { 19 + let articles = &ctx.content.get_source::<ArticleContent>("articles").entries; 20 + 21 + let mut articles = articles.to_vec(); 22 + articles.sort_by(|a, b| b.data(ctx).date.cmp(&a.data(ctx).date)); 23 + 24 + paginate(articles, 4, |page| ArticlesParams { 25 + page: if page == 0 { None } else { Some(page) }, 26 + }) 27 + } 28 + 29 + fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 30 + let current_page = ctx.params::<ArticlesParams>().page.unwrap_or(0); 31 + let props = ctx.props::<PaginatedContentPage<ArticleContent>>(); 32 + 33 + let markup = html! { 34 + ul.articles-list { 35 + @for entry in &props.items { 36 + li { 37 + a href=(&Article.url(ArticleParams { article: entry.id.clone() })) { 38 + h2 { (entry.data(ctx).title) } 39 + } 40 + p { (entry.data(ctx).description) } 41 + span { (entry.data(ctx).date) } 42 + } 43 + } 44 + } 45 + div.article-pagination { 46 + @if props.has_next { 47 + @let next_page = current_page + 1; 48 + @let next_param = if next_page == 0 { None } else { Some(next_page) }; 49 + a href=(&Articles.url(ArticlesParams { page: next_param })) { "Previous page..." } 50 + } 51 + @if props.has_prev { 52 + @let prev_page = current_page.saturating_sub(1); 53 + @let prev_param = if prev_page == 0 { None } else { Some(prev_page) }; 54 + a href=(&Articles.url(ArticlesParams { page: prev_param })) { "Next page..." } 55 + } 56 + } 57 + } 58 + .into_string(); 59 + 60 + layout(ctx, markup) 61 + } 62 + } 63 + 64 + #[route("/articles/[article]")] 65 + pub struct Article; 66 + 67 + #[derive(Params, Clone)] 68 + pub struct ArticleParams { 69 + pub article: String, 70 + } 71 + 72 + impl Route<ArticleParams> for Article { 73 + fn pages(&self, ctx: &mut DynamicRouteContext) -> Pages<ArticleParams> { 74 + let articles = ctx.content.get_source::<ArticleContent>("articles"); 75 + 76 + articles.into_pages(|entry| { 77 + Page::from_params(ArticleParams { 78 + article: entry.id.clone(), 79 + }) 80 + }) 81 + } 82 + 83 + fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 84 + let params = ctx.params::<ArticleParams>(); 85 + let articles = ctx.content.get_source::<ArticleContent>("articles"); 86 + let article = articles.get_entry(&params.article); 87 + 88 + let content = article.render(ctx); 89 + layout(ctx, content) 90 + } 91 + }
+53
benchmarks/realistic-blog/src/routes/index.rs
··· 1 + use maud::html; 2 + use maudit::route::prelude::*; 3 + 4 + use crate::{ 5 + content::ArticleContent, 6 + layout::layout, 7 + routes::{ 8 + article::{ArticleParams, ArticlesParams}, 9 + Article, Articles, 10 + }, 11 + }; 12 + 13 + #[route("/")] 14 + pub struct Index; 15 + 16 + impl Route for Index { 17 + fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 18 + let mut articles = ctx 19 + .content 20 + .get_source::<ArticleContent>("articles") 21 + .entries 22 + .iter() 23 + .collect::<Vec<_>>(); // Collect into a Vec to allow sorting 24 + 25 + // Sort by date, newest first 26 + articles.sort_by(|a, b| b.data(ctx).date.cmp(&a.data(ctx).date)); 27 + 28 + // Take three latest 29 + articles = articles.into_iter().take(3).collect::<Vec<_>>(); 30 + 31 + let markup = html! { 32 + h2 { "Hello!" } 33 + p { "Welcome to my blog. I'm a super real blog that was totally not created to serve as a benchmark. In my articles, you'll find various content such as, for example, 36 guides on how to use Markdown. Suspiciously, some of them are slightly different." } 34 + 35 + h2 { "Latest Articles" } 36 + ul.articles-list { 37 + @for entry in &articles { 38 + li { 39 + a href=(&Article.url(ArticleParams { article: entry.id.clone() })) { 40 + h2 { (entry.data(ctx).title) } 41 + } 42 + p { (entry.data(ctx).description) } 43 + span { (entry.data(ctx).date) } 44 + } 45 + } 46 + } 47 + a href=(&Articles.url(ArticlesParams { page: None })) { "See all articles..." } 48 + } 49 + .into_string(); 50 + 51 + layout(ctx, markup) 52 + } 53 + }
+149
benchmarks/realistic-blog/src/style.css
··· 1 + /* Base styles */ 2 + * { 3 + margin: 0; 4 + padding: 0; 5 + box-sizing: border-box; 6 + } 7 + 8 + body { 9 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 10 + line-height: 1.6; 11 + color: #333; 12 + background-color: #fff; 13 + } 14 + 15 + /* Header */ 16 + header { 17 + background-color: #1ea070ff; 18 + padding: 2rem 0; 19 + text-align: center; 20 + } 21 + 22 + header h1 { 23 + font-size: 3rem; 24 + font-weight: 600; 25 + } 26 + 27 + header h1 a { 28 + color: white; 29 + text-decoration: none; 30 + } 31 + 32 + /* Main content */ 33 + main { 34 + max-width: 700px; 35 + margin: 0 auto; 36 + padding: 2rem 1rem; 37 + } 38 + 39 + /* Typography */ 40 + h1, 41 + h2, 42 + h3 { 43 + color: #1e293b; 44 + margin-bottom: 1rem; 45 + } 46 + 47 + h1 { 48 + font-size: 2rem; 49 + } 50 + h2 { 51 + font-size: 1.5rem; 52 + margin-top: 2rem; 53 + } 54 + h3 { 55 + font-size: 1.25rem; 56 + margin-top: 1.5rem; 57 + } 58 + 59 + p { 60 + margin-bottom: 1rem; 61 + color: #64748b; 62 + } 63 + 64 + /* Links */ 65 + a { 66 + color: #2563eb; 67 + text-decoration: none; 68 + } 69 + 70 + a:hover { 71 + text-decoration: underline; 72 + } 73 + 74 + /* Code */ 75 + code { 76 + background-color: #f1f5f9; 77 + padding: 0.2rem 0.4rem; 78 + border-radius: 3px; 79 + font-family: ui-monospace, "SF Mono", monospace; 80 + font-size: 0.875rem; 81 + } 82 + 83 + pre { 84 + background-color: #1e293b; 85 + color: #e2e8f0; 86 + padding: 1rem; 87 + border-radius: 6px; 88 + overflow-x: auto; 89 + margin: 1rem 0; 90 + } 91 + 92 + pre code { 93 + background: none; 94 + padding: 0; 95 + color: inherit; 96 + } 97 + 98 + /* Lists */ 99 + ul, 100 + ol { 101 + margin-bottom: 1rem; 102 + padding-left: 1.5rem; 103 + } 104 + 105 + .article-list { 106 + list-style-type: none; 107 + } 108 + 109 + .article-pagination { 110 + display: flex; 111 + justify-content: space-between; 112 + margin-top: 2rem; 113 + } 114 + 115 + li { 116 + margin-bottom: 0.25rem; 117 + color: #64748b; 118 + } 119 + 120 + img { 121 + max-width: 100%; 122 + height: auto; 123 + margin: 1rem 0; 124 + border-radius: 8px; 125 + } 126 + 127 + /* Footer */ 128 + footer { 129 + padding: 1.5rem; 130 + text-align: center; 131 + margin-top: 3rem; 132 + border-top: 1px solid #e2e8f0; 133 + } 134 + 135 + footer p { 136 + color: #64748b; 137 + font-size: 0.875rem; 138 + } 139 + 140 + /* Responsive */ 141 + @media (max-width: 640px) { 142 + main { 143 + padding: 1rem; 144 + } 145 + 146 + header h1 { 147 + font-size: 1.75rem; 148 + } 149 + }
+3 -3
crates/maudit/src/content.rs
··· 41 41 /// coronate( 42 42 /// routes![], 43 43 /// content_sources![ 44 - /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 44 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 45 45 /// ], 46 46 /// BuildOptions::default(), 47 47 /// ) ··· 102 102 /// coronate( 103 103 /// routes![], 104 104 /// content_sources![ 105 - /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 105 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 106 106 /// ], 107 107 /// BuildOptions::default(), 108 108 /// ) ··· 346 346 /// # } 347 347 /// 348 348 /// pub fn content_sources() -> ContentSources { 349 - /// content_sources!["docs" => glob_markdown::<ArticleContent>("content/docs/*.md", None)] 349 + /// content_sources!["docs" => glob_markdown::<ArticleContent>("content/docs/*.md")] 350 350 /// } 351 351 pub struct ContentSources(pub(crate) Vec<Box<dyn ContentSourceInternal>>); 352 352
+39 -4
crates/maudit/src/content/markdown.rs
··· 122 122 /// coronate( 123 123 /// routes![], 124 124 /// content_sources![ 125 - /// "articles" => glob_markdown::<UntypedMarkdownContent>("content/spooky/*.md", None) 125 + /// "articles" => glob_markdown::<UntypedMarkdownContent>("content/spooky/*.md") 126 126 /// ], 127 127 /// BuildOptions::default(), 128 128 /// ) ··· 183 183 /// ## Example 184 184 /// ```rs 185 185 /// use maudit::{coronate, content_sources, routes, BuildOptions, BuildOutput}; 186 - /// use maudit::content::{markdown_entry, glob_markdown}; 186 + /// use maudit::content::{markdown_entry, glob_markdown_with_options, MarkdownOptions}; 187 187 /// 188 188 /// #[markdown_entry] 189 189 /// pub struct ArticleContent { ··· 195 195 /// coronate( 196 196 /// routes![], 197 197 /// content_sources![ 198 - /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 198 + /// "articles" => glob_markdown_with_options::<ArticleContent>("content/articles/*.md", ) 199 199 /// ], 200 200 /// BuildOptions::default(), 201 201 /// ) 202 202 /// } 203 203 /// ``` 204 - pub fn glob_markdown<T>(pattern: &str, options: Option<MarkdownOptions>) -> Vec<Entry<T>> 204 + pub fn glob_markdown_with_options<T>(pattern: &str, options: MarkdownOptions) -> Vec<Entry<T>> 205 205 where 206 206 T: DeserializeOwned + MarkdownContent + InternalMarkdownContent + Send + Sync + 'static, 207 207 { 208 + let options = Some(options); 208 209 let mut entries = vec![]; 209 210 let options = options.map(Arc::new); 210 211 ··· 245 246 } 246 247 247 248 entries 249 + } 250 + 251 + /// Glob for Markdown files and return a vector of [`Entry`]s. 252 + /// 253 + /// Typically used by [`content_sources!`](crate::content_sources) to define a Markdown content source in [`coronate()`](crate::coronate). 254 + /// 255 + /// To provide custom options for Markdown rendering, use [`glob_markdown_with_options`] instead. 256 + /// 257 + /// ## Example 258 + /// ```rs 259 + /// use maudit::{coronate, content_sources, routes, BuildOptions, BuildOutput}; 260 + /// use maudit::content::{markdown_entry, glob_markdown}; 261 + /// 262 + /// #[markdown_entry] 263 + /// pub struct ArticleContent { 264 + /// pub title: String, 265 + /// pub description: String, 266 + /// } 267 + /// 268 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 269 + /// coronate( 270 + /// routes![], 271 + /// content_sources![ 272 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 273 + /// ], 274 + /// BuildOptions::default(), 275 + /// ) 276 + /// } 277 + /// ``` 278 + pub fn glob_markdown<T>(pattern: &str) -> Vec<Entry<T>> 279 + where 280 + T: DeserializeOwned + MarkdownContent + InternalMarkdownContent + Send + Sync + 'static, 281 + { 282 + glob_markdown_with_options(pattern, MarkdownOptions::default()) 248 283 } 249 284 250 285 fn get_text_from_events(events_slice: &[Event]) -> String {
+3 -2
crates/maudit/src/content/markdown/shortcodes.rs
··· 68 68 while let Some(start) = rest.find("{{") { 69 69 // Check for escaped shortcode syntax like `\{{` - if found, skip this occurrence 70 70 if start > 0 && rest.chars().nth(start - 1) == Some('\\') { 71 - // This is an escaped shortcode, add everything up to and including the {{ 72 - output.push_str(&rest[..start + 2]); 71 + // Remove the backslash and output the literal {{ 72 + output.push_str(&rest[..start - 1]); // up to the backslash 73 + output.push_str("{{"); // output {{ 73 74 rest = &rest[start + 2..]; 74 75 continue; 75 76 }
+3 -3
crates/maudit/src/lib.rs
··· 133 133 /// coronate( 134 134 /// routes![], 135 135 /// content_sources![ 136 - /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 136 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 137 137 /// ], 138 138 /// BuildOptions::default(), 139 139 /// ) ··· 151 151 /// # } 152 152 /// 153 153 /// content_sources![ 154 - /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 154 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 155 155 /// ]; 156 156 /// ``` 157 157 /// expands to ··· 164 164 /// # } 165 165 /// 166 166 /// maudit::content::ContentSources(vec![ 167 - /// Box::new(maudit::content::ContentSource::new("articles", Box::new(move || glob_markdown::<ArticleContent>("content/articles/*.md", None)))) 167 + /// Box::new(maudit::content::ContentSource::new("articles", Box::new(move || glob_markdown::<ArticleContent>("content/articles/*.md")))) 168 168 /// ]); 169 169 #[macro_export] 170 170 macro_rules! content_sources {
+29 -14
crates/maudit/src/route.rs
··· 11 11 use std::any::Any; 12 12 use std::path::{Path, PathBuf}; 13 13 14 - /// The result of a page render, can be either text or raw bytes. 14 + /// The result of a page render, can be either text, raw bytes, or an error. 15 15 /// 16 16 /// Typically used through the [`Into<RenderResult>`](std::convert::Into) and [`From<RenderResult>`](std::convert::From) implementations for common types. 17 17 /// End users should rarely need to interact with this enum directly. ··· 25 25 /// 26 26 /// impl Route for Index { 27 27 /// fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 28 - /// "<h1>Hello, world!</h1>".into() 28 + /// "<h1>Hello, world!</h1>" 29 29 /// } 30 30 /// } 31 31 /// ``` ··· 119 119 pub total_pages: usize, 120 120 pub has_next: bool, 121 121 pub has_prev: bool, 122 - pub next_page: Option<usize>, 123 - pub prev_page: Option<usize>, 124 122 pub start_index: usize, 125 123 pub end_index: usize, 126 124 pub items: Vec<T>, ··· 143 141 total_pages, 144 142 has_next: page < total_pages - 1, 145 143 has_prev: page > 0, 146 - next_page: if page < total_pages - 1 { 147 - Some(page + 1) 148 - } else { 149 - None 150 - }, 151 - prev_page: if page > 0 { Some(page - 1) } else { None }, 152 144 start_index, 153 145 end_index, 154 146 items: page_items, ··· 165 157 .field("total_pages", &self.total_pages) 166 158 .field("has_next", &self.has_next) 167 159 .field("has_prev", &self.has_prev) 168 - .field("next_page", &self.next_page) 169 - .field("prev_page", &self.prev_page) 170 160 .field("start_index", &self.start_index) 171 161 .field("end_index", &self.end_index) 172 162 // I don't really want to force users to implement Debug for T, so just show the length of items ··· 179 169 pub type PaginatedContentPage<T> = PaginationPage<Entry<T>>; 180 170 181 171 /// Helper function to create paginated routes from any iterator 172 + /// 173 + /// Example: 174 + /// ```rs 175 + /// use maudit::route::prelude::*; 176 + /// 177 + /// #[route("/tags/[page]")] 178 + /// pub struct Tags; 179 + /// 180 + /// #[derive(Params)] 181 + /// pub struct TagsParams { 182 + /// pub page: usize, 183 + /// } 184 + /// 185 + /// impl Route<TagsParams, PaginationPage<String>> for Tags { 186 + /// fn pages(&self, ctx: &mut DynamicRouteContext) -> Vec<Page<TagsParams, PaginationPage<String>>> { 187 + /// let tags = vec!["rust".to_string(), "javascript".to_string(), "python".to_string()]; 188 + /// paginate(tags, 2, |page| TagsParams { page }) 189 + /// } 190 + /// 191 + /// fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 192 + /// let props = ctx.props::<PaginationPage<String>>(); 193 + /// format!("Page {} of tags: {:?}", props.page + 1, props.items) 194 + /// } 195 + /// } 196 + /// ``` 182 197 pub fn paginate<T, I, Params>( 183 198 items: I, 184 199 per_page: usize, ··· 564 579 { 565 580 } 566 581 567 - /// Used internally by Maudit and should not be implemented by the user. 568 - /// We expose it because [`maudit_macros::route`] implements it for the user behind the scenes. 582 + /// Internal trait implemented by all routes, used by Maudit to render pages. 583 + /// [`maudit_macros::route`] implements it automatically for the user. 569 584 pub trait FullRoute: InternalRoute + Sync + Send { 570 585 #[doc(hidden)] 571 586 fn render_internal(
+1 -1
crates/oubli/src/lib.rs
··· 60 60 stringify!($ident), 61 61 Box::new({ 62 62 let glob = $glob.to_string(); 63 - move || maudit::content::glob_markdown::<oubli::archetypes::blog::BlogEntryContent>(&glob, None) 63 + move || maudit::content::glob_markdown::<oubli::archetypes::blog::BlogEntryContent>(&glob) 64 64 }), 65 65 ); 66 66 // Generate the pages
+1 -1
examples/blog/src/main.rs
··· 16 16 coronate( 17 17 routes![routes::Index, routes::Article], 18 18 content_sources![ 19 - "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 19 + "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 20 20 ], 21 21 BuildOptions::default(), 22 22 )
+1 -1
examples/library/src/main.rs
··· 18 18 build_website( 19 19 routes![routes::Index], 20 20 content_sources![ 21 - "articles" => glob_markdown::<ArticleContent>("content/articles/*.md", None) 21 + "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 22 22 ], 23 23 &BuildOptions::default(), 24 24 )
+2 -3
examples/markdown-components/src/main.rs
··· 1 - use maudit::content::{glob_markdown, MarkdownComponents, MarkdownOptions}; 1 + use maudit::content::{glob_markdown_with_options, MarkdownComponents, MarkdownOptions}; 2 2 use maudit::{content_sources, coronate, routes, BuildOptions, BuildOutput}; 3 3 4 4 mod components; ··· 11 11 coronate( 12 12 routes![IndexPage], 13 13 content_sources![ 14 - "examples" => glob_markdown::<ComponentExample>("content/*.md", Some( 14 + "examples" => glob_markdown_with_options::<ComponentExample>("content/*.md", 15 15 MarkdownOptions::with_components( 16 16 MarkdownComponents::new() 17 17 .heading(CustomHeading) ··· 32 32 .table_head(CustomTableHead) 33 33 .table_row(CustomTableRow) 34 34 .table_cell(CustomTableCell), Default::default() 35 - ) 36 35 )) 37 36 ], 38 37 BuildOptions::default(),
+15 -7
website/content/docs/content.md
··· 15 15 Content sources are defined in the coronate entry point through the `content_sources!` macro. 16 16 17 17 ```rs 18 - use maudit::content::content_sources; 18 + use maudit::content::{content_sources, markdown_entry, glob_markdown}; 19 19 20 20 #[markdown_entry] 21 21 pub struct BlogPost { ··· 30 30 ], 31 31 content_sources![ 32 32 "source_name" => loader(...), 33 - "another_source" => glob_markdown<BlogPost>("path/to/files/*.md", None) 33 + "another_source" => glob_markdown::<BlogPost>("path/to/files/*.md") 34 34 ], 35 35 Default::default() 36 36 ); 37 37 } 38 38 ``` 39 39 40 - Where `loader` and `glob_markdown` are functions returning a Vec of `Entry`. Typically, a loader also accepts a type argument specifying the shape of the data for each entries it returns, which will be used inside your pages to provide typed content. 40 + Where `loader` and `glob_markdown` are functions returning a Vec of `Entry`. 41 + 42 + Typically, a loader also accepts a type argument specifying the shape of the data for each entries it returns, which will be used inside your pages to provide typed content. 41 43 42 44 ## Using a content source in pages 43 45 ··· 89 91 pub section: Option<DocsSection>, 90 92 } 91 93 92 - "docs" => glob_markdown::<DocsContent>("content/docs/*.md", None) 94 + "docs" => glob_markdown::<DocsContent>("content/docs/*.md") 93 95 ``` 94 96 95 - This loader take a glob pattern (compatible with [the `glob` crate](https://github.com/rust-lang/glob)) as its first argument, and an optional `MarkdownOptions` struct as its second argument to customise Markdown rendering. The frontmatter of each Markdown file will be deserialized using [Serde](https://serde.rs) into the type argument provided to `glob_markdown`, which can use the `#[markdown_entry]` macro to derive the necessary traits and add the necessary properties to the struct. Note that using this feature require the installation of Serde into your project as the macro uses Serde's derive macros. 97 + This loader take a glob pattern (compatible with [the `glob` crate](https://github.com/rust-lang/glob)) as its sole argument. 98 + 99 + The frontmatter of each Markdown file will be deserialized using [Serde](https://serde.rs) into the type argument provided to `glob_markdown`, which can use the `#[markdown_entry]` macro to derive the necessary traits and add the necessary properties to the struct. Note that using this feature require the installation of Serde into your project as the macro uses Serde's derive macros. 100 + 101 + ##### Markdown options 102 + 103 + Markdown rendering can be customized by using [`glob_markdown_with_options`](https://docs.rs/maudit/latest/maudit/content/markdown/fn.glob_markdown_with_options.html), which takes an additional [`MarkdownOptions`](https://docs.rs/maudit/latest/maudit/content/markdown/struct.MarkdownOptions.html) argument. See the [Markdown rendering](#markdown-rendering) section for more details. 96 104 97 105 ### Custom loaders 98 106 ··· 225 233 // ... 226 234 ], 227 235 content_sources![ 228 - "blog" => glob_markdown::<BlogPost>("content/blog/**/*.md", Some(create_markdown_options())), 236 + "blog" => glob_markdown_with_options::<BlogPost>("content/blog/**/*.md", create_markdown_options()), 229 237 ], 230 238 ..Default::default() 231 239 ); ··· 279 287 // ... 280 288 ], 281 289 content_sources![ 282 - "blog" => glob_markdown::<BlogPost>("content/blog/**/*.md", Some(create_markdown_options())), 290 + "blog" => glob_markdown_with_options::<BlogPost>("content/blog/**/*.md", create_markdown_options()), 283 291 ], 284 292 ..Default::default(), 285 293 );
+1 -1
website/content/docs/index.md
··· 2 2 title: "Prologue" 3 3 --- 4 4 5 - Welcome to the Maudit documentation! Maudit (pronounced /mo.di/, meaning _cursed_ in French) is a static site generator. 5 + Welcome to the Maudit documentation! Maudit (pronounced `/mo.di/`, meaning _cursed_ in French) is [a Rust library](/docs/philosophy/#maudit-is-a-library-not-a-framework) for generating static websites. 6 6 7 7 [Static site generators](https://en.wikipedia.org/wiki/Static_site_generator) are tools that take a collection of files and convert them into a website, once in a build step. This is in contrast to dynamic websites, which are generated on-the-fly by a server. Other similar tools to Maudit include [Jekyll](https://jekyllrb.com), [Hugo](https://gohugo.io), [Astro](https://astro.build), [Eleventy](https://www.11ty.dev), [Zola](https://www.getzola.org) and [many more](https://jamstack.org/generators/). 8 8
+1 -1
website/content/docs/library.md
··· 12 12 13 13 ## Function signature 14 14 15 - The built-in `coronate` function takes a list of routes (which all implements the [FullRoute](https://docs.rs/maudit/latest/maudit/page/trait.FullRoute.html) trait), content sources, and some build options. We'll do the same. 15 + The built-in `coronate` function takes a list of routes (which all implements the [FullRoute](https://docs.rs/maudit/latest/maudit/route/trait.FullRoute.html) trait), [content sources](/docs/content), and some build options. We'll do the same. 16 16 17 17 ```rs 18 18 use maudit::{
+5 -3
website/src/content.rs
··· 1 1 use maud::{PreEscaped, Render}; 2 - use maudit::content::{glob_markdown, markdown_entry, ContentSources, MarkdownOptions}; 2 + use maudit::content::{ 3 + glob_markdown, glob_markdown_with_options, markdown_entry, ContentSources, MarkdownOptions, 4 + }; 3 5 use maudit::content_sources; 4 6 use serde::Deserialize; 5 7 ··· 39 41 } 40 42 41 43 pub fn content_sources() -> ContentSources { 42 - content_sources!["docs" => glob_markdown::<DocsContent>("content/docs/*.md", Some(MarkdownOptions { 44 + content_sources!["docs" => glob_markdown_with_options::<DocsContent>("content/docs/*.md", MarkdownOptions { 43 45 highlight_theme: "base16-eighties.dark".into(), 44 46 ..Default::default() 45 - })), "news" => glob_markdown::<NewsContent>("content/news/*.md", None)] 47 + }), "news" => glob_markdown::<NewsContent>("content/news/*.md")] 46 48 }
+2
website/src/routes/index.rs
··· 72 72 73 73 div.h-12.bg-linear-to-b."from-darker-white".border-t.border-t-borders{} 74 74 75 + h3.text-4xl.block.mb-12.mt-6.px-12.lg:container.mx-auto { "The court's library, not its king." } 76 + 75 77 }, 76 78 true, 77 79 true,