use std::io::{self, Read}; use std::ops::{Range, RangeBounds}; use pulldown_cmark::{Event, Options, Parser, Tag}; #[derive(Default)] struct Span { first_line: usize, first_column: usize, last_line: usize, last_column: usize, } fn location_to_span(input: &str, loc: &Range) -> Span { let mut ret = Span::default(); let mut line = 1; let mut col = 1; for (pos, ch) in input.chars().enumerate() { if ch == '\n' { line += 1; col = 1; } else { col += 1; } if pos == loc.start { ret.first_line = line; ret.first_column = col; } if pos == loc.end { ret.last_line = line; ret.last_column = col; } } ret } fn print_thing(input: &str, loc: &Range) { let content = &input[loc.clone()]; let span = location_to_span(input, loc); println!( "> From line {} column {} to line {} column {}", span.first_line, span.first_column, span.last_line, span.last_column ); for l in content.lines() { println!("> `{}`", l); } if span.first_line == span.last_line { print!("> `"); for _ in 0..span.first_column { print!(" "); } for _ in span.first_column..=span.last_column { print!("^"); } println!("`"); } } fn main() -> io::Result<()> { let mut input = String::new(); let inputlen = io::stdin().read_to_string(&mut input)?; eprintln!("Read {} bytes of input", inputlen); let options = Options::all(); let parser = Parser::new_ext(&input, options); let mut depth = 0; let mut in_scenario = false; let mut in_scenario_block = false; let mut in_header = false; let mut last_header_depth = 1000; let mut last_header_content: Option = None; for (event, span) in parser.into_offset_iter() { eprintln!("{:?}", event); match event { Event::Start(tag) => { depth += 1; eprintln!("Depth now: {}", depth); if depth == 1 { match tag { Tag::Header(level) => { // Two ways to handle a header, // If we're in a scenario, we care only if this header // is at the same or a higher level, otherwise we // always read the header in. if in_scenario { if level <= last_header_depth { println!("```"); in_scenario = false; in_header = true; last_header_depth = level; } } else { in_header = true; last_header_depth = level; } } Tag::CodeBlock(name) => { if name.as_ref() == "fable" { // Fable code block, only valid if we were // after a header if last_header_content.is_some() { in_scenario_block = true; } } } _ => {} } } } Event::End(tag) => { depth -= 1; eprintln!("Depth now: {}", depth); if depth == 0 { match tag { Tag::Header(_) => { // We're ending a header now in_header = false; } Tag::CodeBlock(_) => { in_scenario_block = false; } _ => {} } } } Event::Text(s) => { if in_header { // We're parsing a header last_header_content = Some(s.to_string()); in_header = false; if cfg!(feature = "print_headers") { print_thing(&input, &span); } } else if in_scenario_block { if !in_scenario { for _ in 0..last_header_depth { print!("#"); } println!(" {}", last_header_content.as_ref().unwrap()); in_scenario = true; println!("```fable"); } print!("{}", s); } } _ => {} } } Ok(()) }