diff --git a/src/blog.rs b/src/blog.rs index e2ab4ec4fa1c51cbff30c76521593a3aabc8fd1d..78ca55ad04a79f06c9b2884b2e27c5d6caea7f28 100644 --- a/src/blog.rs +++ b/src/blog.rs @@ -1,17 +1,16 @@ -use std::path::Path; +use std::path::PathBuf; 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, + config::Config, 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, + config: Config, pages: Vec<Post>, posts: Vec<Post>, @@ -19,39 +18,33 @@ pub struct Blog { 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"), "")?; + pub fn new(config: Config) -> Result<Self, Error> { + let base_path = PathBuf::from(&config.path); + + 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, + config, pages, posts, imgs, misc, custom_style, favicon, - logo, }) } - fn home(&mut self) -> Result<String, Error> { - let name = &self.name; - let description = &self.description; + fn home(&self) -> anyhow::Result<String> { + let name = &self.config.name; + let description = &self.config.description; let mut content = format!( r#"<div class="centered"><h1>{name}</h1>{description}</div><h2>Recent Posts</h2><table>"# @@ -66,10 +59,32 @@ impl Blog { Ok(self.dress_page(None, &content)) } + fn rss(&self) -> anyhow::Result<String> { + let mut out = format!( + concat!( + r#"<?xml version="1.0" encoding="UTF-8" ?>"#, + r#"<rss version="2.0">"#, + "<channel>", + "<title>{}</title>", + "<link>{}</link>", + "<description>{}</description>" + ), + self.config.name, self.config.root, self.config.description + ); + + for p in &self.posts { + out.push_str(&p.rss(&self.config.root)?); + } + + out.push_str("</channel></rss>"); + + Ok(out) + } + 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(), + Some(title) => format!("{} | {}", title.trim(), self.config.name.trim()), + None => self.config.name.trim().to_string(), }; let favicons = match &self.favicon { @@ -77,7 +92,7 @@ impl Blog { None => "", }; - let logo = match &self.logo { + let logo = match &self.config.logo { Some(logo) => format!( r#"<img src="{}" width="{}" height="{}" alt="{}" class="align-right" />"#, logo.path, logo.width, logo.height, logo.alt @@ -152,6 +167,8 @@ impl TryFrom<Blog> for RenderedBlog { insert_path(&mut pages, &m.url.clone(), m.clone().response())?; } + insert_path(&mut pages, "/posts.rss", Response::rss(b.rss()?))?; + let not_found = Response::html(b.dress_page(Some("Page not found"), include_str!("assets/404.html"))); diff --git a/src/config.rs b/src/config.rs index 03a9fb6e5a04d363e375d6986840353580d55d84..6b273f010e6ff1b817f83a1f3869767d26bed2fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,7 @@ pub struct Config { pub name: String, pub description: String, pub path: String, + pub root: String, pub logo: Option<Logo>, } diff --git a/src/main.rs b/src/main.rs index e99c73a3f8dc56c3d06be489fa27a9c5b3d0b4b0..e7ac1fc7d927e7da99396baedd1b4865e2d45e4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use config::Config; -use std::{path::PathBuf, sync::Arc}; +use std::sync::Arc; use tokio::sync::RwLock; use blog::{Blog, RenderedBlog}; @@ -53,15 +53,7 @@ async fn handle_request( async fn main() { let config = Config::load().unwrap(); - let blog: RenderedBlog = Blog::new( - config.name, - config.description, - &PathBuf::from(config.path), - config.logo, - ) - .unwrap() - .try_into() - .unwrap(); + let blog: RenderedBlog = Blog::new(config).unwrap().try_into().unwrap(); let blog = Arc::new(RwLock::new(blog)); let blog_filter = warp::any().map(move || blog.clone()); diff --git a/src/path.rs b/src/path.rs index 6b56e821b512c3b0b7dee3141950d38daa4a69f7..497d8fa7c8925c8485fd74def7c5ba1bc6680a28 100644 --- a/src/path.rs +++ b/src/path.rs @@ -15,6 +15,16 @@ impl UrlPath { Self { parts } } + + pub fn new_from_raw(s: String) -> Self { + Self { parts: vec![s] } + } + + pub fn join(mut self, append: &str) -> Self { + self.parts.extend(UrlPath::new(append).parts); + + self + } } impl Display for UrlPath { diff --git a/src/post.rs b/src/post.rs index 780012116686736b9518a15bd6395fd20aef5418..9c8d9c259e1f2b77e3f62aff3d8e34534815fa92 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,13 +1,14 @@ use std::{cmp::Ordering, fs, path::Path}; use anyhow::{bail, Context}; -use chrono::NaiveDate; +use chrono::{DateTime, NaiveDate, Utc}; use orgize::{elements::Keyword, export::HtmlHandler, Element}; use crate::{ date::parse_org_date, load::Loadable, parser::{parse, CustomHtmlHandler, Parser}, + path::UrlPath, util::slugify, }; @@ -40,6 +41,29 @@ impl Post { Ok(out) } + pub fn rss(&self, root_url: &str) -> anyhow::Result<String> { + let title = &self.title; + let link = UrlPath::new_from_raw(root_url.to_string()).join(&self.url); + let date = match self.date { + Some(d) => { + format!( + "<pubDate>{}</pubDate>", + DateTime::<Utc>::from_utc( + d.and_hms_opt(0, 0, 0) + .context("Could not convert date to datetime")?, + Utc + ) + .to_rfc2822() + ) + } + None => "".to_string(), + }; + + Ok(format!( + "<item><title>{title}</title><link>{link}</link><guid>{link}</guid>{date}</item>" + )) + } + pub fn link(&self) -> anyhow::Result<String> { let link = match self.date_f()? { Some(date_f) => format!( diff --git a/src/resp.rs b/src/resp.rs index b3f45a348e7a1bbe968ced229ad009d2edcb19e0..e6c3aea739d7ac7373e6b6883427d1022fcf4407 100644 --- a/src/resp.rs +++ b/src/resp.rs @@ -33,6 +33,14 @@ impl Response { } } + pub fn rss(rss: String) -> Self { + Self { + content_type: "text/xml; charset=utf-8", + expiry: Expiry::Instant, + data: rss.into_bytes(), + } + } + pub fn css(css: String) -> Self { Self { content_type: "text/css; charset=utf-8",