From 5d479ca112cde06df56e1f97554c2363a25998af Mon Sep 17 00:00:00 2001
From: peterrabbit <peterrabbit@msi.home>
Date: Tue, 30 Aug 2022 18:49:19 +0200
Subject: [PATCH] wip build page body from template

---
 example.json                         |   8 ++-
 src/website/{html.rs => html_doc.rs} |   2 +-
 src/website/item.rs                  | 100 +++++++++++++++++++++------
 src/website/mod.rs                   |   2 +-
 src/website/page.rs                  |  19 +++--
 src/website/website.rs               |   6 +-
 templates/new_website.json           |  12 ++--
 7 files changed, 113 insertions(+), 36 deletions(-)
 rename src/website/{html.rs => html_doc.rs} (97%)

diff --git a/example.json b/example.json
index 09c95cf..c54e4b9 100644
--- a/example.json
+++ b/example.json
@@ -23,11 +23,15 @@
                     {
                         "tag": "p",
                         "text": "Hello Pijar<br />Oui oui oui.",
-                        "class": "pijar_p_class"
+                        "attrs": {
+                            "class": "pijar_p_class"
+                        }
                     },
                     {
                         "tag": "img",
-                        "src": "/img/url.png"
+                        "attrs": {
+                            "src": "/img/url.png"
+                        }
                     }
                 ]
             }
diff --git a/src/website/html.rs b/src/website/html_doc.rs
similarity index 97%
rename from src/website/html.rs
rename to src/website/html_doc.rs
index 5bdb938..06a7d82 100644
--- a/src/website/html.rs
+++ b/src/website/html_doc.rs
@@ -43,7 +43,7 @@ impl HtmlDoc {
         HtmlDoc(html)
     }
 
-    pub fn empty() -> Self {
+    pub fn new() -> Self {
         HtmlDoc(String::new())
     }
 }
