From c0676f8f867e9e9453bda878e8110f2ca5db1f80 Mon Sep 17 00:00:00 2001
From: peterrabbit <peterrabbit@msi.home>
Date: Mon, 22 Aug 2022 16:25:19 +0200
Subject: [PATCH] wip model structure

---
 model.dia              | Bin 3619 -> 3662 bytes
 src/service/page.rs    |   2 +-
 src/website/css.rs     |   5 +++
 src/website/html.rs    |  21 +++++----
 src/website/item.rs    |  17 ++++++++
 src/website/mod.rs     |   2 +
 src/website/page.rs    |  84 ++++++++++++++++++++++--------------
 src/website/website.rs |  96 ++++++++++++++++++++---------------------
 8 files changed, 136 insertions(+), 91 deletions(-)
 create mode 100644 src/website/css.rs
 create mode 100644 src/website/item.rs

diff --git a/model.dia b/model.dia
index 6a0f8a70ac906e294656c8f730efb72eb6e387cd..2c33574f1873a2c4abcc01875ba5adff2d75df05 100644
GIT binary patch
delta 3523
zcmaKucRbXO<HwPaWE>JX<BqH|PKt2WnNe98r;|i<Ms%DJdCSO(%+DETWamW0*(1AT
z-U(URj*xLy$?x<1|NH&DUXRD~@p?R7|Gyru0`MUChDh9by8npb+@}3m!jv-iUoBHe
zef!&}lm_Tk<9joOMx*Ncq5CZI(pzsHI1clT6RrP2C*2TZHFF4|ISL4D>nAz#09#&8
z$|vjH9~HtA0mzx0MeFq?m4(|a&ovuA?~Pryv8k)8lltzAFty-~bWe>Tp8b+qu&Evy
zTkBTp44BYq_o<kjYU|%au)A)c`4t^vR|^Dk&l-(VaNyT|?+-RtZlxCX1PN=1yzpx|
z6R_V@Z<N{hEj+yB5RH;Ks-$J#2X=c8r*N&@n1;n=na{{c@wsxDJ8mcDXIf7_dZm3f
z*jeayRmdLl!`A?dt(Cf$?O@^#yS|_vD^;vt{>(kDuJaJATIo=5zYii<MRGMRiybC^
z#Tu<vUJuWzNWN|wm~Nv#m@LkXn7uVXu6?2)nYCvmU**)f4v*b{52f(1jROM(<)7*N
zynxXZfL;*LA<IqDT1P0qf5X{alWzLb90`ZtPK$H5`e6^745xvX#n>sDbrUPoZ8M!I
z4`dvUKl*3aBYDz0D%EA~=Q^bsKZPS?ihi{Q&4ERb4d!<o5QDmy&H#z99N9bB?_M9f
z5hNFrvxlXmGftr^nVV;Kuv5-@8b%qC&t#*-W`N-xnWXhhnpic}V!Fj@*R0NB8OlTb
zYwS>rIchsAek6;3fVg(R><TFMgq;8L={`DP?R197ViMxNePD9j>ov#kv>3lH+Qm6~
zoF=@!HhUG%5y+1adEs?w>@6ayT9?Xw@7{Cuw4i>&B;7m6ME(9uyX;yu&_@&sS!MuM
z0HDtly2S&Kh=_L>OFcW|`>}*KcWswMQ#abIwxQhpRK%^tGNswoRL^dYL&|#1i|?k2
zSfR6dQx=p86g|CU7%{m^s@Z5`G`^6y<T<$K{S+OQ)W;E);=}m3!AL)U%R9S%5Q^P_
z<UWvm_PCf5)KlxbkROi@yG3w@Y2w#`^ku;!iiLo`Q1jhy7^zV|rG~VS^}DEORJTY;
z`1NJGa&npGCG*P1GwR?~)+8w%g54TlM-gbAqr>-&QN?oU%S5br{@_qUW8Fz_-OFwN
zrsueG?b#}HHW|*Cf%I?e+QyR?G9HSUFWidZ*p(09TvKzub3i9J*%egJ8lZLvgjPSs
zNPEz!%V|1eT}Q^xwrKCJ7Rx>v6MTQy(2gVRdc$<V7B<^cW(ytVU-`Nhg<Cu^(A$oj
zCLZg~9shxqmRI@se^5rot!yS%+ZB)9(F3Ka%-VjBPA>HYDRj@POE1u{T7|DHI2f|`
zY9L>W)tfD>qn*}hvT=}?v%ZIbOR-50R%m%%W>oo=ONEou^6&89@mI-ZV}D+m#FQ(+
zI~wER2V9*bS8l=Mgz(%<ot{nsXKkzah_7B9Qqd{e{IHxazw6@vv8ORN8=1DpLGP<?
zS?nk?HG?CYn!i=$%z=KnX}Aw~+kpJ2@h$XQ4AV^YY~GiD$hX@AqSI->JG5rIz3vbB
zH_~lG`XtVOyYa|H`6kKfWr&UX)-G2O&m+jtRg3&C2Rvu9o#}m^AIqZa+!r3Qwe$Vl
zqa)FI&`&UY7>he*Pd>Jz3jUBg4TyvE_H>C~YzVER^Ric)#XfJpulG{K!9Qf4@6j<&
z7|5FS70LHN;mT87W~~e09T&)y&z5&tTVZ0C{yF_V!y3~tQ6&eK&E_O8bp1ouxlo01
zmz>>GS)*%=zi^Nr8@R5IKj{wg?W^!5GFC6*>rF1eGZ>|1nz))MU4C`3r788L{n44<
zW*TdLCqIpdA>_bEs-V*%N62QLek*a%z*XLPW1`iy2#xT{j6d=~?&|mE0h(J{tZY`u
zi6~iK1{IS^)a9;;X;jxMn$L^vh1Y<tPbVI*-OVCQrB-Hgl$<tu7X5O6mrFioue2RD
zJs?+or?ur4QT>Psdd;O*Mp&c`XPlRUtE8Tfwajt}>vMA%ce84_rS9J3pm6=Qe6wXK
zcS63IaPt-(@w^%E5#CbHQ?_ZhrE>RFCQBWK@I|)O-T)KDzssih;&=w{+nXPUso8%;
zt9GmpDLDz1$9mN%k#{(1Tfu%u*v5f@S+$%%=`k49_Y!A~Yki(sU_6IpLtc4jjGTNk
zdD^&g1C6hbVypjjuQ6n&<ngRyORrtR(R=(zNl%Fwkj6*0HPODhkma0x$hQk~tca~2
z_nQ0blv2tp1R(Y+yZ5D+vt9O<elcL=DkP$BM(?7dMD*Zzzx;7Rk)_)HAzId9JL2!Y
zk`%Yp=rSz%Bp7}g*mUX{+?tRE9J>yS4(Iic=fRKM;7lh^TL>|{`^@_W#92Z&!z@ch
z<=u!aKX6Lx#dP~K6tG`5mG(fWsjp-uUDU?)>Ba<L32kQ8`Ksudz2vsKbKTMQ#gb|y
zKkG=}2MfKR7%1V9?MoDs%cXRyht~fsY-&OABK<C7pnrbzI8;bT?mdAZSwM|(u^vOo
z50%n6q-lX;CE8{}X4!_8LO5jTbMTM6^*zryq)w!Er0|`{YJlc3wvnQeS;|#N3*AL9
zEjN6|k35Xsi|0MSU1J%bA9z_4K|jjfT!CTjGDuJ*B@GrbN_Q6Dhxj7pB0PEeReU8R
zeZf{K6vhQ8-aeqE&iK(Q55_)&(Ep7HNy}BwV0nm+Xho4Oz{PK#zMO&i8kkwG1p2IH
zb1Tz9(mcJdt*B%VhCIK%BN}?hh@3OK#G%HhMo|ox|61_2zeC4I+b5@eOH5-N&1k7f
z=smn4P6*qjJ5;-P_@xnBNs3GV%r*A8#aKYt3Ms)cb*-|Htcx<eRw>j=`8DCWaH)e%
z4P&cOz9B3Q&>de`f+bdJw=(>fPzFhq`wgz(SeU8v`YW?jM3@D}T_%xc9AQ|zRa6P-
z1Lph2<c_zk#*<+Y!|*MYHl4bBy#L(Ga>ik?yeQ_kQGW&{nVr}Ii=)8Vt4uI~dl#yx
z&K{7T=@V4H{B3G@coQEx5v1!3DceJp3wP*CId7;+AFW9Xrzo*11HbqC9?x`s@_jP+
z2E*uf&TWtJ%g<^Lqau3dOOzGq(gB6M8~?GZ+kjepF{<Xl#f#prbB~S|+j_Llm}<_~
ztnJoHE$lEL8S`!?9vtkC&VCqE-5xN|Jb2;r+H+|QA*jQOJq%mx(ywrG^!d6koH*RO
z7Q(6OtxGUHGgY(%WSFU5iGTk;oJOsTihnL4LU=$SYnJ?atD<U$0o^kwiCvE?Jui>w
zmSomA*M>6Qjr)(R=^W<x2ffSF&(k;6iA&vM&f+TNG%LrORJ9HSf`46y_w>Ma6nUvx
zaK5NM>*+L)Zh)gYIBcx5TBvug%AnCI278bnEbWH$&{K3-nLs9N+z;vR#SC@zQvLKc
zdjj+1bSYwY$t2AKjT_8<<uh9`OOXbvOc37p2rFX2wJ%r5=4X>Av^V&6V5USJ&4P7f
z0>`en2QNc5#ooMyxt<C)U9gpRe{3t$bTT8^6Hl21UMy*tre;<rJv)0g|CWK~Kst0d
zh@I<B6GSx>K9IXeb#pww8%##>ZEEx0s8+B@bb6#nYGx%Rxi?f$#a>sfy4S#P1w@iB
zSFLR)Y4;??(~+zrtTC1Vayu~{Dx2Uh|MHBu@Xy6_n!RPiqxh&BGJdOI#V}bH?j`pD
z&H@Xdp~|EPykd!zFqZk$>h%1}vmJE2-|{dWXLix6bjkNQK4n7s6kM=nW}M|FIEti2
zV88f<=bF@(>OmK9FKEH{l};atDt~NA)Q9wa`U*8pbXBEf8+~dmfph6lTo^YZv{`xs
zN`)SmWy3Vm3VOmsZx`+)v2XHhmT%Hc-IW7u)8d%5tXV!TXqKVgY9QuK9l4!1UUcO4
zr}HAgE~-3+P;Qr?VhZ+hP>w+m_Mtb6V8d;w02YCOR({`!anFHYEz2O!?}Tv~G&q|q
z;Nar7qac%39l`h|miJLplhQj}mgaQdNzfvc?Y9(WCs^btt7Bi)(#rN_2F}$!G!RS3
zHAY?N2(0+nQ@q!8L+%6Qo_l2cn2Airm%ExcLt{_3jVGgz)&nEuf-q7iBlTh$g@f-4
z+5?n%3}md|MJs_;p>P+$@ZKRx__Nn$q-jk_!!%VNv48KgR@35Akl38@WXX(W$rM^|
z`BiRK+@!&ikuHiEzpVh>WyRjA>6C95A%LTWHWz_+l<2cXvvG~3X@&>ghIh1@)Fq?i
z)M9hJJ*HG*;t?IbYE)b}Vt90it16xN3sFS!r#RF-<}qXv7FtGpkN`*iVPBH`aR%DZ
zwyf7^+^l8#Sx$6xw0Lu#G*A`(VTEv^0Y_FhpCVi+cP>M)U@(8aRcAF!p1%GF#8ln^
zIWgHxkk|Zqobhfq_YpJ8R@@$WHA2QyZ~r&!JC9EqY&mS~O8eMLW5P0}N@K1xN_^Qa
zdgSO)Til*y1N(OY0$<Ow>ME071i`HQYmpdd7TBfq{-0!a`!v~yDrG4rys)40_gtAX
vLQSRGCbXi?OX1BXU6tsY#zAw@``sUB*p9uDc9-K^&sdH-J#)L&=;{6sEi&sE

delta 3508
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
Jo8F+M`45+@;+6ma

diff --git a/src/service/page.rs b/src/service/page.rs
index 197a50e..7a5ddea 100644
--- a/src/service/page.rs
+++ b/src/service/page.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
 pub async fn page(website: web::Data<WebSite>, pth: web::Path<PathBuf>) -> impl Responder {
     let pth = pth.into_inner();
     match website.get_page_by_url(&pth) {
-        Some(page) => HttpResponse::Ok().body(page.html_doc.to_string()),
+        Some(page) => HttpResponse::Ok().body(page.html.to_string()),
         None => HttpResponse::NotFound().body(format!("Not found {}", pth.display())),
     }
 }
diff --git a/src/website/css.rs b/src/website/css.rs
new file mode 100644
index 0000000..27a5b58
--- /dev/null
+++ b/src/website/css.rs
@@ -0,0 +1,5 @@
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct StyleSheet(pub HashMap<String, String>);
diff --git a/src/website/html.rs b/src/website/html.rs
index 9385527..447612a 100644
--- a/src/website/html.rs
+++ b/src/website/html.rs
@@ -1,8 +1,11 @@
-use crate::website::page::PageData;
+use super::page::Page;
 use regex::{Captures, Regex};
 use serde::{Deserialize, Serialize};
 
-pub const HTML_DOC_TEMPLATE: &'static str = "
+// 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'>
@@ -10,14 +13,14 @@ pub const HTML_DOC_TEMPLATE: &'static str = "
     <meta name='viewport' content='width=device-width, initial-scale=1.0'>
     <meta name='description' content='{description}'>
     <title>{title}</title>
-    <link rel='stylesheet' href='{css}'>
+    {css}
 </head>
 
 <body>
     {body}
 </body>
 
-<script src='{js}'></script>
+{js}
 
 </html>
 ";
@@ -26,21 +29,23 @@ pub const HTML_DOC_TEMPLATE: &'static str = "
 pub struct HtmlDoc(String);
 
 impl HtmlDoc {
-    pub fn from_page_data(page_data: &PageData) -> Self {
+    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();
-                page_data.field_from_str_key(placeholder)
+                page.text_from_key(placeholder)
             })
             .to_string();
 
         HtmlDoc(html)
     }
+}
 
-    pub fn to_string(&self) -> String {
-        self.0.clone()
+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/item.rs b/src/website/item.rs
new file mode 100644
index 0000000..9c0ca17
--- /dev/null
+++ b/src/website/item.rs
@@ -0,0 +1,17 @@
+use super::css::StyleSheet;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Item {
+    contents: Vec<ItemContent>,
+    layout: StyleSheet,
+}
+
+impl std::fmt::Display for Item {
+    fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        unimplemented!()
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct ItemContent {}
diff --git a/src/website/mod.rs b/src/website/mod.rs
index fcd415f..3f286a7 100644
--- a/src/website/mod.rs
+++ b/src/website/mod.rs
@@ -1,4 +1,6 @@
+mod css;
 mod html;
+mod item;
 mod page;
 mod website;
 
diff --git a/src/website/page.rs b/src/website/page.rs
index 4f38e7a..28362f1 100644
--- a/src/website/page.rs
+++ b/src/website/page.rs
@@ -1,47 +1,67 @@
-use crate::website::html::HtmlDoc;
+use super::css::StyleSheet;
+use super::html::HtmlDoc;
+use super::item::*;
 use serde::{Deserialize, Serialize};
 
 #[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>,
+pub struct Page {
+    template: PageTemplate,
+    body: PageBody,
+    pub metadata: PageMetadata,
+    pub sub_pages: Vec<Page>,
+    pub html: HtmlDoc,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct WebPage {
-    pub page_data: PageData,
-    pub html_doc: HtmlDoc,
+pub struct PageTemplate {
+    layout: StyleSheet,
+    name: String,
+    fixed_contents: Vec<ItemContent>,
 }
 
-impl PageData {
-    pub fn to_web_page(&self) -> WebPage {
-        WebPage {
-            page_data: self.clone(),
-            html_doc: HtmlDoc::from_page_data(&self),
-        }
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct PageBody(Vec<Item>);
+
+impl std::fmt::Display for PageBody {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            &self
+                .0
+                .iter()
+                .map(|i| i.to_string())
+                .collect::<Vec<String>>()
+                .join("")
+        )
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct PageMetadata {
+    pub title: String,
+    pub lang: String,
+    pub description: String,
+    pub url_slug: String,
+    pub css_src: Vec<String>,
+    pub js_src: Vec<String>,
+}
+
+impl Page {
+    pub fn build_html(&mut self) {
+        self.html = HtmlDoc::from_page(self);
     }
 
-    pub fn field_from_str_key(&self, key: String) -> String {
+    pub fn text_from_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(),
+            "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_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>>,
-}
diff --git a/src/website/website.rs b/src/website/website.rs
index 6a6cb1f..18e7d70 100644
--- a/src/website/website.rs
+++ b/src/website/website.rs
@@ -1,28 +1,27 @@
 use crate::app::AppConfig;
-use crate::website::page::{PagesTree, WebPage};
+use crate::website::page::{Page, PageTemplate};
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::path::PathBuf;
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct WebSite {
-    pages_tree: PagesTree,
-    pages_index_by_url: HashMap<PathBuf, WebPage>,
+    root_page: Page,
+    assets_index: Vec<String>,
+    templates: Vec<PageTemplate>,
+    pages_index: HashMap<PathBuf, Page>,
 }
 
 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(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();
+        for p in obj.root_page.sub_pages.iter_mut() {
+            p.build_html();
         }
-    }
-
-    pub fn from_json_str(json: &str) -> Self {
-        WebSite::new(serde_json::from_str(json).unwrap())
+        obj
     }
 
     pub fn load(config: &AppConfig) -> WebSite {
@@ -34,52 +33,49 @@ impl WebSite {
             Some(pth) => pth.clone(),
         };
 
-        WebSite::from_json_str(&std::fs::read_to_string(file_path).unwrap())
+        WebSite::from_json(&std::fs::read_to_string(file_path).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);
+    fn build_pages_index(&mut self, root_page: Page, from_url: PathBuf) {
+        let url = from_url.join(&root_page.metadata.url_slug);
 
-        index.insert(url.clone(), page_data.to_web_page());
+        self.pages_index.insert(url.clone(), root_page.clone());
 
-        if let Some(sub_pages) = &pages_tree.sub_pages {
-            for pt in sub_pages {
-                WebSite::create_index_by_url(index, pt, url.clone());
-            }
+        for p in root_page.sub_pages {
+            self.build_pages_index(p, url.clone());
         }
     }
 
-    pub fn get_page_by_url(&self, url: &PathBuf) -> Option<&WebPage> {
-        self.pages_index_by_url.get(&PathBuf::from("/").join(url))
+    fn build_assets_index(&mut self) {
+        unimplemented!();
+    }
+
+    pub fn get_page_by_url(&self, url: &PathBuf) -> Option<&Page> {
+        self.pages_index.get(&PathBuf::from("/").join(url))
     }
 }
 
-#[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_str(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!(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 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>");
-    }
-}
+//         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>");
+//     }
+// }
-- 
GitLab