summaryrefslogtreecommitdiff
path: root/fable-cat-poc/src
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2019-06-15 19:15:48 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2019-06-15 19:15:52 +0100
commit36cf5c88cfe5a3141eaa784905f3a3bcc0f7f70c (patch)
tree2bcb1ca4b3bde910d5ac5bcad602535688045b47 /fable-cat-poc/src
parent6e610f2e840ab2f09b82bc0742037511c03819a7 (diff)
downloadfable-poc-36cf5c88cfe5a3141eaa784905f3a3bcc0f7f70c.tar.gz
Proof of concept extraction state machine
Diffstat (limited to 'fable-cat-poc/src')
-rw-r--r--fable-cat-poc/src/main.rs152
1 files changed, 152 insertions, 0 deletions
diff --git a/fable-cat-poc/src/main.rs b/fable-cat-poc/src/main.rs
new file mode 100644
index 0000000..fb82b8a
--- /dev/null
+++ b/fable-cat-poc/src/main.rs
@@ -0,0 +1,152 @@
+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<usize>) -> 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<usize>) {
+ 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<String> = 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(())
+}