diff --git a/src/website/item.rs b/src/website/item.rs
index 639e975..69a80e4 100644
--- a/src/website/item.rs
+++ b/src/website/item.rs
@@ -1,10 +1,11 @@
 use super::css::StyleSheet;
 use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct Item {
-    contents: Vec<ItemContent>,
-    layout: StyleSheet,
+    pub contents: Vec<HtmlElement>,
+    pub layout: StyleSheet,
 }
 
 impl std::fmt::Display for Item {
@@ -14,7 +15,7 @@ impl std::fmt::Display for Item {
             "{}",
             self.contents
                 .iter()
-                .map(|ic| ic.to_string())
+                .map(|item_content| item_content.to_string())
                 .collect::<Vec<String>>()
                 .join("")
         )
@@ -22,13 +23,42 @@ impl std::fmt::Display for Item {
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct ItemContent {
-    tag: String,
-    text: Option<String>,
-    contents: Option<Vec<ItemContent>>,
+pub struct HtmlAttributes(pub HashMap<String, String>);
+
+impl HtmlAttributes {
+    pub fn new() -> Self {
+        HtmlAttributes(HashMap::new())
+    }
+
+    pub fn get<S: Into<String>>(&self, key: S) -> Option<&String> {
+        self.0.get(&key.into())
+    }
 }
 
-impl std::fmt::Display for ItemContent {
+impl std::fmt::Display for HtmlAttributes {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            self.0
+                .iter()
+                .map(|(key, value)| format!(" {}=\"{}\"", key, value))
+                .collect::<Vec<String>>()
+                .join("")
+        )
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct HtmlElement {
+    pub tag: String,
+    pub text: Option<String>,
+    pub contents: Option<Vec<HtmlElement>>,
+    #[serde(default = "HtmlAttributes::new")]
+    pub attrs: HtmlAttributes,
+}
+
+impl std::fmt::Display for HtmlElement {
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
         let body = match &self.contents {
             Some(contents) => contents
@@ -36,14 +66,13 @@ impl std::fmt::Display for ItemContent {
                 .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(),
+            None => match &self.text {
+                Some(text) => text.to_owned(),
+                None => String::new(),
+            },
         };
 
-        write!(f, "<{}>{}</{}>", self.tag, body, self.tag)
+        write!(f, "<{}{}>{}</{}>", self.tag, self.attrs, body, self.tag)
     }
 }
 
@@ -54,30 +83,59 @@ mod test_items {
 
     #[test]
     fn text_item_content_to_string() {
-        let item_content = ItemContent {
+        let item_content = HtmlElement {
             tag: String::from("p"),
             text: Some(String::from("Hello")),
             contents: None,
+            attrs: HtmlAttributes::new(),
         };
 
         assert_eq!(item_content.to_string(), "<p>Hello</p>")
     }
 
+    #[test]
+    fn item_content_with_attrs_to_string() {
+        let mut attrs = HtmlAttributes::new();
+        attrs.0.insert(String::from("id"), String::from("some-id"));
+
+        let mut item_content = 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>");
+
+        item_content
+            .attrs
+            .0
+            .insert(String::from("class"), String::from("some-class"));
+
+        assert_eq!(
+            item_content.to_string(),
+            "<p id=\"some-id\" class=\"some-class\">Hello</p>"
+        );
+    }
+
     #[test]
     fn complex_item_content_to_string() {
-        let item_content = ItemContent {
+        let item_content = HtmlElement {
             tag: String::from("p"),
             text: None,
+            attrs: HtmlAttributes::new(),
             contents: Some(vec![
-                ItemContent {
+                HtmlElement {
                     tag: String::from("span"),
                     text: Some(String::from("Hello ")),
                     contents: None,
+                    attrs: HtmlAttributes::new(),
                 },
-                ItemContent {
+                HtmlElement {
                     tag: String::from("b"),
                     text: Some(String::from("World")),
                     contents: None,
+                    attrs: HtmlAttributes::new(),
                 },
             ]),
         };
@@ -93,15 +151,17 @@ mod test_items {
         let item = Item {
             layout: StyleSheet(HashMap::new()),
             contents: vec![
-                ItemContent {
+                HtmlElement {
                     tag: String::from("span"),
                     text: Some(String::from("Hello ")),
                     contents: None,
+                    attrs: HtmlAttributes::new(),
                 },
-                ItemContent {
+                HtmlElement {
                     tag: String::from("b"),
                     text: Some(String::from("World")),
                     contents: None,
+                    attrs: HtmlAttributes::new(),
                 },
             ],
         };
diff --git a/src/website/mod.rs b/src/website/mod.rs
index 3f286a7..0fd4f5b 100644
--- a/src/website/mod.rs
+++ b/src/website/mod.rs
@@ -1,5 +1,5 @@
 mod css;
-mod html;
+mod html_doc;
 mod item;
 mod page;
 mod website;
diff --git a/src/website/page.rs b/src/website/page.rs
index c38aa78..424f954 100644
--- a/src/website/page.rs
+++ b/src/website/page.rs
@@ -1,5 +1,5 @@
 use super::css::StyleSheet;
-use super::html::HtmlDoc;
+use super::html_doc::HtmlDoc;
 use super::item::*;
 use serde::{Deserialize, Serialize};
 
@@ -10,7 +10,7 @@ pub struct Page {
     pub metadata: PageMetadata,
     #[serde(default = "Vec::new")]
     pub sub_pages: Vec<Page>,
-    #[serde(default = "HtmlDoc::empty")]
+    #[serde(default = "HtmlDoc::new")]
     pub html: HtmlDoc,
     pub template: Option<PageTemplate>,
 }
@@ -19,7 +19,7 @@ pub struct Page {
 pub struct PageTemplate {
     pub layout: StyleSheet,
     pub name: String,
-    pub contents: Vec<ItemContent>,
+    pub contents: Vec<HtmlElement>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -55,10 +55,17 @@ impl Page {
         self.html = HtmlDoc::from_page(self);
     }
 
-    pub fn build_body_template(&mut self, template: &PageTemplate) {
-        self.template = Some(template.clone());
+    pub fn build_body_template(&mut self, template: PageTemplate) {
+        let mut body = template.clone();
+        self.template = Some(template);
+        let err = "Couldn't find page-body placeholder";
+        let body_container = body
+            .contents
+            .iter()
+            .find(|el| el.attrs.get("id").expect(err).eq("page-body"))
+            .expect(err);
+        // HINT use HashMap::extend() to insert many values
         // TODO concat template with page body (template should have a page body placeholder)
-        unimplemented!()
     }
 
     pub fn text_from_key(&self, key: String) -> String {
diff --git a/src/website/website.rs b/src/website/website.rs
index de26d59..2645eaa 100644
--- a/src/website/website.rs
+++ b/src/website/website.rs
@@ -32,7 +32,8 @@ impl WebSite {
             obj.templates
                 .iter()
                 .find(|t| t.name == obj.root_page.template_name)
-                .expect("Page template not found"),
+                .expect("Page template not found")
+                .clone(),
         );
         for p in obj.root_page.sub_pages.iter_mut() {
             p.build_html();
@@ -40,7 +41,8 @@ impl WebSite {
                 obj.templates
                     .iter()
                     .find(|t| t.name == p.template_name)
-                    .expect("Page template not found"),
+                    .expect("Page template not found")
+                    .clone(),
             );
         }
         obj
diff --git a/templates/new_website.json b/templates/new_website.json
index 749cbd9..58d9d3d 100644
--- a/templates/new_website.json
+++ b/templates/new_website.json
@@ -27,12 +27,16 @@
             "layout": {},
             "contents": [
                 {
-                    "tag": "nav",
-                    "contents": []
+                    "tag": "nav"
                 },
                 {
-                    "tag": "footer",
-                    "contents": []
+                    "tag": "div",
+                    "attrs": {
+                        "id": "page-body"
+                    }
+                },
+                {
+                    "tag": "footer"
                 }
             ]
         }
-- 
GitLab