Browse Source

Assignment command parsing

Mark Rousskov 6 years ago
parent
commit
db433c028f
3 changed files with 132 additions and 6 deletions
  1. 18 0
      parser/src/command.rs
  2. 112 0
      parser/src/command/assign.rs
  3. 2 6
      src/handlers/assign.rs

+ 18 - 0
parser/src/command.rs

@@ -3,6 +3,7 @@ use crate::error::Error;
 use crate::token::{Token, Tokenizer};
 
 pub mod relabel;
+pub mod assign;
 
 pub fn find_commmand_start(input: &str, bot: &str) -> Option<usize> {
     input.find(&format!("@{}", bot))
@@ -11,6 +12,7 @@ pub fn find_commmand_start(input: &str, bot: &str) -> Option<usize> {
 #[derive(Debug)]
 pub enum Command<'a> {
     Relabel(Result<relabel::RelabelCommand, Error<'a>>),
+    Assign(Result<assign::AssignCommand, Error<'a>>),
     None,
 }
 
@@ -62,6 +64,21 @@ impl<'a> Input<'a> {
             }
         }
 
+        {
+            let mut tok = original_tokenizer.clone();
+            let res = assign::AssignCommand::parse(&mut tok);
+            match res {
+                Ok(None) => {}
+                Ok(Some(cmd)) => {
+                    success.push((tok, Command::Assign(Ok(cmd))));
+                }
+                Err(err) => {
+                    success.push((tok, Command::Assign(Err(err))));
+                }
+            }
+        }
+
+
         if success.len() > 1 {
             panic!(
                 "succeeded parsing {:?} to multiple commands: {:?}",
@@ -95,6 +112,7 @@ impl<'a> Command<'a> {
     pub fn is_ok(&self) -> bool {
         match self {
             Command::Relabel(r) => r.is_ok(),
+            Command::Assign(r) => r.is_ok(),
             Command::None => true,
         }
     }

+ 112 - 0
parser/src/command/assign.rs

@@ -0,0 +1,112 @@
+//! The assignment command parser.
+//!
+//! This can parse arbitrary input, giving the user to be assigned.
+//!
+//! The grammar is as follows:
+//!
+//! ```text
+//! Command: `@bot claim` or `@bot assign @user`.
+//! ```
+
+use crate::error::Error;
+use crate::token::{Token, Tokenizer};
+use std::fmt;
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum AssignCommand {
+    Own,
+    User { username: String },
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum ParseError {
+    ExpectedEnd,
+    MentionUser,
+    NoUser,
+}
+
+impl std::error::Error for ParseError {}
+
+impl fmt::Display for ParseError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            ParseError::MentionUser => write!(f, "user should start with @"),
+            ParseError::ExpectedEnd => write!(f, "expected end of command"),
+            ParseError::NoUser => write!(f, "specify user to assign to"),
+        }
+    }
+}
+
+impl AssignCommand {
+    pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
+        let mut toks = input.clone();
+        if let Some(Token::Word("claim")) = toks.peek_token()? {
+            toks.next_token()?;
+            if let Some(Token::Dot) | Some(Token::EndOfLine) = toks.peek_token()? {
+                toks.next_token()?;
+                *input = toks;
+                return Ok(Some(AssignCommand::Own));
+            } else {
+                return Err(toks.error(ParseError::ExpectedEnd));
+            }
+        } else if let Some(Token::Word("assign")) = toks.peek_token()? {
+            toks.next_token()?;
+            if let Some(Token::Word(user)) = toks.next_token()? {
+                if user.starts_with("@") && user.len() != 1 {
+                    Ok(Some(AssignCommand::User {
+                        username: user[1..].to_owned(),
+                    }))
+                } else {
+                    return Err(toks.error(ParseError::MentionUser));
+                }
+            } else {
+                return Err(toks.error(ParseError::NoUser));
+            }
+        } else {
+            return Ok(None);
+        }
+    }
+}
+
+#[cfg(test)]
+fn parse<'a>(input: &'a str) -> Result<Option<AssignCommand>, Error<'a>> {
+    let mut toks = Tokenizer::new(input);
+    Ok(AssignCommand::parse(&mut toks)?)
+}
+
+#[test]
+fn test_1() {
+    assert_eq!(
+        parse("claim."),
+        Ok(Some(AssignCommand::Own)),
+    );
+}
+
+#[test]
+fn test_2() {
+    assert_eq!(
+        parse("claim"),
+        Ok(Some(AssignCommand::Own)),
+    );
+}
+
+#[test]
+fn test_3() {
+    assert_eq!(
+        parse("assign @user"),
+        Ok(Some(AssignCommand::User { username: "user".to_owned() })),
+    );
+}
+
+#[test]
+fn test_4() {
+    use std::error::Error;
+    assert_eq!(
+        parse("assign @")
+            .unwrap_err()
+            .source()
+            .unwrap()
+            .downcast_ref(),
+        Some(&ParseError::MentionUser),
+    );
+}

+ 2 - 6
src/handlers/assign.rs

@@ -1,10 +1,6 @@
 //! Permit assignment of any user to issues, without requiring "write" access to the repository.
 //!
-//! It is unknown which approach is needed here: we may need to fake-assign ourselves and add a
-//! 'claimed by' section to the top-level comment. That would be very unideal.
-//!
-//! The ideal workflow here is that the user is added to a read-only team with no access to the
-//! repository and immediately thereafter assigned to the issue.
+//! We need to fake-assign ourselves and add a 'claimed by' section to the top-level comment.
 //!
 //! Such assigned issues should also be placed in a queue to ensure that the user remains
 //! active; the assigned user will be asked for a status report every 2 weeks (XXX: timing).
@@ -13,7 +9,7 @@
 //! been given for the past 2 weeks, the bot will de-assign the user. They can once more claim
 //! the issue if necessary.
 //!
-//! Assign users with `/assign @gh-user` or `/claim` (self-claim).
+//! Assign users with `@rustbot assign @gh-user` or `@rustbot claim` (self-claim).
 
 use crate::{
     github::GithubClient,