Rust library to generate static websites
5
fork

Configure Feed

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

feat(oubli): archetype store (#12)

* à l'aide erika

* fix: make it compile

* progress

* add display name params for archetypes

* labeler oubli

* contententry new

* stringified_ident

* review fix

* Update crates/oubli/src/lib.rs

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>

---------

Co-authored-by: Princesseuh <3019731+Princesseuh@users.noreply.github.com>

authored by

Goulven CLEC'H
Erika
and committed by
GitHub
ff7fc3b0 488c9b29

+131 -27
+4
.github/labeler.yml
··· 11 11 - changed-files: 12 12 - any-glob-to-any-file: crates/framework/** 13 13 14 + "crt:oubli": 15 + - changed-files: 16 + - any-glob-to-any-file: crates/oubli/** 17 + 14 18 "crt:cli": 15 19 - changed-files: 16 20 - any-glob-to-any-file: crates/cli/**
+16
crates/framework/src/content.rs
··· 224 224 type OptionalContentRenderFn = Option<Box<dyn Fn(&str) -> String + Send + Sync>>; 225 225 226 226 impl<T> ContentEntry<T> { 227 + pub fn new( 228 + id: String, 229 + render: OptionalContentRenderFn, 230 + raw_content: Option<String>, 231 + data: T, 232 + file_path: Option<PathBuf>, 233 + ) -> Self { 234 + Self { 235 + id, 236 + render, 237 + raw_content, 238 + data, 239 + file_path, 240 + } 241 + } 242 + 227 243 pub fn render(&self) -> String { 228 244 (self.render.as_ref().unwrap())(self.raw_content.as_ref().unwrap()) 229 245 }
+10 -5
crates/oubli/src/archetypes/blog.rs
··· 9 9 route: impl FullPage, 10 10 ctx: &mut RouteContext, 11 11 name: &str, 12 + stringified_ident: &str, 12 13 ) -> Markup { 13 - let blog_entries = ctx.content.get_source::<BlogEntryContent>(name); 14 + let blog_entries = ctx 15 + .content 16 + .get_source::<BlogEntryContent>(stringified_ident); 14 17 15 18 let markup = html! { 16 19 main { ··· 24 27 } 25 28 .into_string(); 26 29 27 - layout(markup) 30 + layout(name, markup) 28 31 } 29 32 30 33 #[markdown_entry] ··· 47 50 }) 48 51 } 49 52 50 - pub fn blog_entry_render(ctx: &mut RouteContext, name: &str) -> Markup { 53 + pub fn blog_entry_render(ctx: &mut RouteContext, name: &str, stringified_ident: &str) -> Markup { 51 54 let params = ctx.params::<BlogEntryParams>(); 52 - let blog_entries = ctx.content.get_source::<BlogEntryContent>(name); 55 + let blog_entries = ctx 56 + .content 57 + .get_source::<BlogEntryContent>(stringified_ident); 53 58 let blog_entry = blog_entries.get_entry(&params.entry); 54 59 55 60 let headings = blog_entry.data.get_headings(); 56 61 println!("{:?}", headings); 57 62 58 - layout(blog_entry.render()) 63 + layout(name, blog_entry.render()) 59 64 }
+3 -3
crates/oubli/src/layouts/layout.rs
··· 1 1 use maud::{html, Markup, PreEscaped}; 2 2 3 - pub fn layout(content: String) -> Markup { 3 + pub fn layout(title: &str, content: String) -> Markup { 4 4 html! { 5 5 html { 6 6 head { 7 7 meta charset="utf-8"; 8 - title { "My Blog" } 8 + title { (title) } 9 9 } 10 10 body { 11 11 header { 12 - h1 { "My Blog" } 12 + h1 { (title) } 13 13 } 14 14 main { 15 15 (PreEscaped(content))
+90 -18
crates/oubli/src/lib.rs
··· 1 + use maudit::content::ContentEntry; 2 + use maudit::page::prelude::*; 3 + 1 4 use maudit::{ 2 5 content::{ContentSourceInternal, ContentSources}, 3 6 coronate, 4 - page::FullPage, 7 + page::{prelude::Params, FullPage}, 5 8 }; 6 9 7 10 // Re-expose Maudit's public API. ··· 28 31 } 29 32 30 33 #[macro_export] 34 + /// Helps to define every archetype that should be build by [`forget()`]. 35 + /// 36 + /// ## Example 37 + /// ```rust 38 + /// use oubli::{Archetype, archetypes, content_sources, forget, routes, BuildOptions, BuildOutput}; 39 + /// 40 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 41 + /// forget( 42 + /// // Define archetypes and their glob patterns using the provided macro. 43 + /// archetypes![ 44 + /// ("News", news, Archetype::Blog, "content/blog/**/*.md") 45 + /// ], 46 + /// routes![], 47 + /// content_sources![], 48 + /// BuildOptions::default(), 49 + /// ) 50 + /// } 51 + /// ``` 31 52 macro_rules! archetypes { 32 - ($(($name:ident, $arch:expr, $glob:expr)),* $(,)?) => {{ 53 + ($(($name:expr, $ident:ident, $arch:expr, $glob:expr)),* $(,)?) => {{ 33 54 let mut vec = Vec::new(); 34 55 $( 35 56 let tuple = match $arch { 36 57 oubli::Archetype::Blog => { 37 58 // Generate the content source 38 59 let content_source = maudit::content::ContentSource::new( 39 - stringify!($name), 60 + stringify!($ident), 40 61 Box::new({ 41 62 let glob = $glob.to_string(); 42 63 move || maudit::content::glob_markdown::<oubli::archetypes::blog::BlogEntryContent>(&glob) 43 64 }), 44 65 ); 45 66 // Generate the pages 46 - mod $name { 67 + mod $ident { 47 68 use maudit::page::prelude::*; 48 69 use oubli::archetypes::blog::*; 49 70 50 - #[route(stringify!($name))] 71 + #[route(stringify!($ident))] 51 72 pub struct Index; 52 73 impl Page for Index { 53 74 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 54 - blog_index_content::<Entry>(Entry, ctx, stringify!($name)).into() 75 + blog_index_content::<Entry>(Entry, ctx, $name, stringify!($ident)).into() 55 76 } 56 77 } 57 78 58 - #[route(concat!(stringify!($name), "/[entry]"))] 79 + #[route(concat!(stringify!($ident), "/[entry]"))] 59 80 pub struct Entry; 60 81 impl Page<BlogEntryParams> for Entry { 61 82 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 62 - blog_entry_render(ctx, stringify!($name)).into() 83 + blog_entry_render(ctx, $name, stringify!($ident)).into() 63 84 } 64 85 65 86 fn routes(&self, ctx: &mut DynamicRouteContext) -> Vec<BlogEntryParams> { 66 - blog_entry_routes(ctx, stringify!($name)) 87 + blog_entry_routes(ctx, stringify!($ident)) 67 88 } 68 89 } 69 90 } 70 - (stringify!($name), vec![&$name::Index as &dyn maudit::page::FullPage, &$name::Entry as &dyn maudit::page::FullPage], Box::new(content_source) as Box<dyn maudit::content::ContentSourceInternal>) 91 + ($name, stringify!($ident), vec![&$ident::Index as &dyn maudit::page::FullPage, &$ident::Entry as &dyn maudit::page::FullPage], Box::new(content_source) as Box<dyn maudit::content::ContentSourceInternal>) 71 92 }, 72 93 oubli::Archetype::MarkdownDoc => { 73 94 todo!(); ··· 93 114 /// forget( 94 115 /// // Define archetypes and their glob patterns using the provided macro. 95 116 /// archetypes![ 96 - /// (news, Archetype::Blog, "content/blog/**/*.md") 117 + /// ("News", news, Archetype::Blog, "content/blog/**/*.md") 97 118 /// ], 98 119 /// routes![], 99 120 /// content_sources![], ··· 103 124 /// ``` 104 125 #[allow(clippy::type_complexity)] 105 126 pub fn forget( 106 - archetypes: Vec<(&str, Vec<&dyn FullPage>, Box<dyn ContentSourceInternal>)>, 127 + archetypes: Vec<( 128 + &str, 129 + &str, 130 + Vec<&dyn FullPage>, 131 + Box<dyn ContentSourceInternal>, 132 + )>, 107 133 routes: &[&dyn FullPage], 108 134 mut content_sources: ContentSources, 109 135 options: BuildOptions, 110 136 ) -> Result<BuildOutput, Box<dyn std::error::Error>> { 111 137 // Let's merge the routes and content sources from the archetypes to the user-provided ones. 112 138 let mut combined_routes = routes.to_vec(); 113 - let mut content_sources_archetypes = vec![]; 139 + let mut content_sources_archetypes = Vec::new(); 114 140 115 - for (_name, pages, content_source) in archetypes { 141 + content_sources 142 + .0 143 + .push(generate_archetype_store(&archetypes)); 144 + 145 + for (_name, _stringified_ident, pages, content_source) in archetypes { 116 146 content_sources_archetypes.push(content_source); 117 - combined_routes.extend(pages.iter()); 147 + combined_routes.extend(pages); 118 148 } 119 149 120 - let mut combined_content_sources = ContentSources::new(content_sources_archetypes); 121 - combined_content_sources.0.append(&mut content_sources.0); 150 + content_sources.0.extend(content_sources_archetypes); 122 151 123 152 // At the end of the day, we are just a Maudit wrapper. 124 - coronate(&combined_routes, combined_content_sources, options) 153 + coronate(&combined_routes, content_sources, options) 125 154 } 155 + 156 + /// # Generates a content source with every provided archetype. 157 + fn generate_archetype_store(archetypes: &Vec<ArchetypeTuple>) -> Box<dyn ContentSourceInternal> { 158 + let names: Vec<(String, String)> = archetypes 159 + .iter() 160 + .map(|(name, stringified_ident, _, _)| (name.to_string(), stringified_ident.to_string())) 161 + .collect(); 162 + 163 + let archetype_store = maudit::content::ContentSource::new( 164 + "archetype_store", 165 + Box::new(move || { 166 + let mut entries = Vec::new(); 167 + for (name, stringified_ident) in names.iter() { 168 + entries.push(ContentEntry::new( 169 + stringified_ident.to_string(), 170 + None, 171 + None, 172 + ArchetypeStoreEntry { 173 + title: name.to_string(), 174 + }, 175 + None, 176 + )); 177 + } 178 + entries 179 + }), 180 + ); 181 + 182 + Box::new(archetype_store) 183 + } 184 + 185 + /// Represents each archetype provided by the user. 186 + #[derive(Params)] 187 + pub struct ArchetypeStoreEntry { 188 + pub title: String, 189 + } 190 + 191 + /// # Internal representation of an archetype tuple. (macro expansion) 192 + type ArchetypeTuple<'a> = ( 193 + &'a str, 194 + &'a str, 195 + Vec<&'a dyn FullPage>, 196 + Box<dyn ContentSourceInternal>, 197 + );
+1 -1
examples/oubli-basics/src/main.rs
··· 11 11 12 12 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 13 13 forget( 14 - archetypes![(blog, Archetype::Blog, "content/blog/*.md")], 14 + archetypes![("Our blog", blog, Archetype::Blog, "content/blog/*.md"),], 15 15 routes![Index], 16 16 vec![].into(), 17 17 BuildOptions::default(),
+7
examples/oubli-basics/src/pages/index.rs
··· 9 9 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 10 10 let logo = ctx.assets.add_image("images/logo.svg"); 11 11 12 + let archetype_store = ctx 13 + .content 14 + .get_source::<oubli::ArchetypeStoreEntry>("archetype_store"); 15 + 12 16 layout(html! { 13 17 (logo) 14 18 h1 { "Hello World" } 19 + @for archetype in &archetype_store.entries { 20 + a href=(archetype.id) { (archetype.data.title) } 21 + } 15 22 }) 16 23 .into() 17 24 }