Skip to content

Commit db433c0

Browse files
Assignment command parsing
1 parent 5b2eb38 commit db433c0

File tree

3 files changed

+132
-6
lines changed

3 files changed

+132
-6
lines changed

parser/src/command.rs

+18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::error::Error;
33
use crate::token::{Token, Tokenizer};
44

55
pub mod relabel;
6+
pub mod assign;
67

78
pub fn find_commmand_start(input: &str, bot: &str) -> Option<usize> {
89
input.find(&format!("@{}", bot))
@@ -11,6 +12,7 @@ pub fn find_commmand_start(input: &str, bot: &str) -> Option<usize> {
1112
#[derive(Debug)]
1213
pub enum Command<'a> {
1314
Relabel(Result<relabel::RelabelCommand, Error<'a>>),
15+
Assign(Result<assign::AssignCommand, Error<'a>>),
1416
None,
1517
}
1618

@@ -62,6 +64,21 @@ impl<'a> Input<'a> {
6264
}
6365
}
6466

67+
{
68+
let mut tok = original_tokenizer.clone();
69+
let res = assign::AssignCommand::parse(&mut tok);
70+
match res {
71+
Ok(None) => {}
72+
Ok(Some(cmd)) => {
73+
success.push((tok, Command::Assign(Ok(cmd))));
74+
}
75+
Err(err) => {
76+
success.push((tok, Command::Assign(Err(err))));
77+
}
78+
}
79+
}
80+
81+
6582
if success.len() > 1 {
6683
panic!(
6784
"succeeded parsing {:?} to multiple commands: {:?}",
@@ -95,6 +112,7 @@ impl<'a> Command<'a> {
95112
pub fn is_ok(&self) -> bool {
96113
match self {
97114
Command::Relabel(r) => r.is_ok(),
115+
Command::Assign(r) => r.is_ok(),
98116
Command::None => true,
99117
}
100118
}

parser/src/command/assign.rs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! The assignment command parser.
2+
//!
3+
//! This can parse arbitrary input, giving the user to be assigned.
4+
//!
5+
//! The grammar is as follows:
6+
//!
7+
//! ```text
8+
//! Command: `@bot claim` or `@bot assign @user`.
9+
//! ```
10+
11+
use crate::error::Error;
12+
use crate::token::{Token, Tokenizer};
13+
use std::fmt;
14+
15+
#[derive(PartialEq, Eq, Debug)]
16+
pub enum AssignCommand {
17+
Own,
18+
User { username: String },
19+
}
20+
21+
#[derive(PartialEq, Eq, Debug)]
22+
pub enum ParseError {
23+
ExpectedEnd,
24+
MentionUser,
25+
NoUser,
26+
}
27+
28+
impl std::error::Error for ParseError {}
29+
30+
impl fmt::Display for ParseError {
31+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32+
match self {
33+
ParseError::MentionUser => write!(f, "user should start with @"),
34+
ParseError::ExpectedEnd => write!(f, "expected end of command"),
35+
ParseError::NoUser => write!(f, "specify user to assign to"),
36+
}
37+
}
38+
}
39+
40+
impl AssignCommand {
41+
pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
42+
let mut toks = input.clone();
43+
if let Some(Token::Word("claim")) = toks.peek_token()? {
44+
toks.next_token()?;
45+
if let Some(Token::Dot) | Some(Token::EndOfLine) = toks.peek_token()? {
46+
toks.next_token()?;
47+
*input = toks;
48+
return Ok(Some(AssignCommand::Own));
49+
} else {
50+
return Err(toks.error(ParseError::ExpectedEnd));
51+
}
52+
} else if let Some(Token::Word("assign")) = toks.peek_token()? {
53+
toks.next_token()?;
54+
if let Some(Token::Word(user)) = toks.next_token()? {
55+
if user.starts_with("@") && user.len() != 1 {
56+
Ok(Some(AssignCommand::User {
57+
username: user[1..].to_owned(),
58+
}))
59+
} else {
60+
return Err(toks.error(ParseError::MentionUser));
61+
}
62+
} else {
63+
return Err(toks.error(ParseError::NoUser));
64+
}
65+
} else {
66+
return Ok(None);
67+
}
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
fn parse<'a>(input: &'a str) -> Result<Option<AssignCommand>, Error<'a>> {
73+
let mut toks = Tokenizer::new(input);
74+
Ok(AssignCommand::parse(&mut toks)?)
75+
}
76+
77+
#[test]
78+
fn test_1() {
79+
assert_eq!(
80+
parse("claim."),
81+
Ok(Some(AssignCommand::Own)),
82+
);
83+
}
84+
85+
#[test]
86+
fn test_2() {
87+
assert_eq!(
88+
parse("claim"),
89+
Ok(Some(AssignCommand::Own)),
90+
);
91+
}
92+
93+
#[test]
94+
fn test_3() {
95+
assert_eq!(
96+
parse("assign @user"),
97+
Ok(Some(AssignCommand::User { username: "user".to_owned() })),
98+
);
99+
}
100+
101+
#[test]
102+
fn test_4() {
103+
use std::error::Error;
104+
assert_eq!(
105+
parse("assign @")
106+
.unwrap_err()
107+
.source()
108+
.unwrap()
109+
.downcast_ref(),
110+
Some(&ParseError::MentionUser),
111+
);
112+
}

src/handlers/assign.rs

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

0 commit comments

Comments
 (0)