use std::{cmp::Ordering, fs}; use anyhow::{bail, Context}; use orgize::{elements::Keyword, export::HtmlHandler, Element}; use time::{macros::format_description, Date}; use crate::{ date::parse_org_date, load::Loadable, parser::{parse, CustomHtmlHandler, Parser}, util::slugify, }; #[derive(Eq)] pub struct Post { pub title: String, pub date: Date, pub content: String, pub url: String, } impl Post { pub fn html(&self) -> anyhow::Result<String> { let title = &self.title; let content = &self.content; let date = &self.date_f()?; Ok(format!( r#"<div class="header-container"><h1>{title}</h1><div class="header-date">{date}</div></div>{content}"# )) } pub fn link(&self) -> anyhow::Result<String> { let link = format!( r#"<tr><td><a href="{}">{}</a></td><td>[{}]</td></tr>"#, self.url, self.title, self.date_f()? ); Ok(link) } fn date_f(&self) -> anyhow::Result<String> { let date_f = self .date .format(format_description!("[day] [month repr:short] [year]"))?; Ok(date_f) } } impl Loadable for Post { fn load(path: &std::path::Path, slug_prefix: &str) -> anyhow::Result<Self> { let file_name = path .file_stem() .context("Could not get file name")? .to_str() .context("Could not convert filename into string")? .to_owned(); let url = slugify(&format!("/{slug_prefix}/{file_name}")); parse(PostParser::new(url), &fs::read_to_string(path)?) } } impl Ord for Post { fn cmp(&self, other: &Self) -> Ordering { self.date.cmp(&other.date) } } impl PartialOrd for Post { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } } impl PartialEq for Post { fn eq(&self, other: &Self) -> bool { self.date == other.date } } struct PostParser { title: Option<String>, date: Option<Date>, url: String, content: Vec<u8>, handler: CustomHtmlHandler, } impl PostParser { fn new(url: String) -> Self { Self { title: None, date: None, url, content: vec![], handler: CustomHtmlHandler::default(), } } } impl Parser<Post> for PostParser { fn start(&mut self, e: &Element) -> anyhow::Result<()> { match e { Element::Keyword(Keyword { key, value, .. }) if key == "TITLE" => { if self.title.is_some() { bail!("Post has more than one title"); } self.title = Some(value.to_string()); } Element::Keyword(Keyword { key, value, .. }) if key == "DATE" => { if self.date.is_some() { bail!("Post has more than one date"); } self.date = Some(parse_org_date(value)?); } _ => { self.handler.start(&mut self.content, e)?; } } Ok(()) } fn end(&mut self, e: &Element) -> anyhow::Result<()> { self.handler.end(&mut self.content, e)?; Ok(()) } fn render(self) -> anyhow::Result<Post> { let title = self.title.context("Could not find post title")?; let date = self.date.context("Could not find post date")?; let content = String::from_utf8(self.content)?; let url = self.url; Ok(Post { title, date, content, url, }) } }