use std::path::Path;
use lini::{Diagnostic, Level, Options};
pub fn render(source: &str, chapter: &str, first_line: usize, base_dir: Option<&Path>) -> String {
let padded = "\n".repeat(first_line - 1) + source;
let options = Options { base_dir: base_dir.map(Path::to_path_buf), ..Options::default() };
if let Some(fatal) = report(lini::lint_str(&padded).unwrap_or_default(), &padded, chapter) {
return error_box(&fatal);
}
match lini::compile_str_checked(&padded, &options) {
Ok((svg, routing)) => {
report(routing, &padded, chapter);
wrap(&svg)
}
Err(e) => {
let text = e.display_with_source(&padded, chapter).to_string();
eprintln!("mdbook-lini: {text}");
error_box(&text)
}
}
}
fn report(diags: Vec<Diagnostic>, source: &str, chapter: &str) -> Option<String> {
let mut fatal = None;
for d in diags {
let text = d.display_with_source(source, chapter).to_string();
eprintln!("mdbook-lini: {text}");
if d.level == Level::Error && fatal.is_none() {
fatal = Some(text);
}
}
fatal
}
fn wrap(svg: &str) -> String {
match natural_width(svg) {
Some(w) => format!("<div class=\"lini-figure\" style=\"--lini-w: {w}px\">{svg}</div>"),
None => format!("<div class=\"lini-figure\">{svg}</div>"),
}
}
fn natural_width(svg: &str) -> Option<&str> {
let tag = &svg[..svg.find('>')?];
let (_, after) = tag.split_once(" width=\"")?;
after.split_once('"').map(|(width, _)| width)
}
fn error_box(message: &str) -> String {
format!("<pre class=\"lini-error\">{}</pre>", escape(message))
}
fn escape(s: &str) -> String {
s.replace('&', "&").replace('<', "<").replace('>', ">")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wraps_a_diagram_with_its_natural_width() {
let html = render("a -> b", "demo.md", 1, None);
assert!(html.starts_with("<div class=\"lini-figure\" style=\"--lini-w: "), "{html}");
assert!(html.contains("<svg") && html.ends_with("</div>"));
}
#[test]
fn a_broken_block_becomes_an_error_box() {
let html = render("|box", "demo.md", 1, None);
assert!(html.starts_with("<pre class=\"lini-error\">"), "{html}");
}
#[test]
fn an_error_points_at_the_chapter_line_not_the_block_line() {
let html = render("a -> b\n|box", "demo.md", 41, None);
assert!(html.contains("demo.md:42:"), "{html}");
}
#[test]
fn reads_the_width_off_the_root_tag() {
assert_eq!(natural_width(r#"<svg viewBox="0 0 8 4" width="8" height="4">"#), Some("8"));
assert_eq!(natural_width("<svg>"), None);
}
}