use crate::*; use markdown::*; use recipe::*; pub fn generate_html(document: &MarkdownDocument, page: &Page, website: &Website) -> String { let root = page.root(); let page_name = &page.name; let site_name = &website.name; let mut parent_url = String::new(); for segment in &page.parents { parent_url.push_str(&make_url_safe(segment)); parent_url.push('/'); } parent_url.pop(); let home_link = format!("{site_name}"); let parent_link = match page.parents.get(page.parents.len()-1) { Some(name) => format!("{name}"), None => String::new(), }; let head = get_html_head(document, page); let head = head.trim(); let table_of_contents = get_table_of_contents(page); let main = document_to_html(document, page, website); let main = main.trim(); format!("\ {page_name} — {site_name} {head}

{page_name}

{main}
") } pub fn get_html_head(document: &MarkdownDocument, page: &Page) -> String { if let Some(Block::Fragment { language, content }) = document.blocks.first() { if language == "embed-html-head" { return content.to_string(); } } let root = page.root(); format!("\ \ ") } pub fn get_table_of_contents(page: &Page) -> String { if page.headings.len() < 3 { return String::new(); } let mut toc = String::from("
\n"); return toc; } pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Website) -> String { let mut html = String::new(); macro_rules! line_to_html { ($l:expr) => {{ line_to_html(&$l, page, website) }}; } macro_rules! html { ($($arg:tt)*) => {{ html.push_str(&format!($($arg)*)); html.push('\n'); }}; } macro_rules! tag { ($t:expr,$l:expr,$c:expr) => { html!("<{} {}>{}", $t, $c, line_to_html!($l), $t) }; ($t:expr,$l:expr) => { html!("<{}>{}", $t, line_to_html!($l), $t) }; } macro_rules! wrap { ($t:expr,$c:expr,$f:expr) => {{ html!("<{} {}>", $t, $c); $f; html!("", $t); }}; ($t:expr,$f:expr) => {{ html!("<{}>", $t); $f; html!("", $t); }}; } let root = page.root(); for block in &document.blocks { match block { Block::Heading { level, line } => match level { Level::Heading1 => tag!("h1", line, format!("id='{}'", make_url_safe(&line_to_html!(line)))), Level::Heading2 => tag!("h2", line, format!("id='{}'", make_url_safe(&line_to_html!(line)))), Level::Heading3 => tag!("h3", line, format!("id='{}'", make_url_safe(&line_to_html!(line)))), } Block::Paragraph(line) => { if let Some(stripped) = line.to_string().strip_prefix("$$ ") { if let Some(stripped) = stripped.strip_suffix(" $$") { html!("
{stripped}
"); continue; } } tag!("p", line); } Block::List(lines) => wrap!("ul", for line in lines { // Insert a
tag directly after the first untagged colon. let mut depth = 0; let mut prev = '\0'; let mut output = String::new(); for c in line_to_html!(line).chars() { output.push(c); if c == '<' { depth += 1; } else if c == '/' && prev == '<' { depth -= 2; } else if c == ':' && depth == 0 { output.pop(); output.push_str("
"); depth += 99; } prev = c; } match output.contains("
") { true => html!("
  • {output}
  • "), false => html!("
  • {output}
  • "), } }), Block::Note(lines) => wrap!("aside", for line in lines { tag!("p", line) }), Block::Embedded { label, path } => match path.rsplit_once('.') { Some((_, extension)) => { let path = match path.strip_prefix('/') { Some(stripped) => format!("{root}{stripped}"), None => path.to_string(), }; match extension.to_lowercase().as_str() { "jpg"|"jpeg"|"png"|"webp"|"gif"|"tiff" => html!( "
    {label}
    "), "mp3"|"wav"|"m4a" => html!("