Skip to content

Commit 45f3e9c

Browse files
committed
Handle r? as assignment
This adds parsing support for handling r? to direct assignment; anyone can do so on a PR to any org member. A future commit will add support for r? with a team. This does not currently auto-assign reviewers like highfive to new PRs, though.
1 parent bcb7f65 commit 45f3e9c

File tree

1 file changed

+122
-65
lines changed

1 file changed

+122
-65
lines changed

parser/src/command.rs

Lines changed: 122 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ pub mod prioritize;
1111
pub mod relabel;
1212
pub mod second;
1313

14-
pub fn find_command_start(input: &str, bot: &str) -> Option<usize> {
15-
input.to_ascii_lowercase().find(&format!("@{}", bot))
14+
pub fn find_command_start(input: &str, bots: &[&str]) -> Option<usize> {
15+
let input = input.to_ascii_lowercase();
16+
bots.iter()
17+
.map(|name| format!("@{}", name))
18+
.chain(std::iter::once("r?".to_owned()))
19+
.filter_map(|s| input.find(&s))
20+
.min()
1621
}
1722

1823
#[derive(Debug, PartialEq)]
@@ -69,61 +74,98 @@ impl<'a> Input<'a> {
6974

7075
fn parse_command(&mut self) -> Option<Command<'a>> {
7176
let mut tok = Tokenizer::new(&self.all[self.parsed..]);
72-
let name_length = if let Ok(Some(Token::Word(bot_name))) = tok.next_token() {
73-
assert!(self
74-
.bot
75-
.iter()
76-
.any(|name| bot_name.eq_ignore_ascii_case(&format!("@{}", name))));
77-
bot_name.len()
78-
} else {
79-
panic!("no bot name?")
77+
let review = match tok.next_token() {
78+
Ok(Some(Token::Word(r))) if r == "r" || r == "R" => true,
79+
Ok(Some(Token::Word(bot_name))) => {
80+
assert!(
81+
self.bot
82+
.iter()
83+
.any(|name| bot_name.eq_ignore_ascii_case(&format!("@{}", name))),
84+
"not this bot name? ({:?})",
85+
bot_name
86+
);
87+
false
88+
}
89+
other => panic!("neiter review nor bot name? ({:?})", other),
8090
};
81-
log::info!("identified potential command");
8291

8392
let mut success = vec![];
8493

85-
let original_tokenizer = tok.clone();
94+
if review {
95+
match tok.next_token() {
96+
Ok(Some(Token::Question)) => {}
97+
other => {
98+
log::trace!("received odd review start token: {:?}", other);
99+
return None;
100+
}
101+
}
102+
log::info!("identified potential review request");
103+
match tok.next_token() {
104+
Ok(Some(Token::Word(w))) => {
105+
let mentions = crate::mentions::get_mentions(w);
106+
if let [a] = &mentions[..] {
107+
success.push((
108+
tok,
109+
Command::Assign(Ok(assign::AssignCommand::User {
110+
username: (*a).to_owned(),
111+
})),
112+
));
113+
} else {
114+
log::trace!("{:?} had non-one mention: {:?}", w, mentions);
115+
return None;
116+
}
117+
}
118+
other => {
119+
log::trace!("received odd review start token: {:?}", other);
120+
return None;
121+
}
122+
}
123+
} else {
124+
log::info!("identified potential command");
86125

87-
success.extend(parse_single_command(
88-
relabel::RelabelCommand::parse,
89-
Command::Relabel,
90-
&original_tokenizer,
91-
));
92-
success.extend(parse_single_command(
93-
assign::AssignCommand::parse,
94-
Command::Assign,
95-
&original_tokenizer,
96-
));
97-
success.extend(parse_single_command(
98-
ping::PingCommand::parse,
99-
Command::Ping,
100-
&original_tokenizer,
101-
));
102-
success.extend(parse_single_command(
103-
nominate::NominateCommand::parse,
104-
Command::Nominate,
105-
&original_tokenizer,
106-
));
107-
success.extend(parse_single_command(
108-
prioritize::PrioritizeCommand::parse,
109-
Command::Prioritize,
110-
&original_tokenizer,
111-
));
112-
success.extend(parse_single_command(
113-
second::SecondCommand::parse,
114-
Command::Second,
115-
&original_tokenizer,
116-
));
117-
success.extend(parse_single_command(
118-
glacier::GlacierCommand::parse,
119-
Command::Glacier,
120-
&original_tokenizer,
121-
));
122-
success.extend(parse_single_command(
123-
close::CloseCommand::parse,
124-
Command::Close,
125-
&original_tokenizer,
126-
));
126+
let original_tokenizer = tok.clone();
127+
128+
success.extend(parse_single_command(
129+
relabel::RelabelCommand::parse,
130+
Command::Relabel,
131+
&original_tokenizer,
132+
));
133+
success.extend(parse_single_command(
134+
assign::AssignCommand::parse,
135+
Command::Assign,
136+
&original_tokenizer,
137+
));
138+
success.extend(parse_single_command(
139+
ping::PingCommand::parse,
140+
Command::Ping,
141+
&original_tokenizer,
142+
));
143+
success.extend(parse_single_command(
144+
nominate::NominateCommand::parse,
145+
Command::Nominate,
146+
&original_tokenizer,
147+
));
148+
success.extend(parse_single_command(
149+
prioritize::PrioritizeCommand::parse,
150+
Command::Prioritize,
151+
&original_tokenizer,
152+
));
153+
success.extend(parse_single_command(
154+
second::SecondCommand::parse,
155+
Command::Second,
156+
&original_tokenizer,
157+
));
158+
success.extend(parse_single_command(
159+
glacier::GlacierCommand::parse,
160+
Command::Glacier,
161+
&original_tokenizer,
162+
));
163+
success.extend(parse_single_command(
164+
close::CloseCommand::parse,
165+
Command::Close,
166+
&original_tokenizer,
167+
));
168+
}
127169

128170
if success.len() > 1 {
129171
panic!(
@@ -133,6 +175,8 @@ impl<'a> Input<'a> {
133175
);
134176
}
135177

178+
let (mut tok, c) = success.pop()?;
179+
136180
if self
137181
.code
138182
.overlaps_code((self.parsed)..(self.parsed + tok.position()))
@@ -142,13 +186,8 @@ impl<'a> Input<'a> {
142186
return None;
143187
}
144188

145-
let (mut tok, c) = success.pop()?;
146189
// if we errored out while parsing the command do not move the input forwards
147-
self.parsed += if c.is_ok() {
148-
tok.position()
149-
} else {
150-
name_length
151-
};
190+
self.parsed += if c.is_ok() { tok.position() } else { 1 };
152191
Some(c)
153192
}
154193
}
@@ -158,16 +197,12 @@ impl<'a> Iterator for Input<'a> {
158197

159198
fn next(&mut self) -> Option<Command<'a>> {
160199
loop {
161-
let start = self
162-
.bot
163-
.iter()
164-
.filter_map(|name| find_command_start(&self.all[self.parsed..], name))
165-
.min()?;
200+
let start = find_command_start(&self.all[self.parsed..], &self.bot)?;
166201
self.parsed += start;
167202
if let Some(command) = self.parse_command() {
168203
return Some(command);
169204
}
170-
self.parsed += self.bot.len() + 1;
205+
self.parsed += 1;
171206
}
172207
}
173208
}
@@ -247,7 +282,7 @@ fn move_input_along_1() {
247282
let mut input = Input::new(input, vec!["bot"]);
248283
assert!(input.next().unwrap().is_err());
249284
// don't move input along if parsing the command fails
250-
assert_eq!(&input.all[..input.parsed], "@bot");
285+
assert_eq!(&input.all[..input.parsed], "@");
251286
}
252287

253288
#[test]
@@ -262,3 +297,25 @@ fn multiname() {
262297
assert!(input.next().unwrap().is_ok());
263298
assert!(input.next().is_none());
264299
}
300+
301+
#[test]
302+
fn parse_assign_review() {
303+
let input = "R? @user";
304+
let mut input = Input::new(input, vec!["bot"]);
305+
match input.next().unwrap() {
306+
Command::Assign(Ok(x)) => assert_eq!(
307+
x,
308+
assign::AssignCommand::User {
309+
username: String::from("user"),
310+
}
311+
),
312+
o => panic!("unknown: {:?}", o),
313+
};
314+
}
315+
316+
#[test]
317+
fn parse_assign_review_no_panic() {
318+
let input = "R ?";
319+
let mut input = Input::new(input, vec!["bot"]);
320+
assert!(input.next().is_none());
321+
}

0 commit comments

Comments
 (0)