use anyhow::bail; use anyhow::Context; use anyhow::Error; use chrono::NaiveDate; use orgize::elements::Keyword; use orgize::elements::Link; use orgize::elements::Timestamp; use orgize::{ export::{DefaultHtmlHandler, HtmlHandler}, Element, Org, }; use std::io::Write; use std::path::Path; use syntect::{ highlighting::{Theme, ThemeSet}, html::highlighted_html_for_string, parsing::SyntaxSet, }; pub trait Parser<T> { fn start(&mut self, _: &Element) -> anyhow::Result<()>; fn end(&mut self, _: &Element) -> anyhow::Result<()>; fn render(self) -> anyhow::Result<T>; } pub fn parse<T, P: Parser<T>>(mut p: P, org: &str) -> anyhow::Result<T> { for e in Org::parse(org).iter() { match e { orgize::Event::Start(ele) => p.start(ele)?, orgize::Event::End(ele) => p.end(ele)?, } } p.render() } pub struct CustomHtmlHandler { fallback: DefaultHtmlHandler, ps: SyntaxSet, theme: Theme, } impl CustomHtmlHandler { pub fn new(base_path: &Path) -> anyhow::Result<Self> { let mut ts = ThemeSet::load_defaults(); let mut ps = SyntaxSet::load_defaults_newlines().into_builder(); ps.add_from_folder(base_path.join("assets/syntaxes"), true)?; let ps = ps.build(); Ok(Self { fallback: Default::default(), ps, theme: ts .themes .remove("base16-eighties.dark") .expect("Could not load theme"), }) } } impl HtmlHandler<Error> for CustomHtmlHandler { fn start<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), Error> { match element { Element::SourceBlock(block) => { let syntax = self .ps .find_syntax_by_token(&block.language) .with_context(|| format!("Unknown language {}", block.language))?; let html = highlighted_html_for_string(&block.contents, &self.ps, syntax, &self.theme)?; write!(w, "{html}")?; } Element::SpecialBlock(block) => match block.name.as_ref() { "JUSTIFYRIGHT" => { write!(w, r#"<div class="justify-right">"#)?; } _ => bail!("Unrecognized special block name {}", block.name), }, Element::Timestamp(Timestamp::Active { start, .. }) => { let date = NaiveDate::from_ymd_opt( start.year.into(), start.month.into(), start.day.into(), ) .context("Invalid date")?; write!(w, "{}", date.format("%d %b %Y"))?; } Element::Link(Link { path, desc }) if desc.is_none() => { write!(w, r#"<a href="/img/{path}"><img src="/thumb/{path}">"#)?; } Element::Keyword(Keyword { key, value, .. }) if key == "VIDEO" => { write!( w, r#"<video controls="controls"><source src="{value}"></video>"# )?; } // fallback to default handler _ => self.fallback.start(w, element)?, } Ok(()) } fn end<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), Error> { match element { Element::SpecialBlock(block) => match block.name.as_ref() { "JUSTIFYRIGHT" => { write!(w, "</div>")?; } _ => bail!("Unrecognized special block name {}", block.name), }, Element::Link(Link { desc, .. }) if desc.is_none() => { write!(w, r#"</img></a>"#)?; } // fallback to default handler _ => self.fallback.end(w, element)?, } Ok(()) } }