Skip to content
Snippets Groups Projects
blog.rs 5.21 KiB
Newer Older
Stephen D's avatar
Stephen D committed
use std::path::Path;

Stephen D's avatar
Stephen D committed
use anyhow::{bail, Error};
Stephen D's avatar
Stephen D committed
use warp::hyper::StatusCode;

Stephen D's avatar
Stephen D committed
use crate::{
Stephen D's avatar
Stephen D committed
    config::Logo, favicon::Favicon, image::MyImage, load::load_from_path, misc::Misc,
    path::UrlPath, post::Post, resp::Response, style::Style,
Stephen D's avatar
Stephen D committed
};
Stephen D's avatar
Stephen D committed

pub struct Blog {
Stephen D's avatar
Stephen D committed
    name: String,
    description: String,

Stephen D's avatar
Stephen D committed
    pages: Vec<Post>,
Stephen D's avatar
Stephen D committed
    posts: Vec<Post>,
    imgs: Vec<MyImage>,
Stephen D's avatar
Stephen D committed
    custom_style: Option<Style>,
Stephen D's avatar
Stephen D committed
    favicon: Option<Favicon>,
Stephen D's avatar
Stephen D committed
    logo: Option<Logo>,
Stephen D's avatar
Stephen D committed
}

impl Blog {
Stephen D's avatar
Stephen D committed
    pub fn new(
        name: String,
        description: String,
        base_path: &Path,
        logo: Option<Logo>,
    ) -> Result<Self, Error> {
Stephen D's avatar
Stephen D committed
        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"))?;
Stephen D's avatar
Stephen D committed

        Ok(Self {
Stephen D's avatar
Stephen D committed
            name,
            description,
Stephen D's avatar
Stephen D committed
            pages,
Stephen D's avatar
Stephen D committed
            posts,
            imgs,
            misc,
            custom_style,
Stephen D's avatar
Stephen D committed
            favicon,
Stephen D's avatar
Stephen D committed
            logo,
Stephen D's avatar
Stephen D committed
        })
Stephen D's avatar
Stephen D committed
    }

Stephen D's avatar
Stephen D committed
    fn home(&mut self) -> Result<String, Error> {
Stephen D's avatar
Stephen D committed
        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>"#
        );
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        for post in &self.posts {
Stephen D's avatar
Stephen D committed
            content.push_str(&post.link()?);
Stephen D's avatar
Stephen D committed
        }

Stephen D's avatar
Stephen D committed
        content.push_str("</table>");

Stephen D's avatar
Stephen D committed
        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>"#
        )
Stephen D's avatar
Stephen D committed
    }
}

pub struct RenderedBlog {
    pages: FnvHashMap<UrlPath, Response>,
Stephen D's avatar
Stephen D committed
    not_found: Response,
Stephen D's avatar
Stephen D committed
}

impl RenderedBlog {
Stephen D's avatar
Stephen D committed
    pub fn get(&self, path: &str) -> (StatusCode, Response) {
Stephen D's avatar
Stephen D committed
        self.pages
Stephen D's avatar
Stephen D committed
            .get(&UrlPath::new(path))
Stephen D's avatar
Stephen D committed
            .map(|x| (StatusCode::OK, x.clone()))
            .unwrap_or((StatusCode::NOT_FOUND, self.not_found.clone()))
Stephen D's avatar
Stephen D committed
    }
}

impl TryFrom<Blog> for RenderedBlog {
    type Error = Error;

Stephen D's avatar
Stephen D committed
    fn try_from(mut b: Blog) -> Result<Self, Error> {
Stephen D's avatar
Stephen D committed
        b.pages.sort();
        b.posts.sort();

        let mut pages = FnvHashMap::default();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        for p in &b.posts {
Stephen D's avatar
Stephen D committed
            let body = b.dress_page(Some(&p.title), &p.html()?);
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
            insert_path(&mut pages, &p.url, Response::html(body))?;
Stephen D's avatar
Stephen D committed
        }

        for p in &b.pages {
Stephen D's avatar
Stephen D committed
            let body = b.dress_page(Some(&p.title), &p.html()?);
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
            insert_path(&mut pages, &p.url, Response::html(body))?;
Stephen D's avatar
Stephen D committed
        }

Stephen D's avatar
Stephen D committed
        let mut style = include_str!("assets/style.css").to_string();
        if let Some(s) = &b.custom_style {
            style.push_str(&s.content);
        }

Stephen D's avatar
Stephen D committed
        insert_path(&mut pages, "/style.css", Response::css(style))?;
Stephen D's avatar
Stephen D committed

        let home = b.home()?;
Stephen D's avatar
Stephen D committed
        insert_path(&mut pages, "/", Response::html(home))?;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        for img in &b.imgs {
            let (original_url, original, thumbnail_url, thumbnail) =
                img.clone().into_responses()?;
Stephen D's avatar
Stephen D committed
            insert_path(&mut pages, &thumbnail_url, thumbnail)?;
            insert_path(&mut pages, &original_url, original)?;
Stephen D's avatar
Stephen D committed
        }

Stephen D's avatar
Stephen D committed
        for m in &b.misc {
            insert_path(&mut pages, &m.url.clone(), m.clone().response())?;
Stephen D's avatar
Stephen D committed
        let not_found =
            Response::html(b.dress_page(Some("Page not found"), include_str!("assets/404.html")));
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        if let Some(fav) = b.favicon {
            for (p, r) in fav.responses {
                insert_path(&mut pages, p, r)?;
            }
        }

Stephen D's avatar
Stephen D committed
        dbg!(pages.keys());
Stephen D's avatar
Stephen D committed

        Ok(Self { not_found, pages })
    }
}

Stephen D's avatar
Stephen D committed
fn insert_path(
    pages: &mut FnvHashMap<UrlPath, Response>,
Stephen D's avatar
Stephen D committed
    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(())
}