use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PageData { pub title: String, pub lang: String, pub description: String, pub slug: String, pub html_body: String, pub css_src: Option<String>, pub js_src: Option<String>, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HtmlDoc(String); impl HtmlDoc { fn load_template() -> String { std::fs::read_to_string( std::env::current_dir() .unwrap() .join("templates") .join("html_doc.html"), ) .expect("Missing html_doc template") } pub fn from_page_data(page_data: &PageData) -> Self { let re = Regex::new(r#"\{[a-z]+\}"#).unwrap(); let html = re .replace_all(&HtmlDoc::load_template(), |captures: &Captures| { let placeholder = captures.iter().next().unwrap().unwrap().as_str(); let placeholder = placeholder[1..placeholder.len() - 1].to_owned(); page_data.field_from_str_key(placeholder) }) .to_string(); HtmlDoc(html) } pub fn to_string(&self) -> String { self.0.clone() } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WebPage { pub page_data: PageData, pub html_doc: HtmlDoc, } impl PageData { pub fn to_web_page(&self) -> WebPage { WebPage { page_data: self.clone(), html_doc: HtmlDoc::from_page_data(&self), } } pub fn field_from_str_key(&self, key: String) -> String { match &key[..] { "title" => self.title.to_owned(), "lang" => self.lang.to_owned(), "description" => self.description.to_owned(), "slug" => self.slug.to_owned(), "body" => self.html_body.to_owned(), "css" => self.css_src.as_ref().unwrap_or(&String::new()).to_owned(), "js" => self.js_src.as_ref().unwrap_or(&String::new()).to_owned(), _ => String::new(), } } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PagesTree { pub page_data: PageData, pub sub_pages: Option<Vec<PagesTree>>, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WebSite { pages_tree: PagesTree, pages_index_by_url: HashMap<PathBuf, WebPage>, } impl WebSite { pub fn new(pages_tree: PagesTree) -> Self { let mut pages_index_by_url = HashMap::new(); WebSite::create_index_by_url(&mut pages_index_by_url, &pages_tree, PathBuf::from("/")); WebSite { pages_tree, pages_index_by_url, } } pub fn from_json_str(json: &str) -> Self { WebSite::new(serde_json::from_str(json).unwrap()) } fn create_index_by_url( index: &mut HashMap<PathBuf, WebPage>, pages_tree: &PagesTree, from_url: PathBuf, ) { let page_data = pages_tree.page_data.clone(); let url = from_url.join(&page_data.slug); index.insert(url.clone(), page_data.to_web_page()); if let Some(sub_pages) = &pages_tree.sub_pages { for pt in sub_pages { WebSite::create_index_by_url(index, pt, url.clone()); } } } pub fn get_page_by_url(&self, url: &PathBuf) -> Option<&WebPage> { self.pages_index_by_url.get(&PathBuf::from("/").join(url)) } } #[cfg(test)] mod test_website { use super::*; const JSON_TEMPLATE: &'static str = " { \"page_data\": { \"title\": \"Test Website\", \"slug\": \"\", \"lang\": \"en\", \"description\": \"A test website\", \"html_body\": \"<h1>Test Website</h1>\" }, \"sub_pages\": [ { \"page_data\": { \"title\": \"A sub page\", \"slug\": \"subpage\", \"lang\": \"en\", \"description\": \"A sub page of the testing web site\", \"html_body\": \"<h1>A sub page</h1>\" }, \"sub_pages\": [ { \"page_data\": { \"title\": \"Nested page\", \"lang\": \"en\", \"slug\": \"nested\", \"description\": \"Nested testing page\", \"html_body\": \"<h1>Nested page</h1>\" } } ] } ] } "; #[test] fn test_index_pages_by_slug() { let website = WebSite::from_json_str(JSON_TEMPLATE); let root_page = website.get_page_by_url(&PathBuf::from("/")); assert!(root_page.is_some()); let root_page = root_page.unwrap(); assert_eq!(root_page.page_data.html_body, "<h1>Test Website</h1>"); let sub_page = website.get_page_by_url(&PathBuf::from("subpage")); assert!(sub_page.is_some()); let sub_page = sub_page.unwrap(); assert_eq!(sub_page.page_data.html_body, "<h1>A sub page</h1>"); let nested_page = website.get_page_by_url(&PathBuf::from("subpage/nested")); assert!(nested_page.is_some()); let nested_page = nested_page.unwrap(); assert_eq!(nested_page.page_data.html_body, "<h1>Nested page</h1>"); } }