use crate::ast::traits;
use crate::ast::traits::{BlockCast, InlineCast};
use html_escape::{encode_quoted_attribute_to_writer, encode_text_to_writer};
use std::io;
use std::io::Write;
pub fn emit_html<W: Write, N: traits::Root>(writer: &mut W, mkt: &N) -> io::Result<()> {
block_container(writer, mkt)
}
fn block_container<W: Write, N: traits::BlockContainer>(writer: &mut W, node: &N) -> io::Result<()> {
for n in node.children() {
block(writer, &*n)?;
}
Ok(())
}
fn block<W: Write, N: traits::Block>(writer: &mut W, node: &N) -> io::Result<()> {
match node.cast() {
BlockCast::Admin(n) => admin(writer, &*n),
BlockCast::Mod(n) => r#mod(writer, &*n),
BlockCast::Emphasis(n) => emphasis(writer, &*n),
BlockCast::Icon(n) => icon(writer, &*n),
BlockCast::Link(n) => link(writer, &*n),
BlockCast::Newline(n) => newline(writer, &*n),
BlockCast::Strikethrough(n) => strikethrough(writer, &*n),
BlockCast::Strong(n) => strong(writer, &*n),
BlockCast::Text(n) => text(writer, &*n),
}
}
fn admin<W: Write, N: traits::Admin>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<div class=\"mkt-admin\">")?;
block_container(writer, node)?;
write!(writer, "</div>")?;
Ok(())
}
fn r#mod<W: Write, N: traits::Mod>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<div class=\"mod\">")?;
block_container(writer, node)?;
write!(writer, "</div>")?;
Ok(())
}
fn inline_container<W: Write, N: traits::InlineContainer>(writer: &mut W, node: &N) -> io::Result<()> {
for n in node.children() {
inline(writer, &*n)?;
}
Ok(())
}
fn inline<W: Write, N: traits::Inline>(writer: &mut W, node: &N) -> io::Result<()> {
match node.cast() {
InlineCast::Emphasis(n) => emphasis(writer, &*n),
InlineCast::Icon(n) => icon(writer, &*n),
InlineCast::Link(n) => link(writer, &*n),
InlineCast::Newline(n) => newline(writer, &*n),
InlineCast::Strikethrough(n) => strikethrough(writer, &*n),
InlineCast::Strong(n) => strong(writer, &*n),
InlineCast::Text(n) => text(writer, &*n),
}
}
fn emphasis<W: Write, N: traits::Emphasis>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<em>")?;
inline_container(writer, node)?;
write!(writer, "</em>")?;
Ok(())
}
fn icon<W: Write, N: traits::Icon>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<span class=\"mkt-icon mkt-icon-")?;
escape_html_attribute(writer, &*node.key())?;
write!(writer, "\"></span>")?;
Ok(())
}
fn link<W: Write, N: traits::Link>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<a href=\"")?;
escape_html_attribute(writer, &*node.uri())?;
write!(writer, "\">")?;
inline_container(writer, node)?;
write!(writer, "</a>")?;
Ok(())
}
fn newline<W: Write, N: traits::Newline>(writer: &mut W, _node: &N) -> io::Result<()> {
#[allow(clippy::write_with_newline)]
write!(writer, "<br />\n")?;
Ok(())
}
fn strikethrough<W: Write, N: traits::Strikethrough>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<span class=\"strikethrough\">")?;
inline_container(writer, node)?;
write!(writer, "</span>")?;
Ok(())
}
fn strong<W: Write, N: traits::Strong>(writer: &mut W, node: &N) -> io::Result<()> {
write!(writer, "<strong>")?;
inline_container(writer, node)?;
write!(writer, "</strong>")?;
Ok(())
}
fn text<W: Write, N: traits::Text>(writer: &mut W, node: &N) -> io::Result<()> {
escape_html_text(writer, &*node.text())
}
fn escape_html_text<W: Write>(writer: &mut W, s: &str) -> io::Result<()> {
encode_text_to_writer(s, writer)
}
fn escape_html_attribute<W: Write>(writer: &mut W, s: &str) -> io::Result<()> {
encode_quoted_attribute_to_writer(s, writer)
}
#[cfg(test)]
mod parser_tests {
use std::fs;
use std::path::{Path, PathBuf};
use crate::emitter::emit_html;
use ::test_generator::test_resources;
#[test_resources("./test-resources/[!.]*/")]
fn test_parse_mkt(path: &str) {
let path: PathBuf = Path::join(Path::new(".."), path);
let _name = path
.components()
.last()
.unwrap()
.as_os_str()
.to_str()
.expect("Failed to retrieve sample name");
let ast_json = fs::read_to_string(path.join("main.ast.json")).expect("Failed to read AST");
let ast: crate::ast::owned::Root = serde_json::from_str(&ast_json).expect("Invalid AST");
let mut actual_html_bytes: Vec<u8> = Vec::new();
emit_html(&mut actual_html_bytes, &ast).expect("FailedToEmit");
fs::write(path.join("local-main.rs.html"), &actual_html_bytes).unwrap();
let actual_html = std::str::from_utf8(&actual_html_bytes).unwrap();
let expected_html: String = fs::read_to_string(path.join("main.html")).expect("Failed to read HTML");
assert_eq!(actual_html, expected_html);
}
}