Rust library to generate static websites
5
fork

Configure Feed

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

oubli: poc (#8)

* init oubli

* basics of archetypes
But it doesn't work, pls Erika help

* fix: make everything compile

* tmp

* expand routes macros

* revert to routes macro

* fix build

* fix tests

* Update Cargo.toml

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

* Update crates/framework/src/build.rs

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

* remove unused test

---------

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

authored by

Goulven CLEC'H
Erika
and committed by
GitHub
226ff8f4 a6dff1d2

+321 -5
+18
Cargo.lock
··· 2374 2374 checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 2375 2375 2376 2376 [[package]] 2377 + name = "oubli" 2378 + version = "0.1.0" 2379 + dependencies = [ 2380 + "maud", 2381 + "maudit", 2382 + "serde", 2383 + ] 2384 + 2385 + [[package]] 2386 + name = "oubli-example-basics" 2387 + version = "0.1.0" 2388 + dependencies = [ 2389 + "maud", 2390 + "maudit", 2391 + "oubli", 2392 + ] 2393 + 2394 + [[package]] 2377 2395 name = "outref" 2378 2396 version = "0.5.2" 2379 2397 source = "registry+https://github.com/rust-lang/crates.io-index"
+1
Cargo.toml
··· 4 4 5 5 [workspace.dependencies] 6 6 maudit = { path = "crates/framework", version = "*" } 7 + oubli = { path = "crates/oubli", version = "*" } 7 8 maud = { version = "0.26.0" } 8 9 serde = { version = "1.0.216" } 9 10
+1 -1
crates/framework/src/content.rs
··· 409 409 /// ) 410 410 /// } 411 411 /// ``` 412 - #[derive(serde::Deserialize)] 412 + #[derive(serde::Deserialize, Debug, Clone)] 413 413 pub struct UntypedMarkdownContent { 414 414 #[serde(skip)] 415 415 __internal_headings: Vec<MarkdownHeading>,
+5 -2
crates/framework/src/page.rs
··· 240 240 fn routes_internal(&self, context: &mut DynamicRouteContext) -> Vec<RouteParams>; 241 241 } 242 242 243 - pub fn get_page_url<T: Into<RouteParams>>(route: impl FullPage, params: T) -> String { 243 + pub fn get_page_url<T: Into<RouteParams>>(route: &impl FullPage, params: T) -> String { 244 244 let params_defs = extract_params_from_raw_route(&route.route_raw()); 245 - get_route_url(&route.route_raw(), &params_defs, &params.into()) 245 + format!( 246 + "/{}", 247 + get_route_url(&route.route_raw(), &params_defs, &params.into()) 248 + ) 246 249 } 247 250 248 251 pub mod prelude {
+11
crates/oubli/Cargo.toml
··· 1 + [package] 2 + name = "oubli" 3 + description = "Library for generating documentation websites with Maudit." 4 + version = "0.1.0" 5 + license = "MIT" 6 + edition = "2021" 7 + 8 + [dependencies] 9 + maudit = { path = "../framework", version = "0.2" } 10 + maud = { workspace = true } 11 + serde = { workspace = true }
+5
crates/oubli/README.md
··· 1 + # Oubli 2 + 3 + > Those who cannot remember the past are condemned to repeat it. 4 + 5 + A library for generating documentation websites with Maudit.
+59
crates/oubli/src/archetypes/blog.rs
··· 1 + //! Blog archetype. 2 + //! Represents a markdown blog archetype, with an index page and individual entry pages. 3 + use crate::layouts::layout; 4 + use maud::{html, Markup}; 5 + use maudit::content::markdown_entry; 6 + use maudit::page::prelude::*; 7 + 8 + pub fn blog_index_content<T: FullPage>( 9 + route: impl FullPage, 10 + ctx: &mut RouteContext, 11 + name: &str, 12 + ) -> Markup { 13 + let blog_entries = ctx.content.get_source::<BlogEntryContent>(name); 14 + 15 + let markup = html! { 16 + main { 17 + @for entry in &blog_entries.entries { 18 + a href=(get_page_url(&route, BlogEntryParams { entry: entry.id.clone() })) { 19 + h2 { (entry.data.title) } 20 + p { (entry.data.description) } 21 + } 22 + } 23 + } 24 + } 25 + .into_string(); 26 + 27 + layout(markup) 28 + } 29 + 30 + #[markdown_entry] 31 + #[derive(Debug, Clone)] 32 + pub struct BlogEntryContent { 33 + pub title: String, 34 + pub description: String, 35 + } 36 + 37 + #[derive(Params)] 38 + pub struct BlogEntryParams { 39 + pub entry: String, 40 + } 41 + 42 + pub fn blog_entry_routes(ctx: &mut DynamicRouteContext, name: &str) -> Vec<BlogEntryParams> { 43 + let blog_entries = ctx.content.get_source::<BlogEntryContent>(name); 44 + 45 + blog_entries.into_params(|entry| BlogEntryParams { 46 + entry: entry.id.clone(), 47 + }) 48 + } 49 + 50 + pub fn blog_entry_render(ctx: &mut RouteContext, name: &str) -> Markup { 51 + let params = ctx.params::<BlogEntryParams>(); 52 + let blog_entries = ctx.content.get_source::<BlogEntryContent>(name); 53 + let blog_entry = blog_entries.get_entry(&params.entry); 54 + 55 + let headings = blog_entry.data.get_headings(); 56 + println!("{:?}", headings); 57 + 58 + layout(blog_entry.render()) 59 + }
+23
crates/oubli/src/layouts/layout.rs
··· 1 + use maud::{html, Markup, PreEscaped}; 2 + 3 + pub fn layout(content: String) -> Markup { 4 + html! { 5 + html { 6 + head { 7 + meta charset="utf-8"; 8 + title { "My Blog" } 9 + } 10 + body { 11 + header { 12 + h1 { "My Blog" } 13 + } 14 + main { 15 + (PreEscaped(content)) 16 + } 17 + footer { 18 + p { "© 2024 My Super Blog" } 19 + } 20 + } 21 + } 22 + } 23 + }
+125
crates/oubli/src/lib.rs
··· 1 + use maudit::{ 2 + content::{ContentSourceInternal, ContentSources}, 3 + coronate, 4 + page::FullPage, 5 + }; 6 + 7 + // Re-expose Maudit's public API. 8 + pub use maudit::{content_sources, routes, BuildOptions, BuildOutput}; 9 + // Expose the archetypes module. 10 + pub mod archetypes { 11 + pub mod blog; 12 + } 13 + // Expose the layout module. 14 + pub mod layouts { 15 + mod layout; 16 + pub use layout::layout; 17 + } 18 + // Expose public components. 19 + pub mod components {} 20 + 21 + /// Help you quickly scaffold common types of content, like blogs or documentation. 22 + #[derive(Debug, Clone)] 23 + pub enum Archetype { 24 + /// Represents a markdown blog archetype. 25 + Blog, 26 + /// Represents a markdown documentation archetype. 27 + MarkdownDoc, 28 + } 29 + 30 + #[macro_export] 31 + macro_rules! archetypes { 32 + ($(($name:ident, $arch:expr, $glob:expr)),* $(,)?) => {{ 33 + let mut vec = Vec::new(); 34 + $( 35 + let tuple = match $arch { 36 + oubli::Archetype::Blog => { 37 + // Generate the content source 38 + let content_source = maudit::content::ContentSource::new( 39 + stringify!($name), 40 + Box::new({ 41 + let glob = $glob.to_string(); 42 + move || maudit::content::glob_markdown::<oubli::archetypes::blog::BlogEntryContent>(&glob) 43 + }), 44 + ); 45 + // Generate the pages 46 + mod $name { 47 + use maudit::page::prelude::*; 48 + use oubli::archetypes::blog::*; 49 + 50 + #[route(stringify!($name))] 51 + pub struct Index; 52 + impl Page for Index { 53 + fn render(&self, ctx: &mut RouteContext) -> RenderResult { 54 + blog_index_content::<Entry>(Entry, ctx, stringify!($name)).into() 55 + } 56 + } 57 + 58 + #[route(concat!(stringify!($name), "/[entry]"))] 59 + pub struct Entry; 60 + impl Page<BlogEntryParams> for Entry { 61 + fn render(&self, ctx: &mut RouteContext) -> RenderResult { 62 + blog_entry_render(ctx, stringify!($name)).into() 63 + } 64 + 65 + fn routes(&self, ctx: &mut DynamicRouteContext) -> Vec<BlogEntryParams> { 66 + blog_entry_routes(ctx, stringify!($name)) 67 + } 68 + } 69 + } 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>) 71 + }, 72 + oubli::Archetype::MarkdownDoc => { 73 + todo!(); 74 + } 75 + }; 76 + vec.push(tuple); 77 + )* 78 + vec 79 + }}; 80 + } 81 + 82 + /// 🪶 Oubli entrypoint. Starts the build process and generates the output files. 83 + /// 84 + /// This function wraps Maudit's [`coronate`](maudit::coronate) function and adds an `archetypes` argument. 85 + /// The user can specify one or more archetypes with their corresponding glob pattern. The routes and content 86 + /// sources generated by these archetypes are merged with the routes and content sources provided directly. 87 + /// 88 + /// ## Example 89 + /// ```rust 90 + /// use oubli::{Archetype, archetypes, content_sources, forget, routes, BuildOptions, BuildOutput}; 91 + /// 92 + /// fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 93 + /// forget( 94 + /// // Define archetypes and their glob patterns using the provided macro. 95 + /// archetypes![ 96 + /// (news, Archetype::Blog, "content/blog/**/*.md") 97 + /// ], 98 + /// routes![], 99 + /// content_sources![], 100 + /// BuildOptions::default(), 101 + /// ) 102 + /// } 103 + /// ``` 104 + #[allow(clippy::type_complexity)] 105 + pub fn forget( 106 + archetypes: Vec<(&str, Vec<&dyn FullPage>, Box<dyn ContentSourceInternal>)>, 107 + routes: &[&dyn FullPage], 108 + mut content_sources: ContentSources, 109 + options: BuildOptions, 110 + ) -> Result<BuildOutput, Box<dyn std::error::Error>> { 111 + // Let's merge the routes and content sources from the archetypes to the user-provided ones. 112 + let mut combined_routes = routes.to_vec(); 113 + let mut content_sources_archetypes = vec![]; 114 + 115 + for (_name, pages, content_source) in archetypes { 116 + content_sources_archetypes.push(content_source); 117 + combined_routes.extend(pages.iter()); 118 + } 119 + 120 + let mut combined_content_sources = ContentSources::new(content_sources_archetypes); 121 + combined_content_sources.0.append(&mut content_sources.0); 122 + 123 + // At the end of the day, we are just a Maudit wrapper. 124 + coronate(&combined_routes, combined_content_sources, options) 125 + }
+1 -1
examples/blog/src/pages/index.rs
··· 18 18 ul { 19 19 @for entry in &articles.entries { 20 20 li { 21 - a href=(get_page_url(Article, ArticleParams { article: entry.id.clone() })) { 21 + a href=(get_page_url(&Article, ArticleParams { article: entry.id.clone() })) { 22 22 h2 { (entry.data.title) } 23 23 } 24 24 p { (entry.data.description) }
+2 -1
examples/kitchen-sink/src/pages/index.rs
··· 13 13 let script = ctx.assets.add_script("data/some_other_script.js"); 14 14 let style = ctx.assets.add_style("data/tailwind.css", true); 15 15 16 - let link_to_first_dynamic = get_page_url(DynamicExample, &DynamicExampleParams { page: 1 }); 16 + let link_to_first_dynamic = 17 + get_page_url(&DynamicExample, &DynamicExampleParams { page: 1 }); 17 18 18 19 html! { 19 20 head {
+10
examples/oubli-basics/Cargo.toml
··· 1 + [package] 2 + name = "oubli-example-basics" 3 + version = "0.1.0" 4 + edition = "2021" 5 + publish = false 6 + 7 + [dependencies] 8 + maudit = { workspace = true } 9 + oubli = { workspace = true } 10 + maud = { workspace = true }
+6
examples/oubli-basics/content/blog/test.md
··· 1 + --- 2 + title: "This is a test" 3 + description: "But I'm not sure what I'm testing" 4 + --- 5 + 6 + Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+1
examples/oubli-basics/images/logo.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="357.3" height="281" fill="none"><path fill="#0d0d0d" d="M303 267c-51-6-51-6-83-5h-32l-63 7a1419 1419 0 0 0-92 12L22 173 12 66l-3-1C3 63-1 55 0 46c0-5 1-6 6-9 5-4 5-4 11-3 5 0 7 1 9 3 3 3 3 3 4 12 0 8 0 10-2 12l-2 2 15 19c22 27 58 67 59 66l4-15a1924 1924 0 0 1 13-64l-4-8c-3-3-7-11-9-16l-3-10 6-7c6-6 6-7 13-8 9-3 13-1 18 5 5 8 5 11 1 23l-4 12 7 21c7 19 7 19 27 52 20 32 25 39 26 36l3-14 14-60 10-48c0-2-2-3-5-6l-5-7 1-6c1-6 2-7 7-11 4-2 6-3 10-2 6 0 8 1 12 7 4 7 5 15 3 19l-6 5-5 3c0 5 48 117 49 117 4 0 5-5 18-63l13-59-6-4-5-3-1-10-1-10 6-8 6-8 8-1c8 0 8 0 13 3s6 4 7 11c2 8 2 15 0 18l-8 6-6 4 8 34 8 34 8 81c7 80 7 81 5 81z"/></svg>
+16
examples/oubli-basics/src/layout.rs
··· 1 + use maud::{html, Markup, DOCTYPE}; 2 + 3 + pub fn layout(content: Markup) -> Markup { 4 + html! { 5 + (DOCTYPE) 6 + html { 7 + head { 8 + meta charset="utf-8"; 9 + title { "Test page" } 10 + } 11 + body { 12 + (content) 13 + } 14 + } 15 + } 16 + }
+19
examples/oubli-basics/src/main.rs
··· 1 + mod layout; 2 + 3 + use oubli::{archetypes, forget, routes, Archetype, BuildOptions, BuildOutput}; 4 + 5 + mod pages { 6 + mod index; 7 + pub use index::Index; 8 + } 9 + 10 + pub use pages::Index; 11 + 12 + fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 13 + forget( 14 + archetypes![(blog, Archetype::Blog, "content/blog/*.md")], 15 + routes![Index], 16 + vec![].into(), 17 + BuildOptions::default(), 18 + ) 19 + }
+18
examples/oubli-basics/src/pages/index.rs
··· 1 + use crate::layout::layout; 2 + use maud::html; 3 + use maudit::page::prelude::*; 4 + 5 + #[route("/")] 6 + pub struct Index; 7 + 8 + impl Page for Index { 9 + fn render(&self, ctx: &mut RouteContext) -> RenderResult { 10 + let logo = ctx.assets.add_image("images/logo.svg"); 11 + 12 + layout(html! { 13 + (logo) 14 + h1 { "Hello World" } 15 + }) 16 + .into() 17 + } 18 + }