use std::path::Path; use anyhow::{bail, Error}; use fnv::FnvHashMap; use warp::hyper::StatusCode; use crate::{ config::Logo, favicon::Favicon, image::MyImage, load::load_from_path, misc::Misc, path::UrlPath, post::Post, resp::Response, style::Style, }; pub struct Blog { name: String, description: String, pages: Vec<Post>, posts: Vec<Post>, imgs: Vec<MyImage>, misc: Vec<Misc>, custom_style: Option<Style>, favicon: Option<Favicon>, logo: Option<Logo>, } impl Blog { pub fn new( name: String, description: String, base_path: &Path, logo: Option<Logo>, ) -> Result<Self, Error> { let pages = load_from_path(base_path, &base_path.join("pages"), "pages")?; let posts = load_from_path(base_path, &base_path.join("posts"), "posts")?; let imgs = load_from_path(base_path, &base_path.join("assets/img"), "")?; let misc = load_from_path(base_path, &base_path.join("assets/misc"), "")?; let custom_style = Style::load_if_exists(base_path.join("assets/style.css"))?; let favicon = Favicon::load_if_exists(base_path.join("assets/favicon.png"))?; Ok(Self { name, description, pages, posts, imgs, misc, custom_style, favicon, logo, }) } fn home(&mut self) -> Result<String, Error> { let name = &self.name; let description = &self.description; let mut content = format!( r#"<div class="centered"><h1>{name}</h1>{description}</div><h2>Recent Posts</h2><table>"# ); for post in &self.posts { content.push_str(&post.link()?); } content.push_str("</table>"); Ok(self.dress_page(None, &content)) } fn dress_page(&self, title: Option<&str>, content: &str) -> String { let title = match title { Some(title) => format!("{} | {}", title.trim(), self.name.trim()), None => self.name.trim().to_string(), }; let favicons = match &self.favicon { Some(f) => f.html(), None => "", }; let logo = match &self.logo { Some(logo) => format!( r#"<img src="{}" width="{}" height="{}" alt="{}" class="align-right" />"#, logo.path, logo.width, logo.height, logo.alt ), None => "".to_string(), }; let mut page_links = String::new(); for p in &self.pages { page_links.push_str(&p.link_short()); } format!( r#"<!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/style.css" />{favicons}<title>{title}</title></head><body><div><a href="/">Home</a>{page_links}{logo}</div><hr>{content}</body></html>"# ) } } pub struct RenderedBlog { pages: FnvHashMap<UrlPath, Response>, not_found: Response, } impl RenderedBlog { pub fn get(&self, path: &str) -> (StatusCode, Response) { self.pages .get(&UrlPath::new(path)) .map(|x| (StatusCode::OK, x.clone())) .unwrap_or((StatusCode::NOT_FOUND, self.not_found.clone())) } } impl TryFrom<Blog> for RenderedBlog { type Error = Error; fn try_from(mut b: Blog) -> Result<Self, Error> { b.pages.sort(); b.posts.sort(); let mut pages = FnvHashMap::default(); for p in &b.posts { let body = b.dress_page(Some(&p.title), &p.html()?); insert_path(&mut pages, &p.url, Response::html(body))?; } for p in &b.pages { let body = b.dress_page(Some(&p.title), &p.html()?); insert_path(&mut pages, &p.url, Response::html(body))?; } let mut style = include_str!("assets/style.css").to_string(); if let Some(s) = &b.custom_style { style.push_str(&s.content); } insert_path(&mut pages, "/style.css", Response::css(style))?; let home = b.home()?; insert_path(&mut pages, "/", Response::html(home))?; for img in &b.imgs { let (original_url, original, thumbnail_url, thumbnail) = img.clone().into_responses()?; insert_path(&mut pages, &thumbnail_url, thumbnail)?; insert_path(&mut pages, &original_url, original)?; } for m in &b.misc { insert_path(&mut pages, &m.url.clone(), m.clone().response())?; } let not_found = Response::html(b.dress_page(Some("Page not found"), include_str!("assets/404.html"))); if let Some(fav) = b.favicon { for (p, r) in fav.responses { insert_path(&mut pages, p, r)?; } } dbg!(pages.keys()); Ok(Self { not_found, pages }) } } fn insert_path( pages: &mut FnvHashMap<UrlPath, Response>, path: &str, value: Response, ) -> anyhow::Result<()> { let path = UrlPath::new(path); if pages.contains_key(&path) { bail!( "Cannot serve {} - already serving an asset with the same path", path ); } pages.insert(path, value); Ok(()) }