Browse Source

Ignore bot invocations in code blocks

Mark Rousskov 6 years ago
parent
commit
85010385c3
2 changed files with 89 additions and 45 deletions
  1. 7 6
      parser/src/code_block.rs
  2. 82 39
      parser/src/command.rs

+ 7 - 6
parser/src/code_block.rs

@@ -1,6 +1,7 @@
 use pulldown_cmark::{Event, Parser, Tag};
 use std::ops::Range;
 
+#[derive(Debug)]
 pub struct ColorCodeBlocks {
     code: Vec<Range<usize>>,
 }
@@ -31,14 +32,14 @@ impl ColorCodeBlocks {
         ColorCodeBlocks { code }
     }
 
-    pub fn is_in_code(&self, pos: usize) -> bool {
-        for range in &self.code {
-            if range.start <= pos && pos <= range.end {
-                return true;
+    pub fn overlaps_code(&self, region: Range<usize>) -> Option<Range<usize>> {
+        for code in &self.code {
+            // See https://stackoverflow.com/questions/3269434.
+            if code.start <= region.end && region.start <= code.end {
+                return Some(code.clone());
             }
         }
-
-        false
+        None
     }
 }
 

+ 82 - 39
parser/src/command.rs

@@ -1,3 +1,4 @@
+use crate::code_block::ColorCodeBlocks;
 use crate::error::Error;
 use crate::token::{Token, Tokenizer};
 
@@ -12,59 +13,101 @@ pub enum Command<'a> {
     Label(label::LabelCommand<'a>),
 }
 
-pub fn parse_command<'a>(input: &mut &'a str, bot: &str) -> Result<Option<Command<'a>>, Error<'a>> {
-    let start = match find_commmand_start(input, bot) {
-        Some(pos) => pos,
-        None => return Ok(None),
-    };
-    *input = &input[start..];
-    let mut tok = Tokenizer::new(&input);
-    assert_eq!(
-        tok.next_token().unwrap(),
-        Some(Token::Word(&format!("@{}", bot)))
-    );
-
-    let mut success = vec![];
-
-    {
-        let mut lc = tok.clone();
-        let res = label::LabelCommand::parse(&mut lc)?;
-        match res {
-            None => {}
-            Some(cmd) => {
-                // save tokenizer off
-                tok = lc;
-                success.push(Command::Label(cmd));
-            }
+#[derive(Debug)]
+pub struct Input<'a> {
+    all: &'a str,
+    parsed: usize,
+    code: ColorCodeBlocks,
+    bot: &'a str,
+}
+
+impl<'a> Input<'a> {
+    pub fn new(input: &'a str, bot: &'a str) -> Input<'a> {
+        Input {
+            all: input,
+            parsed: 0,
+            code: ColorCodeBlocks::new(input),
+            bot: bot,
         }
     }
 
-    if success.len() > 1 {
-        panic!(
-            "succeeded parsing {:?} to multiple commands: {:?}",
-            input, success
+    pub fn parse_command(&mut self) -> Result<Option<Command<'a>>, Error<'a>> {
+        let start = match find_commmand_start(&self.all[self.parsed..], self.bot) {
+            Some(pos) => pos,
+            None => return Ok(None),
+        };
+        self.parsed += start;
+        let mut tok = Tokenizer::new(&self.all[self.parsed..]);
+        assert_eq!(
+            tok.next_token().unwrap(),
+            Some(Token::Word(&format!("@{}", self.bot)))
         );
-    }
 
-    // XXX: Check that command did not intersect with code block
+        let mut success = vec![];
 
-    *input = &input[tok.position()..];
+        {
+            let mut lc = tok.clone();
+            let res = label::LabelCommand::parse(&mut lc)?;
+            match res {
+                None => {}
+                Some(cmd) => {
+                    // save tokenizer off
+                    tok = lc;
+                    success.push(Command::Label(cmd));
+                }
+            }
+        }
 
-    Ok(success.pop())
+        if success.len() > 1 {
+            panic!(
+                "succeeded parsing {:?} to multiple commands: {:?}",
+                &self.all[self.parsed..],
+                success
+            );
+        }
+
+        if let Some(_) = self
+            .code
+            .overlaps_code((self.parsed)..(self.parsed + tok.position()))
+        {
+            return Ok(None);
+        }
+
+        self.parsed += tok.position();
+
+        Ok(success.pop())
+    }
+}
+
+#[test]
+fn code_1() {
+    let input = "`@bot modify labels: +bug.`";
+    let mut input = Input::new(input, "bot");
+    assert!(input.parse_command().unwrap().is_none());
+}
+
+#[test]
+fn code_2() {
+    let input = "```
+    @bot modify labels: +bug.
+    ```";
+    let mut input = Input::new(input, "bot");
+    assert!(input.parse_command().unwrap().is_none());
 }
 
 #[test]
 fn move_input_along() {
-    let mut input = "@bot modify labels: +bug. Afterwards, delete the world.";
-    assert!(parse_command(&mut input, "bot").unwrap().is_some());
-    assert_eq!(input, " Afterwards, delete the world.");
+    let input = "@bot modify labels: +bug. Afterwards, delete the world.";
+    let mut input = Input::new(input, "bot");
+    assert!(input.parse_command().unwrap().is_some());
+    assert_eq!(&input.all[input.parsed..], " Afterwards, delete the world.");
 }
 
 #[test]
 fn move_input_along_1() {
-    let mut input = "@bot modify labels\": +bug. Afterwards, delete the world.";
-    let input_cp = input;
-    assert!(parse_command(&mut input, "bot").is_err());
+    let input = "@bot modify labels\": +bug. Afterwards, delete the world.";
+    let mut input = Input::new(input, "bot");
+    assert!(input.parse_command().is_err());
     // don't move input along if parsing the command fails
-    assert_eq!(input, input_cp);
+    assert_eq!(input.parsed, 0);
 }