@@ -5,15 +5,13 @@ use crate::github::{
55} ; 
66use  crate :: load:: SiteCtxt ; 
77
8- use  std:: sync:: Arc ; 
9- 
8+ use  hashbrown:: HashMap ; 
109use  regex:: Regex ; 
10+ use  std:: sync:: Arc ; 
1111
1212lazy_static:: lazy_static! { 
1313    static  ref BODY_TIMER_BUILD :  Regex  =
1414        Regex :: new( r"(?:\W|^)@rust-timer\s+build\s+(\w+)(?:\W|$)(?:include=(\S+))?\s*(?:exclude=(\S+))?\s*(?:runs=(\d+))?" ) . unwrap( ) ; 
15-     static  ref BODY_TIMER_QUEUE :  Regex  =
16-         Regex :: new( r"(?:\W|^)@rust-timer\s+queue(?:\W|$)(?:include=(\S+))?\s*(?:exclude=(\S+))?\s*(?:runs=(\d+))?" ) . unwrap( ) ; 
1715} 
1816
1917pub  async  fn  handle_github ( 
@@ -118,26 +116,25 @@ async fn handle_rust_timer(
118116        return  Ok ( github:: Response ) ; 
119117    } 
120118
121-     if  let  Some ( captures)  = BODY_TIMER_QUEUE . captures ( & comment. body )  { 
122-         let  include = captures. get ( 1 ) . map ( |v| v. as_str ( ) ) ; 
123-         let  exclude = captures. get ( 2 ) . map ( |v| v. as_str ( ) ) ; 
124-         let  runs = captures. get ( 3 ) . and_then ( |v| v. as_str ( ) . parse :: < i32 > ( ) . ok ( ) ) ; 
125-         { 
126-             let  conn = ctxt. conn ( ) . await ; 
127-             conn. queue_pr ( issue. number ,  include,  exclude,  runs) . await ; 
128-         } 
129-         main_client
130-             . post_comment ( 
131-                 issue. number , 
119+     if  let  Some ( queue)  = parse_queue_command ( & comment. body )  { 
120+         let  msg = match  queue { 
121+             Ok ( cmd)  => { 
122+                 let  conn = ctxt. conn ( ) . await ; 
123+                 conn. queue_pr ( issue. number ,  cmd. include ,  cmd. exclude ,  cmd. runs ) 
124+                     . await ; 
132125                format ! ( 
133126                    "Awaiting bors try build completion. 
134127
135128@rustbot label: +S-waiting-on-perf 
136129
137130{COMMENT_MARK_TEMPORARY}" 
138-                 ) , 
139-             ) 
140-             . await ; 
131+                 ) 
132+             } 
133+             Err ( error)  => { 
134+                 format ! ( "Error occurred while parsing comment: {error}" ) 
135+             } 
136+         } ; 
137+         main_client. post_comment ( issue. number ,  msg) . await ; 
141138        return  Ok ( github:: Response ) ; 
142139    } 
143140
@@ -163,6 +160,68 @@ async fn handle_rust_timer(
163160    Ok ( github:: Response ) 
164161} 
165162
163+ /// Parses the first occurrence of a `@rust-timer queue <...>` command 
164+ /// in the input string. 
165+ fn  parse_queue_command ( body :  & str )  -> Option < Result < QueueCommand ,  String > >  { 
166+     let  prefix = "@rust-timer" ; 
167+     let  bot_line = body. lines ( ) . find_map ( |line| { 
168+         let  line = line. trim ( ) ; 
169+         line. find ( prefix) 
170+             . map ( |index| line[ index + prefix. len ( ) ..] . trim ( ) ) 
171+     } ) ?; 
172+ 
173+     let  args = bot_line. strip_prefix ( "queue" ) . map ( |l| l. trim ( ) ) ?; 
174+     let  mut  args = match  parse_command_arguments ( args)  { 
175+         Ok ( args)  => args, 
176+         Err ( error)  => return  Some ( Err ( error) ) , 
177+     } ; 
178+     let  mut  cmd = QueueCommand  { 
179+         include :  args. remove ( "include" ) , 
180+         exclude :  args. remove ( "exclude" ) , 
181+         runs :  None , 
182+     } ; 
183+     if  let  Some ( runs)  = args. remove ( "runs" )  { 
184+         let  Ok ( runs)  = runs. parse :: < u32 > ( )  else  { 
185+             return  Some ( Err ( format ! ( "Cannot parse runs {runs} as a number" ) ) ) ; 
186+         } ; 
187+         cmd. runs  = Some ( runs as  i32 ) ; 
188+     } 
189+ 
190+     if  let  Some ( ( key,  _) )  = args. into_iter ( ) . next ( )  { 
191+         return  Some ( Err ( format ! ( "Unknown command argument {key}" ) ) ) ; 
192+     } 
193+ 
194+     Some ( Ok ( cmd) ) 
195+ } 
196+ 
197+ /// Parses command arguments from a single line of text. 
198+ /// Expects that arguments are separated by whitespace, and each argument 
199+ /// has the format `<key>=<value>`. 
200+ fn  parse_command_arguments ( args :  & str )  -> Result < HashMap < & str ,  & str > ,  String >  { 
201+     let  mut  argmap = HashMap :: new ( ) ; 
202+     for  arg in  args. split_whitespace ( )  { 
203+         let  Some ( ( key,  value) )  = arg. split_once ( '=' )  else  { 
204+             return  Err ( format ! ( 
205+                 "Invalid command argument `{arg}` (there may be no spaces around the `=` character)" 
206+             ) ) ; 
207+         } ; 
208+         let  key = key. trim ( ) ; 
209+         let  value = value. trim ( ) ; 
210+         if  argmap. insert ( key,  value) . is_some ( )  { 
211+             return  Err ( format ! ( "Duplicate command argument `{key}`" ) ) ; 
212+         } 
213+     } 
214+ 
215+     Ok ( argmap) 
216+ } 
217+ 
218+ #[ derive( Debug ) ]  
219+ struct  QueueCommand < ' a >  { 
220+     include :  Option < & ' a  str > , 
221+     exclude :  Option < & ' a  str > , 
222+     runs :  Option < i32 > , 
223+ } 
224+ 
166225/// Run the `@rust-timer build` regex over the comment message extracting the commit and the other captures 
167226fn  build_captures ( comment_body :  & str )  -> impl  Iterator < Item  = ( & str ,  regex:: Captures ) >  { 
168227    BODY_TIMER_BUILD 
@@ -196,6 +255,7 @@ pub async fn get_authorized_users() -> Result<Vec<u64>, String> {
196255#[ cfg( test) ]  
197256mod  tests { 
198257    use  super :: * ; 
258+ 
199259    #[ test]  
200260    fn  captures_all_shas ( )  { 
201261        let  comment_body = r#" 
@@ -215,4 +275,80 @@ Going to do perf runs for a few of these:
215275            ] 
216276        ) ; 
217277    } 
278+ 
279+     #[ test]  
280+     fn  command_missing ( )  { 
281+         assert ! ( parse_queue_command( "" ) . is_none( ) ) ; 
282+     } 
283+ 
284+     #[ test]  
285+     fn  unknown_command ( )  { 
286+         assert ! ( parse_queue_command( "@rust-timer foo" ) . is_none( ) ) ; 
287+     } 
288+ 
289+     #[ test]  
290+     fn  queue_command ( )  { 
291+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue" ) , 
292+             @"Some(Ok(QueueCommand { include: None, exclude: None, runs: None }))" ) ; 
293+     } 
294+ 
295+     #[ test]  
296+     fn  queue_command_unknown_arg ( )  { 
297+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue foo=bar" ) , 
298+             @r###"Some(Err("Unknown command argument foo"))"### ) ; 
299+     } 
300+ 
301+     #[ test]  
302+     fn  queue_command_duplicate_arg ( )  { 
303+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=a exclude=c include=b" ) , 
304+             @r###"Some(Err("Duplicate command argument `include`"))"### ) ; 
305+     } 
306+ 
307+     #[ test]  
308+     fn  queue_command_include ( )  { 
309+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=abcd,feih" ) , 
310+             @r###"Some(Ok(QueueCommand { include: Some("abcd,feih"), exclude: None, runs: None }))"### ) ; 
311+     } 
312+ 
313+     #[ test]  
314+     fn  queue_command_exclude ( )  { 
315+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue exclude=foo134,barzbaz41baf" ) , 
316+             @r###"Some(Ok(QueueCommand { include: None, exclude: Some("foo134,barzbaz41baf"), runs: None }))"### ) ; 
317+     } 
318+ 
319+     #[ test]  
320+     fn  queue_command_runs ( )  { 
321+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue runs=5" ) , 
322+             @"Some(Ok(QueueCommand { include: None, exclude: None, runs: Some(5) }))" ) ; 
323+     } 
324+ 
325+     #[ test]  
326+     fn  queue_command_runs_nan ( )  { 
327+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue runs=xxx" ) , 
328+             @r###"Some(Err("Cannot parse runs xxx as a number"))"### ) ; 
329+     } 
330+ 
331+     #[ test]  
332+     fn  queue_command_combination ( )  { 
333+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include=acda,13asd exclude=c13,DA runs=5" ) , 
334+             @r###"Some(Ok(QueueCommand { include: Some("acda,13asd"), exclude: Some("c13,DA"), runs: Some(5) }))"### ) ; 
335+     } 
336+ 
337+     #[ test]  
338+     fn  queue_command_argument_spaces ( )  { 
339+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer queue include  =  abcd,das" ) , 
340+             @r###"Some(Err("Invalid command argument `include` (there may be no spaces around the `=` character)"))"### ) ; 
341+     } 
342+ 
343+     #[ test]  
344+     fn  queue_command_spaces ( )  { 
345+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@rust-timer     queue     include=abcd,das   " ) , 
346+             @r###"Some(Ok(QueueCommand { include: Some("abcd,das"), exclude: None, runs: None }))"### ) ; 
347+     } 
348+ 
349+     #[ test]  
350+     fn  queue_command_with_bors ( )  { 
351+         insta:: assert_compact_debug_snapshot!( parse_queue_command( "@bors try @rust-timer queue include=foo,bar" ) , 
352+             @r###"Some(Ok(QueueCommand { include: Some("foo,bar"), exclude: None, runs: None }))"### ) ; 
353+     } 
218354} 
0 commit comments