diff --git a/Cargo.lock b/Cargo.lock index cc5e17be1a803a4f413edd3c86543e7913202419..ac6b5418abaccc981ffa155e6f8c18765e065be1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,18 +1316,18 @@ checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "serde" -version = "1.0.143" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.143" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index fdacb1b7df55403cb20d59fc1b53d2f3a6ca9ee8..c86923c22830affbe358278e647674e322210d0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" actix-web = { version = "4.1.0", features = ["rustls", "secure-cookies"] } rustls = "0.20.6" rustls-pemfile = "1.0.1" -serde = { version = "1.0.143", features = ["derive"] } +serde = { version = "1.0.144", features = ["derive"] } serde_json = "1.0.83" regex = "1.6" fs_extra = "1.2" diff --git a/example.json b/example.json index eb31b637426279b15d23335c61a7394798168dac..09c95cf00e150859dd55493a02f54bf56c4e1ed1 100644 --- a/example.json +++ b/example.json @@ -1,6 +1,6 @@ { "root_page": { - "template": "Pijar Custom Template", + "template_name": "Pijar Custom Template", "metadata": { "title": "Hello Pijar !", "description": "A website for Pijar", @@ -57,7 +57,7 @@ "layout": { "display": "grid" }, - "fixed_contents": [ + "contents": [ { "tag": "nav", "contents": [ diff --git a/src/testing.rs b/src/testing.rs index dec579bd8b59430ca3b65b51dcd7f0f38b8adb35..74a98598f267436d3a3e5cecc3eb2efba33415b4 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,31 +1,109 @@ #[cfg(test)] pub const TEST_JSON_WEBSITE: &'static str = " { - \"page_data\": { - \"title\": \"Test Website\", - \"slug\": \"\", - \"lang\": \"en\", - \"description\": \"A test website\", - \"html_body\": \"<h1>Test Website</h1>\" + \"root_page\": { + \"template_name\": \"TEST TEMPLATE\", + \"metadata\": { + \"title\": \"TEST\", + \"description\": \"TEST DESCRIPTION\", + \"image\": \"https://test/static/images/test.png\", + \"css\": [], + \"lang\":\"en\", + \"js\": [], + \"url_slug\": \"\" + }, + \"body\": [ + { + \"layout\": { + \"display\": \"flex\", + \"padding\": \"20px\" + }, + \"contents\": [ + { + \"tag\": \"h1\", + \"text\": \"testing\" + } + ] + } + ], + \"sub_pages\": [ + { + \"template_name\": \"TEST TEMPLATE\", + \"metadata\": { + \"title\": \"TEST SUBPAGE\", + \"description\": \"TEST DESCRIPTION SUBPAGE\", + \"image\": \"https://test/static/images/test.png\", + \"css\": [], + \"lang\":\"en\", + \"js\": [], + \"url_slug\": \"subpage\" + }, + \"body\": [ + { + \"layout\": { + \"display\": \"flex\", + \"padding\": \"20px\" + }, + \"contents\": [ + { + \"tag\": \"h1\", + \"text\": \"testing subpage\" + } + ] + } + ], + \"sub_pages\": [ + { + \"template_name\": \"TEST TEMPLATE\", + \"metadata\": { + \"title\": \"TEST NESTTED\", + \"description\": \"TEST DESCRIPTION NESTED\", + \"image\": \"https://test/static/images/test.png\", + \"css\": [], + \"js\": [], + \"url_slug\": \"nested\", + \"lang\":\"en\" + }, + \"body\": [ + { + \"layout\": { + \"display\": \"flex\", + \"padding\": \"20px\" + }, + \"contents\": [ + { + \"tag\": \"h1\", + \"text\": \"testing nested\" + } + ] + } + ] + } + ] + } + ] }, - \"sub_pages\": [ + \"assets_index\": { + \"images\": [], + \"sounds\": [], + \"videos\": [], + \"docs\": [], + \"source_code\": [] + }, + \"templates\": [ { - \"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>\" + \"name\": \"TEST TEMPLATE\", + \"layout\": { + \"display\": \"grid\" }, - \"sub_pages\": [ + \"contents\": [ + { + \"tag\": \"nav\", + \"contents\": [] + }, { - \"page_data\": { - \"title\": \"Nested page\", - \"lang\": \"en\", - \"slug\": \"nested\", - \"description\": \"Nested testing page\", - \"html_body\": \"<h1>Nested page</h1>\" - } + \"tag\": \"footer\", + \"contents\": [] } ] } diff --git a/src/website/html.rs b/src/website/html.rs index 447612a2a1e9a5af915f3e404d5005bd0c526eec..5bdb93856632f14ad31ade40fb65deb11c915a83 100644 --- a/src/website/html.rs +++ b/src/website/html.rs @@ -35,13 +35,17 @@ impl HtmlDoc { 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(); + let placeholder = placeholder[1..placeholder.len() - 1].to_owned(); // strip the brackets page.text_from_key(placeholder) }) .to_string(); HtmlDoc(html) } + + pub fn empty() -> Self { + HtmlDoc(String::new()) + } } impl std::fmt::Display for HtmlDoc { diff --git a/src/website/item.rs b/src/website/item.rs index 9c0ca17b1ce184333c9494d66962795e4c9489e6..639e975d355704383347de2fe1be76f2280ae86d 100644 --- a/src/website/item.rs +++ b/src/website/item.rs @@ -8,10 +8,104 @@ pub struct Item { } impl std::fmt::Display for Item { - fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { - unimplemented!() + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + self.contents + .iter() + .map(|ic| ic.to_string()) + .collect::<Vec<String>>() + .join("") + ) } } #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ItemContent {} +pub struct ItemContent { + tag: String, + text: Option<String>, + contents: Option<Vec<ItemContent>>, +} + +impl std::fmt::Display for ItemContent { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let body = match &self.contents { + Some(contents) => contents + .iter() + .map(|item| item.to_string()) + .collect::<Vec<String>>() + .join(""), + None => self + .text + .as_ref() + .expect("Either contents or text field must be provided") + .to_string(), + }; + + write!(f, "<{}>{}</{}>", self.tag, body, self.tag) + } +} + +#[cfg(test)] +mod test_items { + use super::*; + use std::collections::HashMap; + + #[test] + fn text_item_content_to_string() { + let item_content = ItemContent { + tag: String::from("p"), + text: Some(String::from("Hello")), + contents: None, + }; + + assert_eq!(item_content.to_string(), "<p>Hello</p>") + } + + #[test] + fn complex_item_content_to_string() { + let item_content = ItemContent { + tag: String::from("p"), + text: None, + contents: Some(vec![ + ItemContent { + tag: String::from("span"), + text: Some(String::from("Hello ")), + contents: None, + }, + ItemContent { + tag: String::from("b"), + text: Some(String::from("World")), + contents: None, + }, + ]), + }; + + assert_eq!( + item_content.to_string(), + "<p><span>Hello </span><b>World</b></p>" + ) + } + + #[test] + fn item_to_string() { + let item = Item { + layout: StyleSheet(HashMap::new()), + contents: vec![ + ItemContent { + tag: String::from("span"), + text: Some(String::from("Hello ")), + contents: None, + }, + ItemContent { + tag: String::from("b"), + text: Some(String::from("World")), + contents: None, + }, + ], + }; + + assert_eq!(item.to_string(), "<span>Hello </span><b>World</b>") + } +} diff --git a/src/website/page.rs b/src/website/page.rs index 28362f1722dbb3fc85b8ed950cbf0671da578628..c38aa7845f026b6af73849bbe47f54159e18b01f 100644 --- a/src/website/page.rs +++ b/src/website/page.rs @@ -5,18 +5,21 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Page { - template: PageTemplate, - body: PageBody, + pub template_name: String, + pub body: PageBody, pub metadata: PageMetadata, + #[serde(default = "Vec::new")] pub sub_pages: Vec<Page>, + #[serde(default = "HtmlDoc::empty")] pub html: HtmlDoc, + pub template: Option<PageTemplate>, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PageTemplate { - layout: StyleSheet, - name: String, - fixed_contents: Vec<ItemContent>, + pub layout: StyleSheet, + pub name: String, + pub contents: Vec<ItemContent>, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -43,8 +46,8 @@ pub struct PageMetadata { pub lang: String, pub description: String, pub url_slug: String, - pub css_src: Vec<String>, - pub js_src: Vec<String>, + pub css: Vec<String>, + pub js: Vec<String>, } impl Page { @@ -52,6 +55,12 @@ impl Page { self.html = HtmlDoc::from_page(self); } + pub fn build_body_template(&mut self, template: &PageTemplate) { + self.template = Some(template.clone()); + // TODO concat template with page body (template should have a page body placeholder) + unimplemented!() + } + pub fn text_from_key(&self, key: String) -> String { match &key[..] { "title" => self.metadata.title.to_owned(), @@ -59,8 +68,8 @@ impl Page { "description" => self.metadata.description.to_owned(), "slug" => self.metadata.url_slug.to_owned(), "body" => self.body.to_string(), - // "css" => self.css_src.as_ref().unwrap_or(&String::new()).to_owned(), - // "js" => self.js_src.as_ref().unwrap_or(&String::new()).to_owned(), + // "css" => self.css.as_ref().unwrap_or(&String::new()).to_owned(), + // "js" => self.js.as_ref().unwrap_or(&String::new()).to_owned(), _ => String::new(), } } diff --git a/src/website/website.rs b/src/website/website.rs index 18e7d70f16208f4281664e96016de824aef5269a..de26d59b9bf30a01399247dc212e5c2fe07d8642 100644 --- a/src/website/website.rs +++ b/src/website/website.rs @@ -7,19 +7,41 @@ use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WebSite { root_page: Page, - assets_index: Vec<String>, + assets_index: AssetsIndex, templates: Vec<PageTemplate>, + #[serde(default = "HashMap::new")] pages_index: HashMap<PathBuf, Page>, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AssetsIndex { + pub images: Vec<String>, + pub sounds: Vec<String>, + pub videos: Vec<String>, + pub docs: Vec<String>, + pub source_code: Vec<String>, +} + 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(); + obj.root_page.build_body_template( + obj.templates + .iter() + .find(|t| t.name == obj.root_page.template_name) + .expect("Page template not found"), + ); for p in obj.root_page.sub_pages.iter_mut() { p.build_html(); + p.build_body_template( + obj.templates + .iter() + .find(|t| t.name == p.template_name) + .expect("Page template not found"), + ); } obj } @@ -47,7 +69,7 @@ impl WebSite { } fn build_assets_index(&mut self) { - unimplemented!(); + return; } pub fn get_page_by_url(&self, url: &PathBuf) -> Option<&Page> { @@ -55,27 +77,27 @@ impl WebSite { } } -// #[cfg(test)] -// mod test_website { -// use super::*; -// use crate::testing::TEST_JSON_WEBSITE; +#[cfg(test)] +mod test_website { + use super::*; + use crate::testing::TEST_JSON_WEBSITE; -// #[test] -// fn test_index_pages_by_slug() { -// let website = WebSite::from_json(TEST_JSON_WEBSITE); -// 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>"); + #[test] + fn test_index_pages_by_slug() { + let website = WebSite::from_json(TEST_JSON_WEBSITE); + let root_page = website.get_page_by_url(&PathBuf::from("/")); + assert!(root_page.is_some()); + let root_page = root_page.unwrap(); + assert_eq!(format!("{}", root_page.body), "<h1>testing</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 sub_page = website.get_page_by_url(&PathBuf::from("subpage")); + assert!(sub_page.is_some()); + let sub_page = sub_page.unwrap(); + assert_eq!(format!("{}", sub_page.body), "<h1>testing subpage</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>"); -// } -// } + 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!(format!("{}", nested_page.body), "<h1>testing nested</h1>"); + } +} diff --git a/templates/new_website.json b/templates/new_website.json index c6c845b183ec34d9d3fb805cb176ac14d07b0163..749cbd93cfd3cf078ac82568ad8cf692e337cecd 100644 --- a/templates/new_website.json +++ b/templates/new_website.json @@ -1,10 +1,40 @@ { - "page_data": { - "title": "New Website", - "lang": "en", - "slug": "", - "description": "A new website", - "html_body": "<h1>New Website</h1>" + "root_page": { + "template_name": "basic", + "metadata": { + "title": "New Website", + "description": "A new website", + "image": "", + "css": [], + "js": [], + "url_slug": "" + }, + "body": [ + { + "layout": {}, + "contents": [ + { + "tag": "h1", + "text": "New website" + } + ] + } + ] }, - "sub_pages": [] + "templates": [ + { + "name": "basic", + "layout": {}, + "contents": [ + { + "tag": "nav", + "contents": [] + }, + { + "tag": "footer", + "contents": [] + } + ] + } + ] } \ No newline at end of file