Skip to content
Snippets Groups Projects
parser.rs 3.83 KiB
Newer Older
Stephen D's avatar
Stephen D committed
use anyhow::bail;
use anyhow::Context;
use anyhow::Error;
use chrono::NaiveDate;
use orgize::elements::Keyword;
Stephen D's avatar
Stephen D committed
use orgize::elements::Link;
Stephen D's avatar
Stephen D committed
use orgize::elements::Timestamp;
use orgize::{
    export::{DefaultHtmlHandler, HtmlHandler},
    Element, Org,
};
use std::io::Write;
Stephen D's avatar
Stephen D committed
use std::path::Path;
Stephen D's avatar
Stephen D committed
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,
}

Stephen D's avatar
Stephen D committed
impl CustomHtmlHandler {
Stephen D's avatar
Stephen D committed
    pub fn new(base_path: &Path) -> anyhow::Result<Self> {
Stephen D's avatar
Stephen D committed
        let mut ts = ThemeSet::load_defaults();
Stephen D's avatar
Stephen D committed
        let mut ps = SyntaxSet::load_defaults_newlines().into_builder();
Stephen D's avatar
Stephen D committed
        ps.add_from_folder(base_path.join("assets/syntaxes"), true)?;
Stephen D's avatar
Stephen D committed
        let ps = ps.build();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        Ok(Self {
Stephen D's avatar
Stephen D committed
            fallback: Default::default(),
Stephen D's avatar
Stephen D committed
            ps,
Stephen D's avatar
Stephen D committed
            theme: ts
                .themes
                .remove("base16-eighties.dark")
                .expect("Could not load theme"),
Stephen D's avatar
Stephen D committed
        })
Stephen D's avatar
Stephen D committed
    }
}

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(
Stephen D's avatar
Stephen D committed
                    start.year.into(),
                    start.month.into(),
                    start.day.into(),
                )
                .context("Invalid date")?;
Stephen D's avatar
Stephen D committed

                write!(w, "{}", date.format("%d %b %Y"))?;
Stephen D's avatar
Stephen D committed
            }
Stephen D's avatar
Stephen D committed
            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>"#
                )?;
            }
Stephen D's avatar
Stephen D committed
            // 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),
            },
Stephen D's avatar
Stephen D committed
            Element::Link(Link { desc, .. }) if desc.is_none() => {
                write!(w, r#"</img></a>"#)?;
            }
Stephen D's avatar
Stephen D committed
            // fallback to default handler
            _ => self.fallback.end(w, element)?,
        }

        Ok(())
    }
}