use std::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, }; pub struct Post { pub title: String, pub date_f: String, 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) -> String { format!( r#"<a href="{}">{}</a> [{}]"#, self.url, self.title, self.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)?) } } 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 date_f = date.format(format_description!("[day] [month repr:short] [year]"))?; let content = String::from_utf8(self.content)?; let url = self.url; Ok(Post { title, date_f, content, url, }) } }