use crate::metadata::Metadata; use crate::template::{Context, Value}; use std::collections::HashMap; pub struct Post { pub metadata: Metadata, pub index: u8, pub html: String, pub raw: String, } impl Post { /// Converts a post into a template `Context` object pub fn to_template_context(posts: &[Post]) -> Context { let mut context = HashMap::new(); let posts_collection = posts.iter().map(Post::to_template_value).collect(); context.insert("has_posts".to_string(), Value::Bool(!posts.is_empty())); // If you really reach over 18 quintillion posts, I guess you can send // me a bug-request via e-mail. context.insert( "posts_length".to_string(), Value::Unsigned(posts.len().try_into().unwrap_or(u64::MAX)), ); context.insert("posts".to_string(), Value::Collection(posts_collection)); context } pub fn to_template_value(&self) -> Context { let mut context = HashMap::new(); context.insert("id".to_string(), Value::String(self.metadata.id.clone())); context.insert( "created_on".to_string(), Value::Unsigned(self.metadata.created_on), ); if let Some(created_on_utc) = self.metadata.created_on_utc() { context.insert("created_on_utc".to_string(), Value::String(created_on_utc)); } context.insert("title".to_string(), Value::String(self.title())); context.insert("index".to_string(), Value::Unsigned(self.index.into())); context.insert("html".to_string(), Value::String(self.html.clone())); context.insert( "escaped_html".to_string(), Value::String(self.escaped_html().clone()), ); context.insert("raw".to_string(), Value::String(self.raw.clone())); context } fn title(&self) -> String { self.raw .lines() .next() .unwrap_or("") .replace('#', "") .replace('&', "&") .trim() .to_string() } fn escaped_html(&self) -> String { self.html .replace('&', "&") .replace('<', "<") .replace('>', ">") .replace('"', """) .replace('\'', "'") } } #[cfg(test)] mod tests { use super::*; use crate::template::Value; #[test] fn test_creates_context_with_empty_posts() { let context = Post::to_template_context(&[]); assert_eq!(context.get("has_posts"), Some(&Value::Bool(false))); assert_eq!(context.get("posts_length"), Some(&Value::Unsigned(0))); if let Some(Value::Collection(posts)) = &context.get("posts") { assert!(posts.is_empty()); } else { panic!("The posts context was not the right type."); } } #[test] fn test_creates_context_with_post_slice() { let post = Post { metadata: Metadata { id: "cool".to_string(), created_on: 1_736_035_200_000, }, index: 28, html: "
beep boop
".to_string(), raw: "beep boop".to_string(), }; let context = Post::to_template_context(&[post]); assert_eq!(context.get("has_posts"), Some(&Value::Bool(true))); assert_eq!(context.get("posts_length"), Some(&Value::Unsigned(1))); if let Some(Value::Collection(posts)) = &context.get("posts") { if let Some(post) = posts.first() { assert_eq!(post["id"], Value::String("cool".to_string())); } else { panic!("The template context had no posts"); } } else { panic!("The posts context was not the right type."); } } #[test] fn test_converts_post_to_context() { let post = Post { metadata: Metadata { id: "cool".to_string(), created_on: 1_736_035_200_000, }, index: 28, html: "beep boop
".to_string(), raw: "\ # Hello everybody beep boop" .to_string(), }; let context = post.to_template_value(); assert_eq!(context.get("id"), Some(&Value::String("cool".to_string()))); assert_eq!( context.get("created_on"), Some(&Value::Unsigned(1_736_035_200_000)) ); assert_eq!( context.get("created_on_utc"), Some(&Value::String( "Sun, 05 Jan 2025 00:00:00 +0000".to_string() )) ); assert_eq!( context.get("title"), Some(&Value::String("Hello everybody".to_string())) ); assert_eq!(context.get("index"), Some(&Value::Unsigned(28))); assert_eq!( context.get("html"), Some(&Value::String("beep boop
".to_string())) ); assert_eq!( context.get("raw"), Some(&Value::String( "\ # Hello everybody beep boop" .to_string() )) ); } }