From 697bf62175b310b325342efe51b50d5b2c763b69 Mon Sep 17 00:00:00 2001
From: peterrabbit <peterrabbit@msi.home>
Date: Tue, 30 Aug 2022 15:28:14 +0200
Subject: [PATCH] wip build html rendering

---
 Cargo.lock                 |   8 +--
 Cargo.toml                 |   2 +-
 example.json               |   4 +-
 src/testing.rs             | 120 ++++++++++++++++++++++++++++++-------
 src/website/html.rs        |   6 +-
 src/website/item.rs        | 100 ++++++++++++++++++++++++++++++-
 src/website/page.rs        |  27 ++++++---
 src/website/website.rs     |  68 ++++++++++++++-------
 templates/new_website.json |  44 +++++++++++---
 9 files changed, 308 insertions(+), 71 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index cc5e17b..ac6b541 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 fdacb1b..c86923c 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 eb31b63..09c95cf 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 dec579b..74a9859 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 447612a..5bdb938 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 9c0ca17..639e975 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 28362f1..c38aa78 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 18e7d70..de26d59 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 c6c845b..749cbd9 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
-- 
GitLab