quatrain 0.6.0

Not intended to be a static site generator
Documentation
use super::escape;
use anyhow;
use katex;
use crate::{Alignment, CowStr, Event, LinkType, Tag};
use std::collections::HashMap;
use std::io::{self, Write};

enum TableState {
    Head,
    Body,
}

pub struct HTMLWriter<'e, I, W> {
    iter: I,
    writer: W,

    newline: bool,
    table_state: TableState,
    table_alignments: Vec<Alignment>,
    table_cell_index: usize,

    footnote_id: HashMap<CowStr<'e>, usize>,
    math: bool,
}

impl<'e, I, W> HTMLWriter<'e, I, W>
where
    I: Iterator<Item = Event<'e>>,
    W: Write,
{
    pub fn new(iter: I, writer: W) -> Self {
        Self {
            iter,
            writer,
            newline: false,
            table_state: TableState::Head,
            table_alignments: vec![],
            table_cell_index: 0,
            footnote_id: HashMap::new(),
            math: false,
        }
    }

    pub fn run(mut self) -> anyhow::Result<()> {
        let opts = katex::Opts::builder().display_mode(true).build().unwrap();
        while let Some(event) = self.iter.next() {
            match event {
                Event::Frontmatter(_) => (),
                Event::Start(tag) => self.start(tag)?,
                Event::End(tag) => self.end(tag)?,
                Event::Text(text) => {
                    if self.math {
                        self.write(&katex::render_with_opts(&text, opts.clone())?)?;
                    } else {
                        escape::escape_html(&mut self.writer, &text)?;
                        self.newline = text.ends_with('\n');
                    }
                }
                Event::Code(code) => {
                    self.write("<code>")?;
                    escape::escape_html(&mut self.writer, &code)?;
                    self.write("</code>")?;
                }
                Event::Html(raw) => self.write(&raw)?,
                Event::Math(raw) => self.write(&katex::render(&raw)?)?,
                Event::SoftBreak => self.write_newline()?,
                Event::HardBreak => self.write("<br />\n")?,
                Event::Rule => {
                    if self.newline {
                        self.write("<hr />\n")?
                    } else {
                        self.write("\n<hr />\n")?
                    }
                }
                Event::FootnoteReference(name) => {
                    let len = self.footnote_id.len() + 1;
                    self.write("<sup class=\"footnote-reference\"><a href=\"#")?;
                    escape::escape_html(&mut self.writer, &name)?;
                    self.write("\">")?;
                    let number = *self.footnote_id.entry(name).or_insert(len);
                    write!(&mut self.writer, "{}", number)?;
                    self.write("</a></sup>")?;
                }
                Event::TaskListMarker(true) => {
                    self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n")?;
                }
                Event::TaskListMarker(false) => {
                    self.write("<input disabled=\"\" type=\"checkbox\"/>\n")?;
                }
            }
        }
        Ok(())
    }

    #[inline]
    fn write(&mut self, s: &str) -> io::Result<()> {
        self.writer.write_all(s.as_bytes())?;

        if !s.is_empty() {
            self.newline = s.ends_with('\n');
        }
        Ok(())
    }

    fn write_newline(&mut self) -> io::Result<()> {
        self.newline = true;
        self.writer.write(b"\n")?;
        Ok(())
    }

    fn start(&mut self, tag: Tag<'e>) -> io::Result<()> {
        match tag {
            Tag::Emphasis => self.write("<em>"),
            Tag::Strong => self.write("<strong>"),
            Tag::Strikethrough => self.write("<del>"),
            Tag::List(Some(1)) => {
                if self.newline {
                    self.write("<ol>\n")
                } else {
                    self.write("\n<ol>\n")
                }
            }
            Tag::List(Some(start)) => {
                if self.newline {
                    self.write("<ol start=\"")?;
                } else {
                    self.write("\n<ol start=\"")?;
                }
                write!(&mut self.writer, "{}", start)?;
                self.write("\">\n")
            }
            Tag::List(None) => {
                if self.newline {
                    self.write("<ul>\n")
                } else {
                    self.write("\n<ul>\n")
                }
            }
            Tag::Item => {
                if self.newline {
                    self.write("<li>")
                } else {
                    self.write("\n<li>")
                }
            }
            Tag::Paragraph => {
                if self.newline {
                    self.write("<p>")
                } else {
                    self.write("\n<p>")
                }
            }
            Tag::DisplayMath => {
                self.math = true;
                self.write(r#"<div class="math">"#)
            }
            Tag::CodeBlock(_) => {
                if !self.newline {
                    self.write_newline()?;
                }
                self.write("<pre><code>")
            }
            Tag::Heading(no) => {
                if self.newline {
                    self.newline = false;
                    write!(&mut self.writer, "<h{}>", no)
                } else {
                    write!(&mut self.writer, "\n<h{}>", no)
                }
            }
            Tag::BlockQuote => {
                if self.newline {
                    self.write("<blockquote>\n")
                } else {
                    self.write("\n<blockquote>\n")
                }
            }
            Tag::Link(LinkType::Email, dest, title) => {
                self.write("<a href=\"mailto:")?;
                escape::escape_href(&mut self.writer, &dest)?;
                if !title.is_empty() {
                    self.write("\" title=\"")?;
                    escape::escape_html(&mut self.writer, &title)?;
                }
                self.write("\">")
            }
            Tag::Link(_link_type, dest, title) => {
                self.write("<a href=\"")?;
                escape::escape_href(&mut self.writer, &dest)?;
                if !title.is_empty() {
                    self.write("\" title=\"")?;
                    escape::escape_html(&mut self.writer, &title)?;
                }
                self.write("\">")
            }
            Tag::FootnoteDefinition(name) => {
                if self.newline {
                    self.write("<div class=\"footnote-definition\" id=\"")?;
                } else {
                    self.write("\n<div class=\"footnote-definition\" id=\"")?;
                }
                escape::escape_html(&mut self.writer, &*name)?;
                self.write("\"><sup class=\"footnote-definition-label\">")?;
                let len = self.footnote_id.len() + 1;
                let number = *self.footnote_id.entry(name).or_insert(len);
                write!(&mut self.writer, "{}", number)?;
                self.write("</sup>")
            }
            Tag::Image(_, url, title) => {
                self.write("<img src=\"")?;
                escape::escape_href(&mut self.writer, &url)?;
                self.write(r#"\" alt=\""#)?;
                self.raw_text()?;
                if !title.is_empty() {
                    self.write("\" title=\"")?;
                    escape::escape_html(&mut self.writer, &title)?;
                }
                self.write("\">")
            }
            Tag::Table(align) => {
                self.table_alignments = align;
                self.write("<table>")
            }
            Tag::TableHead => {
                self.table_state = TableState::Head;
                self.table_cell_index = 0;
                self.write("<thead><tr>")
            }
            Tag::TableRow => {
                self.table_cell_index = 0;
                self.write("<tr>")
            }
            Tag::TableCell => {
                match self.table_state {
                    TableState::Head => {
                        self.write("<th")?;
                    }
                    TableState::Body => {
                        self.write("<td")?;
                    }
                }
                match self.table_alignments.get(self.table_cell_index) {
                    Some(&Alignment::Left) => self.write(" align=\"left\">"),
                    Some(&Alignment::Center) => self.write(" align=\"center\">"),
                    Some(&Alignment::Right) => self.write(" align=\"right\">"),
                    _ => self.write(" align=\"center\">"),
                }
            }
        }
    }

    fn end(&mut self, tag: Tag<'e>) -> io::Result<()> {
        match tag {
            Tag::Emphasis => self.write("</em>"),
            Tag::Strong => self.write("</strong>"),
            Tag::Strikethrough => self.write("</del>"),
            Tag::List(Some(_)) => self.write("</ol>\n"),
            Tag::List(None) => self.write("</ul>"),
            Tag::Item => self.write("</li>\n"),
            Tag::Paragraph => self.write("</p>\n"),
            Tag::DisplayMath => {
                self.math = false;
                self.write("</div>\n")
            }
            Tag::CodeBlock(_) => self.write("</code></pre>\n"),
            Tag::Heading(no) => write!(&mut self.writer, "</h{}>\n", no),
            Tag::BlockQuote => self.write("</blockquote>\n"),
            Tag::Link(_, _, _) => self.write("</a>"),
            Tag::FootnoteDefinition(_) => self.write("</div>\n"),
            Tag::Image(_, _, _) => Ok(()),
            Tag::Table(_) => self.write("</tbody></table>\n"),
            Tag::TableHead => {
                self.table_state = TableState::Body;
                self.write("</tr></thead></tbody>\n")
            }
            Tag::TableRow => self.write("</tr>\n"),
            Tag::TableCell => {
                self.table_cell_index += 1;
                match self.table_state {
                    TableState::Head => self.write("</th>"),
                    TableState::Body => self.write("</td>"),
                }
            }
        }
    }

    fn raw_text(&mut self) -> io::Result<()> {
        let mut nest = 0;
        while let Some(event) = self.iter.next() {
            match event {
                Event::Start(_) => nest += 1,
                Event::End(_) => {
                    if nest == 0 {
                        break;
                    }
                    nest -= 1;
                }
                Event::Frontmatter(_) => (),
                Event::Html(text) | Event::Code(text) | Event::Text(text) | Event::Math(text) => {
                    escape::escape_html(&mut self.writer, &text)?;
                    self.newline = text.ends_with('\n');
                }
                Event::SoftBreak | Event::HardBreak | Event::Rule => {
                    self.write(" ")?;
                }
                Event::FootnoteReference(name) => {
                    let len = self.footnote_id.len() + 1;
                    let number = *self.footnote_id.entry(name).or_insert(len);
                    write!(&mut self.writer, "[{}]", number)?;
                }
                Event::TaskListMarker(true) => self.write("[x]")?,
                Event::TaskListMarker(false) => self.write("[ ]")?,
            }
        }
        Ok(())
    }
}