From 2e57f96872aab0009a8954aaf72ae9777a97b0b1 Mon Sep 17 00:00:00 2001 From: peterrabbit <peterrabbit@msi.home> Date: Wed, 31 Aug 2022 10:42:16 +0200 Subject: [PATCH] website build pages --- example.json | 3 +- src/website/{item.rs => html.rs} | 80 ++++++++++++++++++++---- src/website/html_doc.rs | 55 ----------------- src/website/mod.rs | 3 +- src/website/page.rs | 101 +++++++++++++++++++++++++------ src/website/website.rs | 28 +++++++-- templates/new_website.json | 5 +- 7 files changed, 180 insertions(+), 95 deletions(-) rename src/website/{item.rs => html.rs} (59%) delete mode 100644 src/website/html_doc.rs diff --git a/example.json b/example.json index f115628..1c11d16 100644 --- a/example.json +++ b/example.json @@ -7,7 +7,8 @@ "image": "https://pijar.com/static/images/pijar_pic.png", "css": [], "js": [], - "url_slug": "" + "url_slug": "", + "lang": "en" }, "body": [ { diff --git a/src/website/item.rs b/src/website/html.rs similarity index 59% rename from src/website/item.rs rename to src/website/html.rs index 3a59435..0563966 100644 --- a/src/website/item.rs +++ b/src/website/html.rs @@ -1,6 +1,64 @@ +use super::page::Page; +use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +pub const CSS_LINK_FRAGMENT: &'static str = "<link rel='stylesheet' href='{url}'>"; +pub const SCRIPT_FRAGMENT: &'static str = "<script src='{url}'></script>"; + +const HTML_DOC_TEMPLATE: &'static str = " +<html lang='{lang}'> +<head> + <meta charset='UTF-8'> + <meta http-equiv='X-UA-Compatible' content='IE=edge'> + <meta name='viewport' content='width=device-width, initial-scale=1.0'> + <meta name='description' content='{description}'> + <title>{title}</title> + {css} +</head> + +<body> + {body} +</body> + +{js} + +</html> +"; + +pub fn replace_placeholders(template: &str, map: HashMap<String, String>) -> String { + let re = Regex::new(r#"\{[a-z]+\}"#).unwrap(); + let def = String::new(); + re.replace_all(template, |captures: &Captures| { + let placeholder = captures.iter().next().unwrap().unwrap().as_str(); + let placeholder = placeholder[1..placeholder.len() - 1].to_owned(); // strip the brackets + match map.get(&placeholder) { + Some(s) => s, + None => &def, + } + }) + .to_string() +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct HtmlDoc(String); + +impl HtmlDoc { + pub fn from_page(page: &Page) -> Self { + HtmlDoc(replace_placeholders(HTML_DOC_TEMPLATE, page.to_map())) + } + + pub fn new() -> Self { + HtmlDoc(String::new()) + } +} + +impl std::fmt::Display for HtmlDoc { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HtmlAttributes(pub HashMap<String, String>); @@ -48,7 +106,7 @@ impl std::fmt::Display for HtmlElement { let body = match &self.contents { Some(contents) => contents .iter() - .map(|item| item.to_string()) + .map(|el| el.to_string()) .collect::<Vec<String>>() .join(""), None => match &self.text { @@ -62,39 +120,39 @@ impl std::fmt::Display for HtmlElement { } #[cfg(test)] -mod test_items { +mod test_html_elements { use super::*; #[test] - fn text_item_content_to_string() { - let item_content = HtmlElement { + fn text_html_element_to_string() { + let html_el = HtmlElement { tag: String::from("p"), text: Some(String::from("Hello")), contents: None, attrs: HtmlAttributes::new(), }; - assert_eq!(item_content.to_string(), "<p>Hello</p>") + assert_eq!(html_el.to_string(), "<p>Hello</p>") } #[test] - fn item_content_with_attrs_to_string() { + fn html_el_with_attrs_to_string() { let mut attrs = HtmlAttributes::new(); attrs.0.insert(String::from("id"), String::from("some-id")); - let item_content = HtmlElement { + let html_el = HtmlElement { tag: String::from("p"), text: Some(String::from("Hello")), contents: None, attrs: attrs.clone(), }; - assert_eq!(item_content.to_string(), "<p id=\"some-id\">Hello</p>"); + assert_eq!(html_el.to_string(), "<p id=\"some-id\">Hello</p>"); } #[test] - fn complex_item_content_to_string() { - let item_content = HtmlElement { + fn complex_html_el_to_string() { + let html_el = HtmlElement { tag: String::from("p"), text: None, attrs: HtmlAttributes::new(), @@ -115,7 +173,7 @@ mod test_items { }; assert_eq!( - item_content.to_string(), + html_el.to_string(), "<p><span>Hello </span><b>World</b></p>" ) } diff --git a/src/website/html_doc.rs b/src/website/html_doc.rs deleted file mode 100644 index 06a7d82..0000000 --- a/src/website/html_doc.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::page::Page; -use regex::{Captures, Regex}; -use serde::{Deserialize, Serialize}; - -// const CSS_LINK_FRAGMENT: &'static str = "<link rel='stylesheet' href='{url}'>"; -// const SCRIPT_FRAGMENT: &'static str = "<script src='{url}'></script>"; - -const HTML_DOC_TEMPLATE: &'static str = " -<html lang='{lang}'> -<head> - <meta charset='UTF-8'> - <meta http-equiv='X-UA-Compatible' content='IE=edge'> - <meta name='viewport' content='width=device-width, initial-scale=1.0'> - <meta name='description' content='{description}'> - <title>{title}</title> - {css} -</head> - -<body> - {body} -</body> - -{js} - -</html> -"; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct HtmlDoc(String); - -impl HtmlDoc { - pub fn from_page(page: &Page) -> Self { - let re = Regex::new(r#"\{[a-z]+\}"#).unwrap(); - - let html = re - .replace_all(HTML_DOC_TEMPLATE, |captures: &Captures| { - let placeholder = captures.iter().next().unwrap().unwrap().as_str(); - let placeholder = placeholder[1..placeholder.len() - 1].to_owned(); // strip the brackets - page.text_from_key(placeholder) - }) - .to_string(); - - HtmlDoc(html) - } - - pub fn new() -> Self { - HtmlDoc(String::new()) - } -} - -impl std::fmt::Display for HtmlDoc { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/src/website/mod.rs b/src/website/mod.rs index 0fd4f5b..df329bf 100644 --- a/src/website/mod.rs +++ b/src/website/mod.rs @@ -1,6 +1,5 @@ mod css; -mod html_doc; -mod item; +mod html; mod page; mod website; diff --git a/src/website/page.rs b/src/website/page.rs index 68f47a9..c4b33a7 100644 --- a/src/website/page.rs +++ b/src/website/page.rs @@ -1,7 +1,7 @@ use super::css::StyleSheet; -use super::html_doc::HtmlDoc; -use super::item::*; +use super::html::{replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, SCRIPT_FRAGMENT}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Page { @@ -46,8 +46,66 @@ pub struct PageMetadata { pub lang: String, pub description: String, pub url_slug: String, - pub css: Vec<String>, - pub js: Vec<String>, + #[serde(default = "CSSLinks::new")] + pub css: CSSLinks, + #[serde(default = "JSLinks::new")] + pub js: JSLinks, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CSSLinks(Vec<String>); + +impl CSSLinks { + pub fn new() -> Self { + CSSLinks(vec!["/static/default/style.css".to_owned()]) + } +} + +impl std::fmt::Display for CSSLinks { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + &self + .0 + .iter() + .map(|url| replace_placeholders(CSS_LINK_FRAGMENT, { + let mut map = HashMap::new(); + map.insert("url".to_string(), url.to_owned()); + map + })) + .collect::<Vec<String>>() + .join("") + ) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct JSLinks(Vec<String>); + +impl JSLinks { + pub fn new() -> Self { + JSLinks(vec!["/static/default/script.js".to_owned()]) + } +} + +impl std::fmt::Display for JSLinks { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + &self + .0 + .iter() + .map(|url| replace_placeholders(SCRIPT_FRAGMENT, { + let mut map = HashMap::new(); + map.insert("url".to_string(), url.to_owned()); + map + })) + .collect::<Vec<String>>() + .join("") + ) + } } impl Page { @@ -56,28 +114,35 @@ impl Page { } pub fn build_body_template(&mut self, template: PageTemplate) { - let mut body = template.clone(); + let mut complete_body = template.clone(); self.template = Some(template); let err = "Couldn't find page-body placeholder"; - body.contents + + // Insert page body inside the template page-body container. + // Then replace the page body by the complete template + complete_body + .contents .iter_mut() .find(|el| el.attrs.get("id").unwrap_or(&String::new()).eq("page-body")) .expect(err) .set_contents(self.body.0.clone()); - self.body.0 = body.contents; + self.body.0 = complete_body.contents; } - pub fn text_from_key(&self, key: String) -> String { - match &key[..] { - "title" => self.metadata.title.to_owned(), - "lang" => self.metadata.lang.to_owned(), - "description" => self.metadata.description.to_owned(), - "slug" => self.metadata.url_slug.to_owned(), - "body" => self.body.to_string(), - // "css" => self.css.as_ref().unwrap_or(&String::new()).to_owned(), - // "js" => self.js.as_ref().unwrap_or(&String::new()).to_owned(), - _ => String::new(), - } + pub fn to_map(&self) -> HashMap<String, String> { + let mut map = HashMap::new(); + map.insert("title".to_string(), self.metadata.title.to_owned()); + map.insert("lang".to_string(), self.metadata.lang.to_owned()); + map.insert( + "description".to_string(), + self.metadata.description.to_owned(), + ); + map.insert("slug".to_string(), self.metadata.url_slug.to_owned()); + map.insert("body".to_string(), self.body.to_string()); + map.insert("css".to_string(), self.metadata.css.to_string()); + map.insert("js".to_string(), self.metadata.js.to_string()); + + map } } diff --git a/src/website/website.rs b/src/website/website.rs index 2645eaa..20274b3 100644 --- a/src/website/website.rs +++ b/src/website/website.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WebSite { root_page: Page, + #[serde(default = "AssetsIndex::new")] assets_index: AssetsIndex, templates: Vec<PageTemplate>, #[serde(default = "HashMap::new")] @@ -22,12 +23,22 @@ pub struct AssetsIndex { pub source_code: Vec<String>, } +impl AssetsIndex { + pub fn new() -> Self { + AssetsIndex { + images: Vec::new(), + sounds: Vec::new(), + videos: Vec::new(), + docs: Vec::new(), + source_code: Vec::new(), + } + } +} + impl WebSite { pub fn from_json(json: &str) -> Self { - let mut obj: Self = serde_json::from_str(json).unwrap(); - obj.build_assets_index(); - obj.build_pages_index(obj.root_page.clone(), PathBuf::from("/")); - obj.root_page.build_html(); + let mut obj: WebSite = serde_json::from_str(json).unwrap(); + obj.root_page.build_body_template( obj.templates .iter() @@ -35,8 +46,10 @@ impl WebSite { .expect("Page template not found") .clone(), ); + + obj.root_page.build_html(); + for p in obj.root_page.sub_pages.iter_mut() { - p.build_html(); p.build_body_template( obj.templates .iter() @@ -44,7 +57,12 @@ impl WebSite { .expect("Page template not found") .clone(), ); + p.build_html(); } + + obj.build_assets_index(); + obj.build_pages_index(obj.root_page.clone(), PathBuf::from("/")); + obj } diff --git a/templates/new_website.json b/templates/new_website.json index 29706c5..c13fb2d 100644 --- a/templates/new_website.json +++ b/templates/new_website.json @@ -5,9 +5,8 @@ "title": "New Website", "description": "A new website", "image": "", - "css": [], - "js": [], - "url_slug": "" + "url_slug": "", + "lang": "en" }, "body": [ { -- GitLab