use crate::markdown::{Block, DiagramKind};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct DiagramNeeds {
pub mermaid: bool,
pub math: bool,
}
impl DiagramNeeds {
pub fn any(self) -> bool {
self.mermaid || self.math
}
pub fn merge(&mut self, other: DiagramNeeds) {
self.mermaid |= other.mermaid;
self.math |= other.math;
}
}
pub fn collect_needs(blocks: &[Block]) -> DiagramNeeds {
let mut needs = DiagramNeeds::default();
collect_needs_inner(blocks, &mut needs);
needs
}
fn collect_needs_inner(blocks: &[Block], out: &mut DiagramNeeds) {
for block in blocks {
match block {
Block::Diagram { kind, .. } => match kind {
DiagramKind::Mermaid => out.mermaid = true,
DiagramKind::Math => out.math = true,
},
Block::Blockquote(inner) => collect_needs_inner(inner, out),
Block::Center(inner) => collect_needs_inner(inner, out),
Block::Div { children, .. } => collect_needs_inner(children, out),
_ => {}
}
}
}
pub const VENDOR_ASSETS: &[(&str, &[u8])] = &[
("mermaid.min.js", include_bytes!("vendor/mermaid.min.js")),
("katex.min.js", include_bytes!("vendor/katex.min.js")),
("katex.min.css", include_bytes!("vendor/katex.min.css")),
(
"fonts/KaTeX_AMS-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_AMS-Regular.woff2"),
),
(
"fonts/KaTeX_Caligraphic-Bold.woff2",
include_bytes!("vendor/fonts/KaTeX_Caligraphic-Bold.woff2"),
),
(
"fonts/KaTeX_Caligraphic-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Caligraphic-Regular.woff2"),
),
(
"fonts/KaTeX_Fraktur-Bold.woff2",
include_bytes!("vendor/fonts/KaTeX_Fraktur-Bold.woff2"),
),
(
"fonts/KaTeX_Fraktur-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Fraktur-Regular.woff2"),
),
(
"fonts/KaTeX_Main-Bold.woff2",
include_bytes!("vendor/fonts/KaTeX_Main-Bold.woff2"),
),
(
"fonts/KaTeX_Main-BoldItalic.woff2",
include_bytes!("vendor/fonts/KaTeX_Main-BoldItalic.woff2"),
),
(
"fonts/KaTeX_Main-Italic.woff2",
include_bytes!("vendor/fonts/KaTeX_Main-Italic.woff2"),
),
(
"fonts/KaTeX_Main-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Main-Regular.woff2"),
),
(
"fonts/KaTeX_Math-BoldItalic.woff2",
include_bytes!("vendor/fonts/KaTeX_Math-BoldItalic.woff2"),
),
(
"fonts/KaTeX_Math-Italic.woff2",
include_bytes!("vendor/fonts/KaTeX_Math-Italic.woff2"),
),
(
"fonts/KaTeX_SansSerif-Bold.woff2",
include_bytes!("vendor/fonts/KaTeX_SansSerif-Bold.woff2"),
),
(
"fonts/KaTeX_SansSerif-Italic.woff2",
include_bytes!("vendor/fonts/KaTeX_SansSerif-Italic.woff2"),
),
(
"fonts/KaTeX_SansSerif-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_SansSerif-Regular.woff2"),
),
(
"fonts/KaTeX_Script-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Script-Regular.woff2"),
),
(
"fonts/KaTeX_Size1-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Size1-Regular.woff2"),
),
(
"fonts/KaTeX_Size2-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Size2-Regular.woff2"),
),
(
"fonts/KaTeX_Size3-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Size3-Regular.woff2"),
),
(
"fonts/KaTeX_Size4-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Size4-Regular.woff2"),
),
(
"fonts/KaTeX_Typewriter-Regular.woff2",
include_bytes!("vendor/fonts/KaTeX_Typewriter-Regular.woff2"),
),
];
pub fn write_vendor_assets(out: &std::path::Path) -> std::io::Result<()> {
let dir = out.join("assets").join("vendor");
for (name, bytes) in VENDOR_ASSETS {
let path = dir.join(name);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, bytes)?;
}
Ok(())
}
pub fn vendor_tags(needs: DiagramNeeds) -> String {
let mut tags = String::new();
if needs.math {
tags.push_str(
"<link rel=\"stylesheet\" href=\"/assets/vendor/katex.min.css\">\n\
<script src=\"/assets/vendor/katex.min.js\"></script>\n",
);
}
if needs.mermaid {
tags.push_str("<script src=\"/assets/vendor/mermaid.min.js\"></script>\n");
}
if needs.any() {
let diagram_js = include_str!("diagram.js");
tags.push_str(&format!("<script>{diagram_js}</script>\n"));
}
tags
}
#[cfg(test)]
mod tests {
use super::*;
use crate::markdown::parse;
#[test]
fn mermaid_fence_is_a_diagram_not_a_code_block() {
let blocks = parse("```mermaid\ngraph TD;\n A-->B;\n```\n");
assert_eq!(
blocks,
vec![Block::Diagram {
kind: DiagramKind::Mermaid,
source: "graph TD;\n A-->B;".to_string(),
}]
);
let needs = collect_needs(&blocks);
assert!(needs.mermaid && !needs.math);
}
#[test]
fn math_aliases_all_route_to_katex() {
for info in ["math", "latex", "katex"] {
let src = format!("```{info}\nE = mc^2\n```\n");
let blocks = parse(&src);
assert!(
matches!(
&blocks[0],
Block::Diagram {
kind: DiagramKind::Math,
source,
} if source == "E = mc^2"
),
"fence '{info}' did not produce a math diagram: {blocks:?}"
);
}
}
#[test]
fn nested_diagrams_are_found_and_flags_merge() {
let blocks = parse("> ```math\n> x^2\n>\n\n<div class=\"note\">\n\n```mermaid\ngraph LR;A-->B\n```\n\n</div>\n");
let needs = collect_needs(&blocks);
assert!(needs.mermaid && needs.math, "needs: {needs:?}");
let mut page = DiagramNeeds::default();
page.merge(needs);
page.merge(DiagramNeeds::default());
assert!(page.any());
}
#[test]
fn vendor_tags_only_pull_what_the_page_uses() {
let mermaid_only = vendor_tags(DiagramNeeds {
mermaid: true,
math: false,
});
assert!(mermaid_only.contains("mermaid.min.js"));
assert!(!mermaid_only.contains("katex.min."));
assert!(mermaid_only.contains("lgDiagram"));
let none = vendor_tags(DiagramNeeds::default());
assert!(none.is_empty());
}
}