post.rs 4.72 KiB
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: Option<Date>,
pub index: u32,
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()?;
let mut out = format!(r#"<div class="header-container"><h1>{title}</h1>"#);
if let Some(date) = date {
out.push_str(r#"<div class="header-date">"#);
out.push_str(date);
out.push_str("</div>");
}
out.push_str("</div>");
out.push_str(content);
Ok(out)
}
pub fn link(&self) -> anyhow::Result<String> {
let link = match self.date_f()? {
Some(date_f) => format!(
r#"<tr><td><a href="{}">{}</a></td><td>[{}]</td></tr>"#,
self.url, self.title, date_f
),
None => format!(
r#"<tr><td><a href="{}">{}</a></td><td></td></tr>"#,
self.url, self.title
),
};
Ok(link)
}
pub fn link_short(&self) -> String {
format!(
r#"<a href="{}" class="page-link">{}</a>"#,
self.url, self.title
)
}
fn date_f(&self) -> anyhow::Result<Option<String>> {
let date_f = match self.date {
Some(date) => date.format(format_description!("[day] [month repr:short] [year]"))?,
None => return Ok(None),
};
Ok(Some(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 {
let mut o = self.index.cmp(&other.index);
if o.is_eq() {
o = self.date.cmp(&other.date).reverse();
}
o
}
}
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.index == other.index && self.date == other.date
}
}
struct PostParser {
title: Option<String>,
date: Option<Date>,
index: Option<u32>,
url: String,
content: Vec<u8>,
handler: CustomHtmlHandler,
}
impl PostParser {
fn new(url: String) -> anyhow::Result<Self> {
Ok(Self {
title: None,
date: None,
index: None,
url,
content: vec![],
handler: CustomHtmlHandler::new()?,
})
}
}
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)?);
}
Element::Keyword(Keyword { key, value, .. }) if key == "INDEX" => {
if self.index.is_some() {
bail!("Post has more than one index");
}
self.index = Some(value.parse()?);
}
_ => {
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;
let index = self.index.unwrap_or(u32::MAX);
let content = String::from_utf8(self.content)?;
let url = self.url;
Ok(Post {
title,
date,
index,
content,
url,
})
}
}