Learning project: static site generator for ATproto PDS

Adds global context for template rendering #3

merged opened by hello.j23n.com targeting main from feature/global-context
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:enau5rzvrui4fx4dq5icgtle/sh.tangled.repo.pull/3m3podkovhp22
+62 -53
Diff #0
+5 -7
src/atproto/mod.rs
··· 29 pub name: String, 30 } 31 32 - #[derive(serde::Serialize)] 33 pub struct IndexPage { 34 pub content: Vec<ContentPage>, 35 pub template: String, ··· 111 } 112 113 impl Renderable for ContentPage { 114 - fn render(&self, tera: &Tera) -> Result<String, tera::Error> { 115 - let mut context = Context::new(); 116 match &self.content { 117 ContentType::Document(doc) => { 118 context.insert("document", doc); 119 - context.insert("host", &doc.host); 120 - context.insert("did", &doc.did); 121 } 122 ContentType::Post(post) => { 123 context.insert("post", post); ··· 131 } 132 133 impl Renderable for IndexPage { 134 - fn render(&self, tera: &Tera) -> Result<String, tera::Error> { 135 - let mut context = Context::new(); 136 context.insert("content", &self.content); 137 tera.render(&self.template, &context) 138 }
··· 29 pub name: String, 30 } 31 32 + #[derive(serde::Serialize, Clone)] 33 pub struct IndexPage { 34 pub content: Vec<ContentPage>, 35 pub template: String, ··· 111 } 112 113 impl Renderable for ContentPage { 114 + fn render(&self, context: &Context, tera: &Tera) -> Result<String, tera::Error> { 115 + let mut context = context.clone(); 116 match &self.content { 117 ContentType::Document(doc) => { 118 context.insert("document", doc); 119 } 120 ContentType::Post(post) => { 121 context.insert("post", post); ··· 129 } 130 131 impl Renderable for IndexPage { 132 + fn render(&self, context: &Context, tera: &Tera) -> Result<String, tera::Error> { 133 + let mut context = context.clone(); 134 context.insert("content", &self.content); 135 tera.render(&self.template, &context) 136 }
+2 -2
src/atproto/renderable.rs
··· 1 - use tera::Tera; 2 3 pub trait Renderable { 4 - fn render(&self, tera: &Tera) -> Result<String, tera::Error>; 5 }
··· 1 + use tera::{Context, Tera}; 2 3 pub trait Renderable { 4 + fn render(&self, context: &Context, tera: &Tera) -> Result<String, tera::Error>; 5 }
-4
src/atproto/types/leaflet.rs
··· 14 pub publication: Unknown, 15 #[serde(rename = "publishedAt")] 16 pub published_at: String, 17 - #[serde(skip)] 18 - pub host: String, 19 - #[serde(skip)] 20 - pub did: String, 21 } 22 23 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
··· 14 pub publication: Unknown, 15 #[serde(rename = "publishedAt")] 16 pub published_at: String, 17 } 18 19 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+43 -34
src/generator.rs
··· 2 use atrium_api::types::string::AtIdentifier; 3 use atrium_api::{agent::atp_agent::AtpAgent, types::TryFromUnknown}; 4 use atrium_xrpc_client::reqwest::ReqwestClient; 5 - use tera::{Context, Tera}; 6 7 use crate::config::Config; 8 use crate::templates::filters; ··· 23 host: String, 24 } 25 26 pub async fn generate(config: &Config) -> Result<usize, Box<dyn std::error::Error>> { 27 let mut tera = match Tera::new("templates/**/*.html") { 28 Ok(t) => t, ··· 98 std::fs::create_dir_all(&output_root)?; 99 100 // Create ContentPages for content 101 - let post_pages: Vec<ContentPage> = posts 102 - .into_iter() 103 - .map(|i| ContentPage::new(ContentType::Post(i), "post.html", "posts")) 104 - .collect(); 105 - 106 let document_pages: Vec<ContentPage> = documents 107 .into_iter() 108 .map(|i| ContentPage::new(ContentType::Document(i), "document.html", "documents")) ··· 113 .map(|r| ContentPage::new(ContentType::Repo(r), "repo.html", "repos")) 114 .collect(); 115 116 // Render individual pages 117 - for pages in [&document_pages, &post_pages, &repo_pages] { 118 for page in pages.iter() { 119 - let html = page.render(tera)?; 120 let mut path = PathBuf::from(output_root); 121 path.push(&page.filepath); 122 std::fs::create_dir_all(path.parent().expect("Invalid file path"))?; ··· 128 } 129 130 // Render index pages 131 - let document_index = generate_index(&document_pages, output_root, "documents", tera)?; 132 counter += 1; 133 134 - let repo_index = generate_index(&repo_pages, output_root, "repos", tera)?; 135 - counter += 1; 136 - 137 - let post_index = generate_index(&post_pages, output_root, "posts", tera)?; 138 counter += 1; 139 140 // Render home page 141 - generate_home( 142 - &(document_index, document_pages), 143 - &(post_index, post_pages), 144 - &(repo_index, repo_pages), 145 - output_root, 146 - tera, 147 - )?; 148 149 Ok(counter) 150 } 151 152 fn generate_home( 153 - documents: &(IndexPage, Vec<ContentPage>), 154 - posts: &(IndexPage, Vec<ContentPage>), 155 - repos: &(IndexPage, Vec<ContentPage>), 156 output_root: &Path, 157 tera: &Tera, 158 ) -> Result<(), tera::Error> { 159 let mut output_path = PathBuf::from(output_root); 160 output_path.push("index.html"); 161 162 - let mut context = Context::new(); 163 - context.insert("documents", documents); 164 - context.insert("posts", posts); 165 - context.insert("repos", repos); 166 - let html = tera.render("home.html", &context)?; 167 168 std::fs::write(output_path, html)?; 169 ··· 171 } 172 173 fn generate_index( 174 - pages: &Vec<ContentPage>, 175 output_root: &Path, 176 - collection: &str, 177 tera: &Tera, 178 - ) -> Result<IndexPage, tera::Error> { 179 - let index_page = IndexPage::new(pages.clone(), "index.html", collection); 180 let mut output_path = PathBuf::from(output_root); 181 output_path.push(&index_page.filepath); 182 183 - let html = index_page.render(tera)?; 184 std::fs::write(output_path, html)?; 185 186 - Ok(index_page) 187 }
··· 2 use atrium_api::types::string::AtIdentifier; 3 use atrium_api::{agent::atp_agent::AtpAgent, types::TryFromUnknown}; 4 use atrium_xrpc_client::reqwest::ReqwestClient; 5 + use tera::Tera; 6 7 use crate::config::Config; 8 use crate::templates::filters; ··· 23 host: String, 24 } 25 26 + #[derive(serde::Serialize)] 27 + pub struct ContentTree { 28 + repos: Collection, 29 + documents: Collection, 30 + } 31 + 32 + #[derive(serde::Serialize)] 33 + pub struct Collection { 34 + index: IndexPage, 35 + pages: Vec<ContentPage>, 36 + } 37 + 38 pub async fn generate(config: &Config) -> Result<usize, Box<dyn std::error::Error>> { 39 let mut tera = match Tera::new("templates/**/*.html") { 40 Ok(t) => t, ··· 110 std::fs::create_dir_all(&output_root)?; 111 112 // Create ContentPages for content 113 let document_pages: Vec<ContentPage> = documents 114 .into_iter() 115 .map(|i| ContentPage::new(ContentType::Document(i), "document.html", "documents")) ··· 120 .map(|r| ContentPage::new(ContentType::Repo(r), "repo.html", "repos")) 121 .collect(); 122 123 + let repo_index = IndexPage::new(repo_pages.clone(), "index.html", "repos"); 124 + let document_index = IndexPage::new(document_pages.clone(), "index.html", "documents"); 125 + 126 + // create global context 127 + let content = ContentTree { 128 + repos: Collection { 129 + index: repo_index.clone(), 130 + pages: repo_pages.clone(), 131 + }, 132 + documents: Collection { 133 + index: document_index.clone(), 134 + pages: document_pages.clone(), 135 + }, 136 + }; 137 + let mut context = tera::Context::new(); 138 + context.insert("site", &site); 139 + context.insert("content", &content); 140 + 141 // Render individual pages 142 + for pages in [&document_pages, &repo_pages] { 143 for page in pages.iter() { 144 + let html = page.render(&context, tera)?; 145 let mut path = PathBuf::from(output_root); 146 path.push(&page.filepath); 147 std::fs::create_dir_all(path.parent().expect("Invalid file path"))?; ··· 153 } 154 155 // Render index pages 156 + generate_index(&document_index, &context, output_root, tera)?; 157 counter += 1; 158 159 + generate_index(&repo_index, &context, output_root, tera)?; 160 counter += 1; 161 162 // Render home page 163 + generate_home(&context, output_root, tera)?; 164 165 Ok(counter) 166 } 167 168 fn generate_home( 169 + context: &tera::Context, 170 output_root: &Path, 171 tera: &Tera, 172 ) -> Result<(), tera::Error> { 173 let mut output_path = PathBuf::from(output_root); 174 output_path.push("index.html"); 175 176 + let html = tera.render("home.html", context)?; 177 178 std::fs::write(output_path, html)?; 179 ··· 181 } 182 183 fn generate_index( 184 + index_page: &IndexPage, 185 + context: &tera::Context, 186 output_root: &Path, 187 tera: &Tera, 188 + ) -> Result<(), tera::Error> { 189 let mut output_path = PathBuf::from(output_root); 190 output_path.push(&index_page.filepath); 191 192 + let html = index_page.render(context, tera)?; 193 std::fs::write(output_path, html)?; 194 195 + Ok(()) 196 }
+5 -5
templates/home.html
··· 1 {% extends "base.html" %} 2 3 - {% block title %}j23n{% endblock %} 4 5 {% block content %} 6 {% include "home-blurb.html" ignore missing %} 7 8 <section id="documents"> 9 - {% set index = documents.0 %} 10 - {% set pages = documents.1 %} 11 <h2><a href="{{ index.url | safe }}">{{ index.name | title }}</a></h2> 12 <ul> 13 {% for document in pages %} ··· 17 </section> 18 19 <section id="repos"> 20 - {% set index = repos.0 %} 21 - {% set pages = repos.1 %} 22 <h2><a href="{{ index.url | safe }}">{{ index.name | title }}</a></h2> 23 <ul> 24 {% for repo in pages %}
··· 1 {% extends "base.html" %} 2 3 + {% block title %}{% endblock %} 4 5 {% block content %} 6 {% include "home-blurb.html" ignore missing %} 7 8 <section id="documents"> 9 + {% set index = content.documents.index %} 10 + {% set pages = content.documents.pages %} 11 <h2><a href="{{ index.url | safe }}">{{ index.name | title }}</a></h2> 12 <ul> 13 {% for document in pages %} ··· 17 </section> 18 19 <section id="repos"> 20 + {% set index = content.repos.index %} 21 + {% set pages = content.repos.pages %} 22 <h2><a href="{{ index.url | safe }}">{{ index.name | title }}</a></h2> 23 <ul> 24 {% for repo in pages %}
+7 -1
templates/macros.html
··· 8 {%- elif type == "pub.leaflet.blocks.code" %} 9 <pre><code class="{{ block.language }}">{{ block.plaintext }}</code></pre> 10 {%- elif type == "pub.leaflet.blocks.image" %} 11 - <p><img src="{{ block.image | blob_to_url(host=host, did=did) | safe }}"></img></p> 12 {% if block.alt %}<small>{{ block.alt }}</small>{% endif %} 13 {%- elif type == "pub.leaflet.blocks.header" %} 14 {%- set level = block.level | default(value="3") %} ··· 19 <li>{{ self::render_block(block=child.content) }}</li> 20 {%- endfor %} 21 </ul> 22 {%- else %} 23 <p>Unknown block type: {{ type }}</p> 24 {%- endif %}
··· 8 {%- elif type == "pub.leaflet.blocks.code" %} 9 <pre><code class="{{ block.language }}">{{ block.plaintext }}</code></pre> 10 {%- elif type == "pub.leaflet.blocks.image" %} 11 + <p><img src="{{ block.image | blob_to_url(host=site.host, did=site.did) | safe }}"></img></p> 12 {% if block.alt %}<small>{{ block.alt }}</small>{% endif %} 13 {%- elif type == "pub.leaflet.blocks.header" %} 14 {%- set level = block.level | default(value="3") %} ··· 19 <li>{{ self::render_block(block=child.content) }}</li> 20 {%- endfor %} 21 </ul> 22 + {%- elif type == "pub.leaflet.blocks.blockquote" %} 23 + <blockquote> 24 + <p>{{ block.plaintext }}</p> 25 + </blockquote> 26 + {%- elif type == "pub.leaflet.blocks.horizontalRule" %} 27 + <hr /> 28 {%- else %} 29 <p>Unknown block type: {{ type }}</p> 30 {%- endif %}

History

1 round 0 comments
sign up or login to add to the discussion
hello.j23n.com submitted #0
1 commit
expand
add global context for template rendering
expand 0 comments
pull request successfully merged