Rust library to generate static websites
5
fork

Configure Feed

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

feat: improve content -> params ergonomy

+85 -47
+25
crates/framework/src/content.rs
··· 6 6 use rustc_hash::FxHashMap; 7 7 use serde::de::DeserializeOwned; 8 8 9 + use crate::page::RouteParams; 10 + 9 11 pub struct ContentEntry<T> { 10 12 pub id: String, 11 13 pub render: Box<dyn Fn() -> String>, 12 14 pub data: T, 13 15 } 16 + 17 + pub type Untyped = FxHashMap<String, String>; 14 18 15 19 pub struct ContentSources(pub Vec<Box<dyn ContentSourceInternal>>); 16 20 ··· 83 87 } 84 88 85 89 impl<T> ContentSource<T> { 90 + pub fn new<P>(name: P, entries: Vec<ContentEntry<T>>) -> Self 91 + where 92 + P: Into<String>, 93 + { 94 + Self { 95 + name: name.into(), 96 + entries, 97 + } 98 + } 99 + 86 100 pub fn get_entry(&self, id: &str) -> &ContentEntry<T> { 87 101 self.entries 88 102 .iter() 89 103 .find(|entry| entry.id == id) 90 104 .unwrap_or_else(|| panic!("Entry with id '{}' not found", id)) 105 + } 106 + 107 + pub fn get_entry_safe(&self, id: &str) -> Option<&ContentEntry<T>> { 108 + self.entries.iter().find(|entry| entry.id == id) 109 + } 110 + 111 + pub fn into_params<P>(&self, cb: impl Fn(&ContentEntry<T>) -> P) -> Vec<RouteParams> 112 + where 113 + P: Into<RouteParams>, 114 + { 115 + self.entries.iter().map(cb).map(Into::into).collect() 91 116 } 92 117 } 93 118
+7
crates/framework/src/lib.rs
··· 41 41 }; 42 42 } 43 43 44 + #[macro_export] 45 + macro_rules! content_sources { 46 + ($($source:expr),*) => { 47 + maudit::content::ContentSources(vec![$(Box::new($source)),*]) 48 + }; 49 + } 50 + 44 51 #[derive(Debug)] 45 52 pub struct PageOutput { 46 53 pub route: String,
+21 -5
crates/framework/src/page.rs
··· 21 21 pub assets: &'a mut PageAssets, 22 22 } 23 23 24 + impl RouteContext<'_> { 25 + pub fn params<T>(&self) -> T 26 + where 27 + T: From<RouteParams>, 28 + { 29 + T::from(self.params.clone()) 30 + } 31 + } 32 + 24 33 pub struct DynamicRouteContext<'a> { 25 34 pub content: &'a ContentSources, 26 35 } ··· 39 48 { 40 49 params.into_iter().map(|p| p.into()).collect() 41 50 } 51 + } 42 52 43 - pub fn parse_into<T>(&self) -> T 44 - where 45 - T: From<RouteParams>, 46 - { 47 - T::from(self.clone()) 53 + impl<T> FromIterator<T> for RouteParams 54 + where 55 + T: Into<RouteParams>, 56 + { 57 + fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { 58 + let mut map = FxHashMap::default(); 59 + for item in iter { 60 + let item = item.into(); 61 + map.extend(item.0); 62 + } 63 + RouteParams(map) 48 64 } 49 65 } 50 66
+15
examples/blog/src/content.rs
··· 1 + use maudit::content::{glob_markdown, ContentSource, ContentSources}; 2 + use maudit::content_sources; 1 3 use serde::Deserialize; 2 4 3 5 #[derive(Deserialize)] ··· 5 7 pub title: String, 6 8 pub description: String, 7 9 } 10 + 11 + pub fn content_sources() -> ContentSources { 12 + content_sources!( 13 + ContentSource::new( 14 + "articles", 15 + glob_markdown::<ArticleContent>("content/articles/*.md") 16 + ), 17 + ContentSource::new( 18 + "authors", 19 + glob_markdown::<ArticleContent>("content/authors/*.md") 20 + ) 21 + ) 22 + }
+3 -9
examples/blog/src/main.rs
··· 1 1 mod content; 2 2 mod layout; 3 - use content::ArticleContent; 4 - use maudit::{ 5 - content::{glob_markdown, ContentSource, ContentSources}, 6 - coronate, routes, BuildOptions, BuildOutput, 7 - }; 3 + use content::content_sources; 4 + use maudit::{coronate, routes, BuildOptions, BuildOutput}; 8 5 9 6 mod pages { 10 7 mod article; ··· 16 13 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 17 14 coronate( 18 15 routes![pages::Index, pages::Article], 19 - ContentSources(vec![Box::new(ContentSource { 20 - name: "articles".to_string(), 21 - entries: glob_markdown::<ArticleContent>("content/articles/*.md"), 22 - })]), 16 + content_sources(), 23 17 BuildOptions::default(), 24 18 ) 25 19 }
+5 -10
examples/blog/src/pages/article.rs
··· 12 12 13 13 impl DynamicPage for Article { 14 14 fn routes(&self, ctx: &DynamicRouteContext) -> Vec<RouteParams> { 15 - let articles = ctx.content.get_collection::<ArticleContent>("articles"); 16 - let mut static_routes: Vec<ArticleParams> = vec![]; 15 + let collection = ctx.content.get_collection::<ArticleContent>("articles"); 17 16 18 - for article in &articles.entries { 19 - static_routes.push(ArticleParams { 20 - article: article.id.clone(), 21 - }); 22 - } 23 - 24 - RouteParams::from_vec(static_routes) 17 + collection.into_params(|entry| ArticleParams { 18 + article: entry.id.clone(), 19 + }) 25 20 } 26 21 } 27 22 28 23 impl Page for Article { 29 24 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 30 - let params = ctx.params.parse_into::<ArticleParams>(); 25 + let params = ctx.params::<ArticleParams>(); 31 26 let articles = ctx.content.get_collection::<ArticleContent>("articles"); 32 27 let article = articles.get_entry(&params.article); 33 28
+7 -15
examples/blog/src/pages/index.rs
··· 14 14 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 15 15 let articles = ctx.content.get_collection::<ArticleContent>("articles"); 16 16 17 - let article_list = articles 18 - .entries 19 - .iter() 20 - .map(|entry| { 21 - html! { 22 - a href=(Article::url_unsafe(ArticleParams { article: entry.id.clone() })) { 23 - h2 { (entry.data.title) } 24 - p { (entry.data.description) } 25 - } 26 - } 27 - }) 28 - .collect::<Vec<_>>(); 29 - 30 17 let markup = html! { 31 18 ul { 32 - @for article in article_list { 33 - (article) 19 + @for entry in &articles.entries { 20 + li { 21 + a href=(Article::url_unsafe(ArticleParams { article: entry.id.clone() })) { 22 + h2 { (entry.data.title) } 23 + } 24 + p { (entry.data.description) } 25 + } 34 26 } 35 27 } 36 28 }
+2 -8
examples/kitchen-sink/src/pages/dynamic.rs
··· 12 12 13 13 impl DynamicPage for DynamicExample { 14 14 fn routes(&self, _: &DynamicRouteContext) -> Vec<RouteParams> { 15 - let mut static_routes: Vec<Params> = vec![]; 16 - 17 - for i in 0..1 { 18 - static_routes.push(Params { page: i }); 19 - } 20 - 21 - RouteParams::from_vec(static_routes) 15 + (0..1).map(|i| Params { page: i }.into()).collect() 22 16 } 23 17 } 24 18 25 19 impl Page for DynamicExample { 26 20 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 27 - let params = ctx.params.parse_into::<Params>(); 21 + let params = ctx.params::<Params>(); 28 22 let image = ctx.assets.add_image("data/social-card.png"); 29 23 ctx.assets.include_style("data/tailwind.css", true); 30 24