#[derive(Debug, Clone)] pub enum Block { Header(usize, Vec), Paragraph(Vec), Blockquote(Vec), CodeBlock(Option, String), LatexBlock(String), OrderedList(Vec>), UnorderedList(Vec>), Raw(String), Hr, } #[derive(Debug, Clone)] pub enum Span { Break, Text(String), Code(String), Link(String, String), Image(String, String), Emphasis(Vec), Strong(Vec), Latex(String), } pub fn parse(mut s: &str) -> Vec { let mut blocks = Vec::new(); while s.len() != 0 { if s.starts_with("\n") { s = &s[1..]; continue; } // TODO bad code here if let Some((block, rest)) = try_header(s) { s = rest; blocks.push(block); continue; } if let Some((block, rest)) = try_latex_block(s) { s = rest; blocks.push(block); continue; } if let Some((block, rest)) = try_code_block(s) { s = rest; blocks.push(block); continue; } if let Some((block, rest)) = try_list(s) { s = rest; blocks.push(block); continue; } let lf = [s.find("\n\n"), s.find("\n-")] .iter() .filter_map(|e| *e) .min() .unwrap_or(s.len()); let span = Span::parse(&s[..lf]); blocks.push(Block::Paragraph(span)); if lf >= s.len() { break; } s = &s[lf + 1..]; } blocks } fn try_code_block(mut s: &str) -> Option<(Block, &str)> { if !s.starts_with("```") { return None; } s = &s[3..]; let lf = s.find('\n')?; let syntax = if lf != 0 { Some(String::from(&s[0..lf])) } else { None }; s = &s[lf..]; let end = s.find("\n```\n")?; Some(( Block::CodeBlock(syntax, String::from(&s[..end])), &s[end + 4..], )) } fn try_latex_block(mut s: &str) -> Option<(Block, &str)> { if !s.starts_with("$$") { return None; } s = &s[2..]; let end = s.find("$$")?; Some((Block::LatexBlock(String::from(&s[..end])), &s[end + 2..])) } fn try_list(mut s: &str) -> Option<(Block, &str)> { if !s.starts_with("-") { return None; }; let mut blocks = vec![]; loop { if !s.starts_with("-") || s.len() == 0 { break Some((Block::UnorderedList(blocks), s)); } s = &s[1..]; let mut lf = s.find("\n").unwrap(); while s[lf + 1..].starts_with(" ") { lf += 2 + s[lf + 2..].find("\n").unwrap(); } let mut k = s[..lf] .split("\n") .map(|l| if l.starts_with(" ") { &l[2..] } else { &l }) .collect::>() .join("\n"); k.push('\n'); blocks.push(parse(&k)); s = &s[lf + 1..]; } } fn try_header(s: &str) -> Option<(Block, &str)> { if s.starts_with("#") { let mut u = 0; while s.chars().nth(u)? == '#' { u += 1; } let lf = s.find('\n')?; Some((Block::Header(u, Span::parse(&s[u..lf])), &s[lf + 1..])) } else { None } } impl Span { pub fn parse(mut s: &str) -> Vec { let mut spans = Vec::new(); while s.len() != 0 { let nt = s.find(&['*', '_', '`', '[', '$']); if let Some(nt) = nt { spans.push(Span::Text(String::from(&s[..nt]))); s = &s[nt..]; if s.starts_with("**") { s = &s[2..]; let end = s.find("**").expect("** not ended"); spans.push(Span::Strong(Span::parse(&s[..end]))); s = &s[end + 2..]; continue; } if s.starts_with("_") { s = &s[1..]; let end = s.find("_").expect("_ not ended"); spans.push(Span::Emphasis(Span::parse(&s[..end]))); s = &s[end + 1..]; continue; } if s.starts_with("`") { s = &s[1..]; let end = s.find("`").expect("` not ended"); spans.push(Span::Code(String::from(&s[..end]))); s = &s[end + 1..]; continue; } if s.starts_with("$") { s = &s[1..]; let end = s.find("$").expect("$ not ended"); spans.push(Span::Latex(String::from(&s[..end]))); s = &s[end + 1..]; continue; } if s.starts_with("[") { s = &s[1..]; let del = s.find("](").expect("]( expected"); let end = del + s[del..].find(")").expect(") expected"); spans.push(Span::Link( String::from(&s[..del]), String::from(&s[del + 2..end]), )); s = &s[end + 1..]; continue; } panic!("{s:?}") } else { spans.push(Span::Text(String::from(s))); break; } } spans } }