use std::{ io::{BufWriter, Write}, path::Path, }; use pulldown_cmark::{CowStr, Options, Parser}; pub fn compile_markdown( src: impl AsRef, html: impl AsRef, gmi: impl AsRef, ) -> std::io::Result<()> { let src_text = std::fs::read_to_string(src)?; let mut html = create_file(html)?; let mut gmi = create_file(gmi)?; let mut state = State::Start; let mut counter: u64 = 0; let mut links: Vec> = vec![]; for event in Parser::new_ext(&src_text, Options::all()) { use pulldown_cmark::Event::*; match event { Start(tag) => { use pulldown_cmark::{CodeBlockKind, HeadingLevel, Tag::*}; match tag { Paragraph => { html.write_all(b"

")?; if state != State::Start { gmi.write_all(b"\r\n\r\n")?; } } Heading { level, id, .. } => { if let Some(id) = id { html.write_fmt(format_args!("<{} id=\"{}\">", level, id))?; } else { html.write_fmt(format_args!("<{}>", level))?; } let hashes = match level { HeadingLevel::H1 => "# ", HeadingLevel::H2 => "## ", _ => "### ", }; gmi.write_all(hashes.as_bytes())?; state = State::GmiPrefix(hashes); } BlockQuote(_kind) => { html.write_all(b"

")?; gmi.write_all(b"> ")?; state = State::GmiPrefix("> "); } CodeBlock(CodeBlockKind::Fenced(lang)) => { html.write_all(b"
")?;
                        // TODO: highlighting with syntect
                        gmi.write_fmt(format_args!("```{}\r\n", lang))?;
                    }

                    CodeBlock(CodeBlockKind::Indented) => {
                        html.write_all(b"
")?;
                        gmi.write_all(b"```")?;
                    }

                    List(None) => {
                        html.write_all(b"
    ")?; } List(Some(counter_start)) => { counter = counter_start; if counter_start == 1 { html.write_all(b"
      ")?; } else { html.write_fmt(format_args!("
        ", counter_start))?; } } Item => { html.write_all(b"
      1. ")?; gmi.write_fmt(format_args!("{}. ", counter))?; counter += 1; } Strong => { html.write_all(b"")?; gmi.write_all(b"**")?; } Emphasis => { html.write_all(b"")?; gmi.write_all(b"*")?; } Strikethrough => { html.write_all(b"")?; gmi.write_all(b"~")?; } Link { dest_url, title, .. } => { html.write_fmt(format_args!("{}", dest_url, title))?; gmi.write_fmt(format_args!("{}[{}]", &title, links.len() + 1))?; links.push(GmiLink { title, url: dest_url, }); } Image { dest_url, title, .. } => { html.write_fmt(format_args!( "\"{}\"", dest_url, title ))?; gmi.write_fmt(format_args!("{}[{}]", &title, links.len() + 1))?; links.push(GmiLink { title, url: dest_url, }); } Table(_align) => { html.write_all(b"")?; // gmi ?? } TableHead => { html.write_all(b"")?; state = State::TableHead; } TableRow => { html.write_all(b"")?; } TableCell => { if state == State::TableHead { html.write_all(b"")?, TableRow => html.write_all(b"")?, TableCell => { if state == State::TableHead { html.write_all(b"")? } else { html.write_all(b"")? } } Table => html.write_all(b"
        ")?; } else { html.write_all(b"")?; } } other => { eprintln!("Unsupported tag: {:?}", other); } } } End(tag) => { use pulldown_cmark::TagEnd::*; match tag { Paragraph => { html.write_all(b"

        ")?; if !links.is_empty() { gmi.write_all(b"\r\n\r\n")?; for (i, link) in links.iter().enumerate() { gmi.write_fmt(format_args!( "=> {} [{}]: {}", link.url, i, link.title, ))?; } links.clear(); } } Heading(level) => html.write_fmt(format_args!("<{}>", level))?, BlockQuote(_) => html.write_all(b"")?, CodeBlock => { html.write_all(b"")?; gmi.write_all(b"```")?; } List(ordered) => { if ordered { html.write_all(b"")?; } else { html.write_all(b"")?; } } Item => html.write_all(b"")?, Strong => { html.write_all(b"")?; gmi.write_all(b"**")?; } Emphasis => { html.write_all(b"")?; gmi.write_all(b"*")?; } Strikethrough => { html.write_all(b"")?; gmi.write_all(b"~")?; } Link => {} TableHead => html.write_all(b"
        ")?, _ => {} } state = State::Paragraph; } Text(text) => { html.write_all(text.as_bytes())?; gmi.write_all(text.as_bytes())?; } Code(code) => { html.write_all(b"")?; html.write_all(code.as_bytes())?; html.write_all(b"")?; gmi.write_all(b"`")?; gmi.write_all(code.as_bytes())?; gmi.write_all(b"`")?; } SoftBreak => {} HardBreak => { html.write_all(b"
        ")?; gmi.write_all(b"\r\n")?; if let State::GmiPrefix(prefix) = state { gmi.write_all(prefix.as_bytes())?; } } Rule => { html.write_all(b"
        ")?; gmi.write_all(b"---")?; } TaskListMarker(state) => { if state { html.write_all(b"")?; gmi.write_all(b"[x]")?; } else { html.write_all(b"")?; gmi.write_all(b"[ ]")?; } } other => { eprintln!("Unsupported event: {:?}", other); } } } html.flush()?; gmi.flush()?; Ok(()) } #[inline(always)] fn create_file(path: impl AsRef) -> std::io::Result> { let f = std::fs::OpenOptions::new() .create(true) .write(true) .open(path)?; Ok(BufWriter::new(f)) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum State { Start, Paragraph, GmiPrefix(&'static str), TableHead, } struct GmiLink<'link> { title: CowStr<'link>, url: CowStr<'link>, }