···2233> A dire coronation, a situation where nobility, power, or status becomes inextricably tied to disastrous circumstances.
4455-Maudit is a framework for generating static websites.
55+A library for generating static websites with Rust.
66+77+## Quick links
88+99+🌍 Visit our [website](https://maudit.org) to read the [documentation](https://maudit.org/docs) and our [news](https://maudit.org/blog)
1010+1111+📦 See the [crate](https://crates.io/crates/maudit) on Crates.io, and the [reference](https://docs.rs/maudit/latest/maudit/) on Docs.rs.
1212+1313+🐛 [Report a bug](https://github.com/web-lsp/maudit/issues), please read our [contributing guidelines](#) and [code of conduct](#) first.
1414+1515+🚨 [Report a security vulnerability](#), and be sure to review our [security policy](#).
1616+1717+💬 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.
1818+1919+## Crates
2020+2121+*Work in progress.*
···2233use rustc_hash::FxHashMap;
4455+/// Metadata returned by [`coronate()`](crate::coronate) for a single page after a successful build.
56#[derive(Debug)]
67pub struct PageOutput {
78 pub route: String,
···910 pub params: Option<FxHashMap<String, String>>,
1011}
11121313+/// Metadata returned by [`coronate()`](crate::coronate) for a single static asset after a successful build.
1414+///
1515+/// A static asset is a file that is copied to the output directory without any processing.
1216#[derive(Debug)]
1317pub struct StaticAssetOutput {
1418 pub file_path: String,
1519 pub original_path: String,
1620}
17212222+/// Metadata returned by [`coronate()`](crate::coronate) after a successful build.
1823#[derive(Debug)]
1924pub struct BuildOutput {
2025 pub start_time: SystemTime,
···11+#![cfg_attr(docsrs, feature(doc_cfg))]
22+#![doc = include_str!("../../../README.md")]
33+//!
44+//! <div class="warning">
55+//! You are currently reading Maudit API reference. For a more gentle introduction, please refer to our <a href="https://maudit.dev/docs">documentation</a>.
66+//! </div>
77+18// Modules the end-user will interact directly or indirectly with
29mod assets;
310pub mod content;
···1623mod templating;
17241825#[cfg(feature = "maud")]
2626+#[cfg_attr(docsrs, doc(cfg(feature = "maud")))]
1927pub mod maud {
2828+ //! Allows to use [Maud](https://maud.lambda.xyz), a macro for writing HTML templates in Rust.
2929+ //!
3030+ //! Maudit supports Maud by default, but you can use your own templating engine.
3131+ //!
3232+ //! ## Example
3333+ //! ```rust
3434+ //! use maudit::page::prelude::*;
3535+ //! use maud::{html, Markup};
3636+ //!
3737+ //! #[route("/")]
3838+ //! pub struct Index;
3939+ //!
4040+ //! impl Page<Markup> for Index {
4141+ //! fn render(&self, ctx: &mut RouteContext) -> Markup {
4242+ //! html! {
4343+ //! h1 { "Hello, world!" }
4444+ //! }
4545+ //! }
4646+ //! }
4747+ //! ```
2048 pub use crate::templating::maud_ext::*;
2149}
2250···3159use page::FullPage;
32603361#[macro_export]
6262+/// Helps to define every route that should be build by [`coronate()`].
6363+///
6464+/// ## Example
6565+/// ```rust
6666+/// use maudit::{
6767+/// content_sources, coronate, routes, BuildOptions, BuildOutput,
6868+/// }
6969+/// use crate::pages::{Index, Article};
7070+///
7171+/// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> {
7272+/// coronate(
7373+/// routes![pages::Index, pages::Article],
7474+/// content_sources![],
7575+/// BuildOptions::default(),
7676+/// )
7777+/// }
7878+/// ```
7979+///
8080+/// ## Expand
8181+/// ```rust
8282+/// routes![pages::Index, pages::Article]
8383+/// ```
8484+/// expands to
8585+/// ```rust
8686+/// &[
8787+/// &pages::Index,
8888+/// &pages::Article,
8989+/// ]
9090+/// ```
9191+///
3492macro_rules! routes {
3593 [$($route:path),*] => {
3694 &[$(&$route),*]
3795 };
3896}
39979898+/// Helps to define all sources of content that should be loaded by [`coronate()`].
9999+///
100100+/// ## Example
101101+/// ```rust
102102+/// use maudit::{
103103+/// content_sources, coronate, routes, BuildOptions, BuildOutput,
104104+/// }
105105+/// use crate::content::ArticleContent;
106106+///
107107+/// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> {
108108+/// coronate(
109109+/// routes![],
110110+/// content_sources![
111111+/// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md")
112112+/// ],
113113+/// BuildOptions::default(),
114114+/// )
115115+/// }
116116+/// ```
117117+///
118118+/// ## Expand
119119+/// ```rust
120120+/// content_sources![
121121+/// "articles" => glob_markdown::<ArticleContent>("content/articles/*.md")
122122+/// ]
123123+/// ```
124124+/// expands to
125125+/// ```rust
126126+/// maudit::content::ContentSources(vec![
127127+/// Box::new(maudit::content::ContentSource::new("articles", Box::new(move || glob_markdown::<ArticleContent>("content/articles/*.md"))))
128128+/// ])
40129#[macro_export]
41130macro_rules! content_sources {
42131 ($($name:expr => $entries:expr),*) => {
43132 maudit::content::ContentSources(vec![$(Box::new(maudit::content::ContentSource::new($name, Box::new(move || $entries)))),*])
44133 };
45134}
4646-135135+/// The version of Maudit being used.
136136+///
137137+/// Can be used to create a generator tag in the output HTML.
47138pub const GENERATOR: &str = concat!("Maudit v", env!("CARGO_PKG_VERSION"));
48139140140+/// 👑 Maudit entrypoint. Starts the build process and generates the output files.
141141+///
142142+/// ## Example
143143+/// Should be called from the main function of the binary crate.
144144+/// ```rust
145145+/// use maudit::{
146146+/// content_sources, coronate, routes, BuildOptions, BuildOutput,
147147+/// }
148148+///
149149+/// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> {
150150+/// coronate(
151151+/// routes![],
152152+/// content_sources![],
153153+/// BuildOptions::default(),
154154+/// )
155155+/// }
156156+/// ```
49157pub fn coronate(
50158 routes: &[&dyn FullPage],
51159 mut content_sources: ContentSources,
+164-2
crates/framework/src/page.rs
···11+//! Core traits and structs to define the pages of your website.
22+//!
33+//! 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.
14use crate::assets::PageAssets;
25use crate::content::Content;
36use rustc_hash::FxHashMap;
47use std::path::PathBuf;
5899+/// Represents the result of a page render, can be either text or raw bytes.
1010+///
1111+/// Typically used through the [`Into<RenderResult>`](std::convert::Into) and [`From<RenderResult>`](std::convert::From) implementations for common types.
1212+/// End users should rarely need to interact with this enum directly.
1313+///
1414+/// ## Example
1515+/// ```rust
1616+/// use maudit::page::prelude::*;
1717+///
1818+/// #[route("/")]
1919+/// pub struct Index;
2020+///
2121+/// impl Page for Index {
2222+/// fn render(&self, ctx: &mut RouteContext) -> RenderResult {
2323+/// "<h1>Hello, world!</h1>".into()
2424+/// }
2525+/// }
2626+/// ```
627pub enum RenderResult {
728 Text(String),
829 Raw(Vec<u8>),
···3859 }
3960}
40616262+/// Allows to access various data and assets in a [`Page`] implementation.
6363+///
6464+/// ## Example
6565+/// ```rust
6666+/// use maudit::page::prelude::*;
6767+/// use maud::html;
6868+/// # use maudit::content::markdown_entry;
6969+/// #
7070+/// # #[markdown_entry]
7171+/// # pub struct ArticleContent {
7272+/// # pub title: String,
7373+/// # pub description: String,
7474+/// # }
7575+///
7676+/// #[route("/")]
7777+/// pub struct Index;
7878+///
7979+/// impl Page for Index {
8080+/// fn render(&self, ctx: &mut RouteContext) -> RenderResult {
8181+/// let logo = ctx.assets.add_image("logo.png");
8282+/// let last_entries = &ctx.content.get_source::<ArticleContent>("articles").entries;
8383+/// html! {
8484+/// main {
8585+/// (logo)
8686+/// ul {
8787+/// @for entry in last_entries {
8888+/// li { (entry.data.title) }
8989+/// }
9090+/// }
9191+/// }
9292+/// }.into()
9393+/// }
9494+/// }
4195pub struct RouteContext<'a> {
4296 pub raw_params: &'a RouteParams,
4397 pub content: &'a Content<'a>,
···54108 }
55109}
56110111111+/// Allows to access the content source in a [`DynamicRoute`] implementation.
112112+///
113113+/// ## Example
114114+/// ```rust
115115+/// use maudit::page::prelude::*;
116116+/// # use maudit::content::markdown_entry;
117117+/// #
118118+/// # #[markdown_entry]
119119+/// # pub struct ArticleContent {
120120+/// # pub title: String,
121121+/// # pub description: String,
122122+/// # }
123123+///
124124+/// #[route("/articles/[article]")]
125125+/// pub struct Article;
126126+///
127127+/// #[derive(Params)]
128128+/// pub struct ArticleParams {
129129+/// pub article: String,
130130+/// }
131131+///
132132+/// impl DynamicRoute<ArticleParams> for Article {
133133+/// fn routes(&self, ctx: &mut DynamicRouteContext) -> Vec<ArticleParams> {
134134+/// let articles = ctx.content.get_source::<ArticleContent>("articles");
135135+///
136136+/// articles.into_params(|entry| ArticleParams {
137137+/// article: entry.id.clone(),
138138+/// })
139139+/// }
140140+/// }
141141+///
142142+/// impl Page for Article {
143143+/// fn render(&self, ctx: &mut RouteContext) -> RenderResult {
144144+/// let params = ctx.params::<ArticleParams>();
145145+/// let articles = ctx.content.get_source::<ArticleContent>("articles");
146146+/// let article = articles.get_entry(¶ms.article);
147147+/// article.render().into()
148148+/// }
149149+/// }
150150+/// ```
57151pub struct DynamicRouteContext<'a> {
58152 pub content: &'a mut Content<'a>,
59153}
60154155155+/// Must be implemented for every page of your website.
156156+///
157157+/// The page struct implementing this trait can be passed to [`coronate()`](crate::coronate), through the [`routes!`](crate::routes) macro, to be built.
158158+///
159159+/// ## Example
160160+/// ```rust
161161+/// use maudit::page::prelude::*;
162162+///
163163+/// #[route("/")]
164164+/// pub struct Index;
165165+///
166166+/// impl Page for Index {
167167+/// fn render(&self, ctx: &mut RouteContext) -> RenderResult {
168168+/// "<h1>Hello, world!</h1>".into()
169169+/// }
170170+/// }
171171+/// ```
61172pub trait Page<T = RenderResult>
62173where
63174 T: Into<RenderResult>,
···65176 fn render(&self, ctx: &mut RouteContext) -> T;
66177}
67178179179+/// Raw representation of a route's parameters.
180180+///
181181+/// Can be accessed through [`RouteContext`]'s `raw_params`.
68182#[derive(Clone, Default, Debug)]
69183pub struct RouteParams(pub FxHashMap<String, String>);
70184···97211 }
98212}
99213214214+/// Must be implemented for every dynamic route of your website.
215215+///
216216+/// 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).
217217+///
218218+/// ## Example
219219+/// ```rust
220220+/// use maudit::page::prelude::*;
221221+///
222222+/// #[route("/tags/[id]")]
223223+/// pub struct Tags;
224224+///
225225+/// #[derive(Params)]
226226+/// struct Params {
227227+/// id: String,
228228+/// }
229229+///
230230+/// impl DynamicRoute for Tags {
231231+/// fn routes(&self, context: &mut DynamicRouteContext) -> Vec<RouteParams> {
232232+/// let tags = vec!["rust", "web", "programming"].iter().map(|tag| Params { id: tag.to_string() }).collect();
233233+/// RouteParams::from_vec(tags)
234234+/// }
235235+/// }
236236+///
237237+/// impl Page for Tags {
238238+/// fn render(&self, ctx: &mut RouteContext) -> RenderResult {
239239+/// let tag = ctx.params::<Params>().id;
240240+/// format!("<h1>Tag: {}</h1>", tag).into()
241241+/// }
242242+/// }
243243+/// ```
100244pub trait DynamicRoute<P = RouteParams>
101245where
102246 P: Into<RouteParams>,
···106250 fn routes(&self, context: &mut DynamicRouteContext) -> Vec<P>;
107251}
108252253253+#[doc(hidden)]
254254+/// Used internally by Maudit and should not be implemented by the user.
255255+/// We expose it because [`maudit_macros::route`] implements it for the user behind the scenes.
109256pub enum RouteType {
110257 Static,
111258 Dynamic,
112259}
113260261261+#[doc(hidden)]
262262+/// Used internally by Maudit and should not be implemented by the user.
263263+/// We expose it because the derive macro implements it for the user behind the scenes.
114264pub trait InternalPage {
115265 fn route_type(&self) -> RouteType;
116266 fn route_raw(&self) -> String;
···122272 fn url_untyped(&self, params: &RouteParams) -> String;
123273}
124274275275+#[doc(hidden)]
276276+/// Used internally by Maudit and should not be implemented by the user.
277277+/// We expose it because [`maudit_macros::route`] implements it for the user behind the scenes.
125278pub trait FullPage: InternalPage + Sync {
126279 fn render_internal(&self, ctx: &mut RouteContext) -> RenderResult;
127280 fn routes_internal(&self, context: &mut DynamicRouteContext) -> Vec<RouteParams>;
128281}
129282130283pub mod prelude {
284284+ //! Re-exports of the most commonly used types and traits for defining pages.
285285+ //!
286286+ //! This module is meant to be glob imported in your page files.
287287+ //!
288288+ //! ## Example
289289+ //! ```rust
290290+ //! use maudit::page::prelude::*;
291291+ //! ```
131292 pub use super::{
132132- DynamicRoute, DynamicRouteContext, FullPage, InternalPage, Page, RenderResult,
133133- RouteContext, RouteParams,
293293+ DynamicRoute, DynamicRouteContext, Page, RenderResult, RouteContext, RouteParams,
134294 };
295295+ #[doc(hidden)]
296296+ pub use super::{FullPage, InternalPage};
135297 pub use crate::assets::Asset;
136298 pub use crate::content::MarkdownContent;
137299 pub use maudit_macros::{route, Params};
+17-1
crates/framework/src/params.rs
···11+//! This module provides a trait for parsing path parameters.
12// Adapted from https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/core/lib/src/request/from_param.rs
23// See https://github.com/rwf2/Rocket/blob/28891e8072136f4641a33fb8c3f2aafce9d88d5b/LICENSE-MIT for license information
33-44use std::str::FromStr;
5566+/// Convert a path parameter string into a type.
77+///
88+/// ## Example
99+/// ```rust
1010+/// use maudit::params::FromParam;
1111+///
1212+/// struct UserId(String);
1313+///
1414+/// impl FromParam for UserId {
1515+/// type Error = std::io::Empty;
1616+///
1717+/// fn from_param(param: &str) -> Result<Self, Self::Error> {
1818+/// Ok(UserId(param.to_string()))
1919+/// }
2020+/// }
2121+/// ```
622pub trait FromParam: Sized {
723 /// The associated error to be returned if parsing/validation fails.
824 type Error: std::fmt::Debug;
+1
crates/framework/src/templating/maud_ext.rs
···2929 }
3030}
31313232+/// Can be used to create a generator tag in the output HTML. See [`GENERATOR`](crate::GENERATOR).
3233pub fn generator() -> Markup {
3334 html! {
3435 meta name="generator" content=(GENERATOR);
+3
crates/macros/src/lib.rs
···247247}
248248249249#[proc_macro_attribute]
250250+// Helps implement a struct as a Markdown content entry.
251251+//
252252+// See complete documentation in `crates/framework/src/content.rs`.
250253pub fn markdown_entry(args: TokenStream, item: TokenStream) -> TokenStream {
251254 let mut item_struct = syn::parse_macro_input!(item as ItemStruct);
252255 let _ = parse_macro_input!(args as parse::Nothing);