Rust library to generate static websites
5
fork

Configure Feed

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

doc(framework): start our API reference (#3)

* doc(framework): start our API reference

* apply review

authored by

Goulven CLEC'H and committed by
GitHub
ddcff780 0f8d8d98

+656 -6
+17 -1
README.md
··· 2 2 3 3 > A dire coronation, a situation where nobility, power, or status becomes inextricably tied to disastrous circumstances. 4 4 5 - Maudit is a framework for generating static websites. 5 + A library for generating static websites with Rust. 6 + 7 + ## Quick links 8 + 9 + 🌍 Visit our [website](https://maudit.org) to read the [documentation](https://maudit.org/docs) and our [news](https://maudit.org/blog) 10 + 11 + 📦 See the [crate](https://crates.io/crates/maudit) on Crates.io, and the [reference](https://docs.rs/maudit/latest/maudit/) on Docs.rs. 12 + 13 + 🐛 [Report a bug](https://github.com/web-lsp/maudit/issues), please read our [contributing guidelines](#) and [code of conduct](#) first. 14 + 15 + 🚨 [Report a security vulnerability](#), and be sure to review our [security policy](#). 16 + 17 + 💬 Join the discussion on [Github](https://github.com/web-lsp/maudit/discussions) and [Discord](https://maudit.org/chat/), if you have any questions, ideas, or suggestions. 18 + 19 + ## Crates 20 + 21 + *Work in progress.*
+5 -1
crates/framework/Cargo.toml
··· 1 1 [package] 2 2 name = "maudit" 3 - description = "Framework for generating static websites." 3 + description = "Library for generating static websites." 4 4 version = "0.2.0" 5 5 license = "MIT" 6 6 edition = "2021" ··· 8 8 [features] 9 9 default = ["maud"] 10 10 maud = ["dep:maud"] 11 + 12 + [package.metadata.docs.rs] 13 + all-features = true 14 + rustdoc-args = ["--cfg", "docsrs"] 11 15 12 16 [dependencies] 13 17 # Optional
+5
crates/framework/src/build/metadata.rs
··· 2 2 3 3 use rustc_hash::FxHashMap; 4 4 5 + /// Metadata returned by [`coronate()`](crate::coronate) for a single page after a successful build. 5 6 #[derive(Debug)] 6 7 pub struct PageOutput { 7 8 pub route: String, ··· 9 10 pub params: Option<FxHashMap<String, String>>, 10 11 } 11 12 13 + /// Metadata returned by [`coronate()`](crate::coronate) for a single static asset after a successful build. 14 + /// 15 + /// A static asset is a file that is copied to the output directory without any processing. 12 16 #[derive(Debug)] 13 17 pub struct StaticAssetOutput { 14 18 pub file_path: String, 15 19 pub original_path: String, 16 20 } 17 21 22 + /// Metadata returned by [`coronate()`](crate::coronate) after a successful build. 18 23 #[derive(Debug)] 19 24 pub struct BuildOutput { 20 25 pub start_time: SystemTime,
+52
crates/framework/src/build/options.rs
··· 1 + /// Maudit build options. Should be passed to [`coronate()`](crate::coronate()). 2 + /// 3 + /// ## Examples 4 + /// Default values: 5 + /// ```rust 6 + /// use maudit::{ 7 + /// content_sources, coronate, routes, BuildOptions, BuildOutput, 8 + /// }; 9 + /// 10 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 11 + /// coronate( 12 + /// routes![], 13 + /// content_sources![], 14 + /// BuildOptions::default(), 15 + /// ) 16 + /// } 17 + /// ``` 18 + /// Custom values: 19 + /// ```rust 20 + /// use maudit::{ 21 + /// content_sources, coronate, routes, BuildOptions, BuildOutput, 22 + /// }; 23 + /// 24 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 25 + /// coronate( 26 + /// routes![], 27 + /// content_sources![], 28 + /// BuildOptions { 29 + /// output_dir: "public".to_string(), 30 + /// assets_dir: "_assets".to_string(), 31 + /// static_dir: "static".to_string(), 32 + /// tailwind_binary_path: "./node_modules/.bin/tailwindcss".to_string(), 33 + /// }, 34 + /// ) 35 + /// } 36 + /// ``` 1 37 pub struct BuildOptions { 2 38 pub output_dir: String, 3 39 pub assets_dir: String, ··· 5 41 pub tailwind_binary_path: String, 6 42 } 7 43 44 + /// Provides default values for [`crate::coronate()`]. Designed to work for most projects. 45 + /// 46 + /// ## Examples 47 + /// ```rust 48 + /// use maudit::{ 49 + /// content_sources, coronate, routes, BuildOptions, BuildOutput, 50 + /// }; 51 + /// 52 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 53 + /// coronate( 54 + /// routes![], 55 + /// content_sources![], 56 + /// BuildOptions::default(), 57 + /// ) 58 + /// } 59 + /// ``` 8 60 impl Default for BuildOptions { 9 61 fn default() -> Self { 10 62 Self {
+282
crates/framework/src/content.rs
··· 1 + //! Core functions and structs to define the content sources of your website. 2 + //! 3 + //! Content sources represent the content of your website, such as articles, blog posts, etc. Then, content sources can be passed to [`coronate()`](crate::coronate), through the [`content_sources!`](crate::content_sources) macro, to be loaded. Typically used in [`DynamicRoute`](crate::page::DynamicRoute). 1 4 use std::{any::Any, path::PathBuf}; 2 5 3 6 use glob::glob as glob_fs; ··· 7 10 use serde::de::DeserializeOwned; 8 11 9 12 use crate::page::RouteParams; 13 + 14 + /// Helps implement a struct as a Markdown content entry. 15 + /// 16 + /// ## Example 17 + /// ```rust 18 + /// use maudit::{coronate, content_sources, routes, BuildOptions, BuildOutput}; 19 + /// use maudit::content::{markdown_entry, glob_markdown}; 20 + /// 21 + /// #[markdown_entry] 22 + /// pub struct ArticleContent { 23 + /// pub title: String, 24 + /// pub description: String, 25 + /// } 26 + /// 27 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 28 + /// coronate( 29 + /// routes![], 30 + /// content_sources![ 31 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 32 + /// ], 33 + /// BuildOptions::default(), 34 + /// ) 35 + /// } 36 + /// ``` 37 + /// 38 + /// ## Expand 39 + /// ```rust 40 + /// #[markdown_entry] 41 + /// pub struct Article { 42 + /// pub title: String, 43 + /// pub content: String, 44 + /// } 45 + /// ``` 46 + /// expands to 47 + /// ```rust 48 + /// #[derive(serde::Deserialize)] 49 + /// pub struct Article { 50 + /// pub title: String, 51 + /// pub content: String, 52 + /// #[serde(skip)] 53 + /// __internal_headings: Vec<maudit::content::MarkdownHeading> 54 + /// } 55 + /// 56 + /// impl maudit::content::MarkdownContent for Article { 57 + /// fn get_headings(&self) -> &Vec<maudit::content::MarkdownHeading> { 58 + /// &self.__internal_headings 59 + /// } 60 + /// } 61 + /// 62 + /// impl maudit::content::InternalMarkdownContent for Article { 63 + /// fn set_headings(&mut self, headings: Vec<maudit::content::MarkdownHeading>) { 64 + /// self.__internal_headings = headings; 65 + /// } 66 + /// } 67 + /// ``` 10 68 pub use maudit_macros::markdown_entry; 11 69 70 + /// Main struct to access all content sources. 71 + /// 72 + /// Can only access content sources that have been defined in [`coronate()`](crate::coronate). 73 + /// 74 + /// ## Example 75 + /// In `main.rs`: 76 + /// ```rust 77 + /// use maudit::{coronate, content_sources, routes, BuildOptions, BuildOutput}; 78 + /// use maudit::content::{markdown_entry, glob_markdown}; 79 + /// 80 + /// #[markdown_entry] 81 + /// pub struct ArticleContent { 82 + /// pub title: String, 83 + /// pub description: String, 84 + /// } 85 + /// 86 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 87 + /// coronate( 88 + /// routes![], 89 + /// content_sources![ 90 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 91 + /// ], 92 + /// BuildOptions::default(), 93 + /// ) 94 + /// } 95 + /// ``` 96 + /// 97 + /// In a page: 98 + /// ```rust 99 + /// use maudit::page::prelude::*; 100 + /// # use maudit::content::markdown_entry; 101 + /// # 102 + /// # #[markdown_entry] 103 + /// # pub struct ArticleContent { 104 + /// # pub title: String, 105 + /// # pub description: String, 106 + /// # } 107 + /// 108 + /// #[route("/articles/[article]")] 109 + /// pub struct Article; 110 + /// 111 + /// #[derive(Params)] 112 + /// pub struct ArticleParams { 113 + /// pub article: String, 114 + /// } 115 + /// 116 + /// impl DynamicRoute<ArticleParams> for Article { 117 + /// fn routes(&self, ctx: &mut DynamicRouteContext) -> Vec<ArticleParams> { 118 + /// let articles = ctx.content.get_source::<ArticleContent>("articles"); 119 + /// 120 + /// articles.into_params(|entry| ArticleParams { 121 + /// article: entry.id.clone(), 122 + /// }) 123 + /// } 124 + /// } 125 + /// 126 + /// impl Page for Article { 127 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 128 + /// let params = ctx.params::<ArticleParams>(); 129 + /// let articles = ctx.content.get_source::<ArticleContent>("articles"); 130 + /// let article = articles.get_entry(&params.article); 131 + /// article.render().into() 132 + /// } 133 + /// } 134 + /// ``` 12 135 pub struct Content<'a> { 13 136 sources: &'a [Box<dyn ContentSourceInternal>], 14 137 } ··· 61 184 } 62 185 } 63 186 187 + /// Represents a single entry in a [`ContentSource`]. 188 + /// 189 + /// ## Example 190 + /// ```rust 191 + /// use maudit::page::prelude::*; 192 + /// # use maudit::content::markdown_entry; 193 + /// # 194 + /// # #[markdown_entry] 195 + /// # pub struct ArticleContent { 196 + /// # pub title: String, 197 + /// # pub description: String, 198 + /// # } 199 + /// 200 + /// #[route("/articles/my-article")] 201 + /// pub struct Article; 202 + /// 203 + /// #[derive(Params)] 204 + /// pub struct ArticleParams { 205 + /// pub article: String, 206 + /// } 207 + /// 208 + /// impl Page for Article { 209 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 210 + /// let articles = ctx.content.get_source::<ArticleContent>("articles"); 211 + /// let article = articles.get_entry("my-article"); // returns a ContentEntry 212 + /// article.render().into() 213 + /// } 214 + /// } 215 + /// ``` 64 216 pub struct ContentEntry<T> { 65 217 pub id: String, 66 218 render: Box<dyn Fn(&str) -> String + Send + Sync>, ··· 75 227 } 76 228 } 77 229 230 + /// Represents an untyped content source. 78 231 pub type Untyped = FxHashMap<String, String>; 79 232 233 + /// Represents a collection of content sources. 234 + /// 235 + /// Mostly seen as the return type of [`content_sources!`](crate::content_sources). 236 + /// 237 + /// ## Example 238 + /// ```rust 239 + /// use maudit::page::prelude::*; 240 + /// use maudit::content::{glob_markdown, ContentSources}; 241 + /// use maudit::content_sources; 242 + /// # use maudit::content::markdown_entry; 243 + /// # 244 + /// # #[markdown_entry] 245 + /// # pub struct ArticleContent { 246 + /// # pub title: String, 247 + /// # pub description: String, 248 + /// # } 249 + /// 250 + /// pub fn content_sources() -> ContentSources { 251 + /// content_sources!["docs" => glob_markdown::<ArticleContent>("content/docs/*.md")] 252 + /// } 80 253 pub struct ContentSources(pub Vec<Box<dyn ContentSourceInternal>>); 81 254 82 255 impl From<Vec<Box<dyn ContentSourceInternal>>> for ContentSources { ··· 93 266 94 267 type ContentSourceInitMethod<T> = Box<dyn Fn() -> Vec<ContentEntry<T>> + Send + Sync>; 95 268 269 + /// A source of content such as articles, blog posts, etc. 96 270 pub struct ContentSource<T = Untyped> { 97 271 pub name: String, 98 272 pub entries: Vec<ContentEntry<T>>, ··· 130 304 } 131 305 } 132 306 307 + #[doc(hidden)] 308 + /// Used internally by Maudit and should not be implemented by the user. 309 + /// We expose it because it's implemented for [`ContentSource`], which is public. 133 310 pub trait ContentSourceInternal: Send + Sync { 134 311 fn init(&mut self); 135 312 fn get_name(&self) -> &str; ··· 148 325 } 149 326 } 150 327 328 + /// Represents a Markdown heading. 329 + /// 330 + /// Can be used to generate a table of contents. 331 + /// 332 + /// ## Example 333 + /// ```rust 334 + /// use maudit::page::prelude::*; 335 + /// use maud::{html, Markup}; 336 + /// # use maudit::content::markdown_entry; 337 + /// # 338 + /// # #[markdown_entry] 339 + /// # pub struct ArticleContent { 340 + /// # pub title: String, 341 + /// # pub description: String, 342 + /// # } 343 + /// 344 + /// #[route("/articles/my-article")] 345 + /// pub struct Article; 346 + /// 347 + /// #[derive(Params)] 348 + /// pub struct ArticleParams { 349 + /// pub article: String, 350 + /// } 351 + /// 352 + /// impl Page<Markup> for Article { 353 + /// fn render(&self, ctx: &mut RouteContext) -> Markup { 354 + /// let articles = ctx.content.get_source::<ArticleContent>("articles"); 355 + /// let article = articles.get_entry("my-article"); 356 + /// let headings = article.data.get_headings(); // returns a Vec<MarkdownHeading> 357 + /// let toc = html! { 358 + /// ul { 359 + /// @for heading in headings { 360 + /// li { 361 + /// a href=(format!("#{}", heading.id)) { (heading.title) } 362 + /// } 363 + /// } 364 + /// } 365 + /// }; 366 + /// html! { 367 + /// main { 368 + /// h1 { (article.data.title) } 369 + /// nav { (toc) } 370 + /// } 371 + /// } 372 + /// } 373 + /// } 374 + /// ``` 151 375 #[derive(Debug, Clone)] 152 376 pub struct MarkdownHeading { 153 377 pub title: String, ··· 157 381 pub attrs: Vec<(String, Option<String>)>, 158 382 } 159 383 384 + #[doc(hidden)] 385 + /// Used internally by Maudit and should not be implemented by the user. 386 + /// We expose it because [`maudit_macros::markdown_entry`] implements it for the user behind the scenes. 160 387 pub trait MarkdownContent { 161 388 fn get_headings(&self) -> &Vec<MarkdownHeading>; 162 389 } 163 390 391 + #[doc(hidden)] 392 + /// Used internally by Maudit and should not be implemented by the user. 393 + /// We expose it because [`maudit_macros::markdown_entry`] implements it for the user behind the scenes. 164 394 pub trait InternalMarkdownContent { 165 395 fn set_headings(&mut self, headings: Vec<MarkdownHeading>); 166 396 } 167 397 398 + /// Represents untyped Markdown content. 399 + /// 400 + /// Assumes that the Markdown content has no frontmatter. 401 + /// 402 + /// ## Example 403 + /// ```rust 404 + /// use maudit::{coronate, content_sources, routes, BuildOptions, BuildOutput}; 405 + /// use maudit::content::{glob_markdown, UntypedMarkdownContent}; 406 + /// 407 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 408 + /// coronate( 409 + /// routes![], 410 + /// content_sources![ 411 + /// "articles" => glob_markdown::<UntypedMarkdownContent>("content/spooky/*.md") 412 + /// ], 413 + /// BuildOptions::default(), 414 + /// ) 415 + /// } 416 + /// ``` 168 417 #[derive(serde::Deserialize)] 169 418 pub struct UntypedMarkdownContent { 170 419 #[serde(skip)] ··· 183 432 } 184 433 } 185 434 435 + /// Glob for Markdown files and return a vector of [`ContentEntry`]s. 436 + /// 437 + /// Typically used by [`content_sources!`](crate::content_sources) to define a Markdown content source in [`coronate()`](crate::coronate). 438 + /// 439 + /// ## Example 440 + /// ```rust 441 + /// use maudit::{coronate, content_sources, routes, BuildOptions, BuildOutput}; 442 + /// use maudit::content::{markdown_entry, glob_markdown}; 443 + /// 444 + /// #[markdown_entry] 445 + /// pub struct ArticleContent { 446 + /// pub title: String, 447 + /// pub description: String, 448 + /// } 449 + /// 450 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 451 + /// coronate( 452 + /// routes![], 453 + /// content_sources![ 454 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 455 + /// ], 456 + /// BuildOptions::default(), 457 + /// ) 458 + /// } 459 + /// ``` 186 460 pub fn glob_markdown<T>(pattern: &str) -> Vec<ContentEntry<T>> 187 461 where 188 462 T: DeserializeOwned + MarkdownContent + InternalMarkdownContent, ··· 276 550 entries 277 551 } 278 552 553 + /// Render Markdown content to HTML. 554 + /// 555 + /// ## Example 556 + /// ```rust 557 + /// use maudit::content::render_markdown; 558 + /// let markdown = r#"# Hello, world!"#; 559 + /// let html = render_markdown(markdown); 560 + /// ``` 279 561 pub fn render_markdown(content: &str) -> String { 280 562 let mut html_output = String::new(); 281 563 let mut options = Options::empty();
+1
crates/framework/src/errors.rs
··· 1 + //! Error types for Maudit. 1 2 use std::fmt::{self, Debug, Formatter}; 2 3 use thiserror::Error; 3 4
+109 -1
crates/framework/src/lib.rs
··· 1 + #![cfg_attr(docsrs, feature(doc_cfg))] 2 + #![doc = include_str!("../../../README.md")] 3 + //! 4 + //! <div class="warning"> 5 + //! You are currently reading Maudit API reference. For a more gentle introduction, please refer to our <a href="https://maudit.dev/docs">documentation</a>. 6 + //! </div> 7 + 1 8 // Modules the end-user will interact directly or indirectly with 2 9 mod assets; 3 10 pub mod content; ··· 16 23 mod templating; 17 24 18 25 #[cfg(feature = "maud")] 26 + #[cfg_attr(docsrs, doc(cfg(feature = "maud")))] 19 27 pub mod maud { 28 + //! Allows to use [Maud](https://maud.lambda.xyz), a macro for writing HTML templates in Rust. 29 + //! 30 + //! Maudit supports Maud by default, but you can use your own templating engine. 31 + //! 32 + //! ## Example 33 + //! ```rust 34 + //! use maudit::page::prelude::*; 35 + //! use maud::{html, Markup}; 36 + //! 37 + //! #[route("/")] 38 + //! pub struct Index; 39 + //! 40 + //! impl Page<Markup> for Index { 41 + //! fn render(&self, ctx: &mut RouteContext) -> Markup { 42 + //! html! { 43 + //! h1 { "Hello, world!" } 44 + //! } 45 + //! } 46 + //! } 47 + //! ``` 20 48 pub use crate::templating::maud_ext::*; 21 49 } 22 50 ··· 31 59 use page::FullPage; 32 60 33 61 #[macro_export] 62 + /// Helps to define every route that should be build by [`coronate()`]. 63 + /// 64 + /// ## Example 65 + /// ```rust 66 + /// use maudit::{ 67 + /// content_sources, coronate, routes, BuildOptions, BuildOutput, 68 + /// } 69 + /// use crate::pages::{Index, Article}; 70 + /// 71 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 72 + /// coronate( 73 + /// routes![pages::Index, pages::Article], 74 + /// content_sources![], 75 + /// BuildOptions::default(), 76 + /// ) 77 + /// } 78 + /// ``` 79 + /// 80 + /// ## Expand 81 + /// ```rust 82 + /// routes![pages::Index, pages::Article] 83 + /// ``` 84 + /// expands to 85 + /// ```rust 86 + /// &[ 87 + /// &pages::Index, 88 + /// &pages::Article, 89 + /// ] 90 + /// ``` 91 + /// 34 92 macro_rules! routes { 35 93 [$($route:path),*] => { 36 94 &[$(&$route),*] 37 95 }; 38 96 } 39 97 98 + /// Helps to define all sources of content that should be loaded by [`coronate()`]. 99 + /// 100 + /// ## Example 101 + /// ```rust 102 + /// use maudit::{ 103 + /// content_sources, coronate, routes, BuildOptions, BuildOutput, 104 + /// } 105 + /// use crate::content::ArticleContent; 106 + /// 107 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 108 + /// coronate( 109 + /// routes![], 110 + /// content_sources![ 111 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 112 + /// ], 113 + /// BuildOptions::default(), 114 + /// ) 115 + /// } 116 + /// ``` 117 + /// 118 + /// ## Expand 119 + /// ```rust 120 + /// content_sources![ 121 + /// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md") 122 + /// ] 123 + /// ``` 124 + /// expands to 125 + /// ```rust 126 + /// maudit::content::ContentSources(vec![ 127 + /// Box::new(maudit::content::ContentSource::new("articles", Box::new(move || glob_markdown::<ArticleContent>("content/articles/*.md")))) 128 + /// ]) 40 129 #[macro_export] 41 130 macro_rules! content_sources { 42 131 ($($name:expr => $entries:expr),*) => { 43 132 maudit::content::ContentSources(vec![$(Box::new(maudit::content::ContentSource::new($name, Box::new(move || $entries)))),*]) 44 133 }; 45 134 } 46 - 135 + /// The version of Maudit being used. 136 + /// 137 + /// Can be used to create a generator tag in the output HTML. 47 138 pub const GENERATOR: &str = concat!("Maudit v", env!("CARGO_PKG_VERSION")); 48 139 140 + /// 👑 Maudit entrypoint. Starts the build process and generates the output files. 141 + /// 142 + /// ## Example 143 + /// Should be called from the main function of the binary crate. 144 + /// ```rust 145 + /// use maudit::{ 146 + /// content_sources, coronate, routes, BuildOptions, BuildOutput, 147 + /// } 148 + /// 149 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 150 + /// coronate( 151 + /// routes![], 152 + /// content_sources![], 153 + /// BuildOptions::default(), 154 + /// ) 155 + /// } 156 + /// ``` 49 157 pub fn coronate( 50 158 routes: &[&dyn FullPage], 51 159 mut content_sources: ContentSources,
+164 -2
crates/framework/src/page.rs
··· 1 + //! Core traits and structs to define the pages of your website. 2 + //! 3 + //! Every page must implement the [`Page`] trait, and optionally the [`DynamicRoute`] trait. Then, pages can be passed to [`coronate()`](crate::coronate), through the [`routes!`](crate::routes) macro, to be built. 1 4 use crate::assets::PageAssets; 2 5 use crate::content::Content; 3 6 use rustc_hash::FxHashMap; 4 7 use std::path::PathBuf; 5 8 9 + /// Represents the result of a page render, can be either text or raw bytes. 10 + /// 11 + /// Typically used through the [`Into<RenderResult>`](std::convert::Into) and [`From<RenderResult>`](std::convert::From) implementations for common types. 12 + /// End users should rarely need to interact with this enum directly. 13 + /// 14 + /// ## Example 15 + /// ```rust 16 + /// use maudit::page::prelude::*; 17 + /// 18 + /// #[route("/")] 19 + /// pub struct Index; 20 + /// 21 + /// impl Page for Index { 22 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 23 + /// "<h1>Hello, world!</h1>".into() 24 + /// } 25 + /// } 26 + /// ``` 6 27 pub enum RenderResult { 7 28 Text(String), 8 29 Raw(Vec<u8>), ··· 38 59 } 39 60 } 40 61 62 + /// Allows to access various data and assets in a [`Page`] implementation. 63 + /// 64 + /// ## Example 65 + /// ```rust 66 + /// use maudit::page::prelude::*; 67 + /// use maud::html; 68 + /// # use maudit::content::markdown_entry; 69 + /// # 70 + /// # #[markdown_entry] 71 + /// # pub struct ArticleContent { 72 + /// # pub title: String, 73 + /// # pub description: String, 74 + /// # } 75 + /// 76 + /// #[route("/")] 77 + /// pub struct Index; 78 + /// 79 + /// impl Page for Index { 80 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 81 + /// let logo = ctx.assets.add_image("logo.png"); 82 + /// let last_entries = &ctx.content.get_source::<ArticleContent>("articles").entries; 83 + /// html! { 84 + /// main { 85 + /// (logo) 86 + /// ul { 87 + /// @for entry in last_entries { 88 + /// li { (entry.data.title) } 89 + /// } 90 + /// } 91 + /// } 92 + /// }.into() 93 + /// } 94 + /// } 41 95 pub struct RouteContext<'a> { 42 96 pub raw_params: &'a RouteParams, 43 97 pub content: &'a Content<'a>, ··· 54 108 } 55 109 } 56 110 111 + /// Allows to access the content source in a [`DynamicRoute`] implementation. 112 + /// 113 + /// ## Example 114 + /// ```rust 115 + /// use maudit::page::prelude::*; 116 + /// # use maudit::content::markdown_entry; 117 + /// # 118 + /// # #[markdown_entry] 119 + /// # pub struct ArticleContent { 120 + /// # pub title: String, 121 + /// # pub description: String, 122 + /// # } 123 + /// 124 + /// #[route("/articles/[article]")] 125 + /// pub struct Article; 126 + /// 127 + /// #[derive(Params)] 128 + /// pub struct ArticleParams { 129 + /// pub article: String, 130 + /// } 131 + /// 132 + /// impl DynamicRoute<ArticleParams> for Article { 133 + /// fn routes(&self, ctx: &mut DynamicRouteContext) -> Vec<ArticleParams> { 134 + /// let articles = ctx.content.get_source::<ArticleContent>("articles"); 135 + /// 136 + /// articles.into_params(|entry| ArticleParams { 137 + /// article: entry.id.clone(), 138 + /// }) 139 + /// } 140 + /// } 141 + /// 142 + /// impl Page for Article { 143 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 144 + /// let params = ctx.params::<ArticleParams>(); 145 + /// let articles = ctx.content.get_source::<ArticleContent>("articles"); 146 + /// let article = articles.get_entry(&params.article); 147 + /// article.render().into() 148 + /// } 149 + /// } 150 + /// ``` 57 151 pub struct DynamicRouteContext<'a> { 58 152 pub content: &'a mut Content<'a>, 59 153 } 60 154 155 + /// Must be implemented for every page of your website. 156 + /// 157 + /// The page struct implementing this trait can be passed to [`coronate()`](crate::coronate), through the [`routes!`](crate::routes) macro, to be built. 158 + /// 159 + /// ## Example 160 + /// ```rust 161 + /// use maudit::page::prelude::*; 162 + /// 163 + /// #[route("/")] 164 + /// pub struct Index; 165 + /// 166 + /// impl Page for Index { 167 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 168 + /// "<h1>Hello, world!</h1>".into() 169 + /// } 170 + /// } 171 + /// ``` 61 172 pub trait Page<T = RenderResult> 62 173 where 63 174 T: Into<RenderResult>, ··· 65 176 fn render(&self, ctx: &mut RouteContext) -> T; 66 177 } 67 178 179 + /// Raw representation of a route's parameters. 180 + /// 181 + /// Can be accessed through [`RouteContext`]'s `raw_params`. 68 182 #[derive(Clone, Default, Debug)] 69 183 pub struct RouteParams(pub FxHashMap<String, String>); 70 184 ··· 97 211 } 98 212 } 99 213 214 + /// Must be implemented for every dynamic route of your website. 215 + /// 216 + /// Dynamic route allows creating many pages that share the same structure and logic, but with different content. Typically used for a [`ContentSource`](crate::content::ContentSource). 217 + /// 218 + /// ## Example 219 + /// ```rust 220 + /// use maudit::page::prelude::*; 221 + /// 222 + /// #[route("/tags/[id]")] 223 + /// pub struct Tags; 224 + /// 225 + /// #[derive(Params)] 226 + /// struct Params { 227 + /// id: String, 228 + /// } 229 + /// 230 + /// impl DynamicRoute for Tags { 231 + /// fn routes(&self, context: &mut DynamicRouteContext) -> Vec<RouteParams> { 232 + /// let tags = vec!["rust", "web", "programming"].iter().map(|tag| Params { id: tag.to_string() }).collect(); 233 + /// RouteParams::from_vec(tags) 234 + /// } 235 + /// } 236 + /// 237 + /// impl Page for Tags { 238 + /// fn render(&self, ctx: &mut RouteContext) -> RenderResult { 239 + /// let tag = ctx.params::<Params>().id; 240 + /// format!("<h1>Tag: {}</h1>", tag).into() 241 + /// } 242 + /// } 243 + /// ``` 100 244 pub trait DynamicRoute<P = RouteParams> 101 245 where 102 246 P: Into<RouteParams>, ··· 106 250 fn routes(&self, context: &mut DynamicRouteContext) -> Vec<P>; 107 251 } 108 252 253 + #[doc(hidden)] 254 + /// Used internally by Maudit and should not be implemented by the user. 255 + /// We expose it because [`maudit_macros::route`] implements it for the user behind the scenes. 109 256 pub enum RouteType { 110 257 Static, 111 258 Dynamic, 112 259 } 113 260 261 + #[doc(hidden)] 262 + /// Used internally by Maudit and should not be implemented by the user. 263 + /// We expose it because the derive macro implements it for the user behind the scenes. 114 264 pub trait InternalPage { 115 265 fn route_type(&self) -> RouteType; 116 266 fn route_raw(&self) -> String; ··· 122 272 fn url_untyped(&self, params: &RouteParams) -> String; 123 273 } 124 274 275 + #[doc(hidden)] 276 + /// Used internally by Maudit and should not be implemented by the user. 277 + /// We expose it because [`maudit_macros::route`] implements it for the user behind the scenes. 125 278 pub trait FullPage: InternalPage + Sync { 126 279 fn render_internal(&self, ctx: &mut RouteContext) -> RenderResult; 127 280 fn routes_internal(&self, context: &mut DynamicRouteContext) -> Vec<RouteParams>; 128 281 } 129 282 130 283 pub mod prelude { 284 + //! Re-exports of the most commonly used types and traits for defining pages. 285 + //! 286 + //! This module is meant to be glob imported in your page files. 287 + //! 288 + //! ## Example 289 + //! ```rust 290 + //! use maudit::page::prelude::*; 291 + //! ``` 131 292 pub use super::{ 132 - DynamicRoute, DynamicRouteContext, FullPage, InternalPage, Page, RenderResult, 133 - RouteContext, RouteParams, 293 + DynamicRoute, DynamicRouteContext, Page, RenderResult, RouteContext, RouteParams, 134 294 }; 295 + #[doc(hidden)] 296 + pub use super::{FullPage, InternalPage}; 135 297 pub use crate::assets::Asset; 136 298 pub use crate::content::MarkdownContent; 137 299 pub use maudit_macros::{route, Params};
+17 -1
crates/framework/src/params.rs
··· 1 + //! This module provides a trait for parsing path parameters. 1 2 // Adapted from https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/core/lib/src/request/from_param.rs 2 3 // See https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/LICENSE-MIT for license information 3 - 4 4 use std::str::FromStr; 5 5 6 + /// Convert a path parameter string into a type. 7 + /// 8 + /// ## Example 9 + /// ```rust 10 + /// use maudit::params::FromParam; 11 + /// 12 + /// struct UserId(String); 13 + /// 14 + /// impl FromParam for UserId { 15 + /// type Error = std::io::Empty; 16 + /// 17 + /// fn from_param(param: &str) -> Result<Self, Self::Error> { 18 + /// Ok(UserId(param.to_string())) 19 + /// } 20 + /// } 21 + /// ``` 6 22 pub trait FromParam: Sized { 7 23 /// The associated error to be returned if parsing/validation fails. 8 24 type Error: std::fmt::Debug;
+1
crates/framework/src/templating/maud_ext.rs
··· 29 29 } 30 30 } 31 31 32 + /// Can be used to create a generator tag in the output HTML. See [`GENERATOR`](crate::GENERATOR). 32 33 pub fn generator() -> Markup { 33 34 html! { 34 35 meta name="generator" content=(GENERATOR);
+3
crates/macros/src/lib.rs
··· 247 247 } 248 248 249 249 #[proc_macro_attribute] 250 + // Helps implement a struct as a Markdown content entry. 251 + // 252 + // See complete documentation in `crates/framework/src/content.rs`. 250 253 pub fn markdown_entry(args: TokenStream, item: TokenStream) -> TokenStream { 251 254 let mut item_struct = syn::parse_macro_input!(item as ItemStruct); 252 255 let _ = parse_macro_input!(args as parse::Nothing);