@@ -85,10 +85,13 @@ pub fn update_nags(comment: &IssueComment) -> DashResult<()> {
85
85
86
86
let subteam_members = subteam_members ( & issue) ?;
87
87
88
- // attempt to parse a command out of the comment
89
- if let Ok ( command) = RfcBotCommand :: from_str ( & comment. body ) {
88
+ // Attempt to parse all commands out of the comment
89
+ let mut any = false ;
90
+ for command in RfcBotCommand :: from_str_all ( & comment. body ) {
91
+ any = true ;
90
92
91
- // don't accept bot commands from non-subteam members
93
+ // Don't accept bot commands from non-subteam members.
94
+ // Early return because we'll just get here again...
92
95
if subteam_members. iter ( ) . find ( |& u| u == & author) . is_none ( ) {
93
96
info ! ( "command author ({}) doesn't appear in any relevant subteams" ,
94
97
author. login) ;
@@ -104,8 +107,9 @@ pub fn update_nags(comment: &IssueComment) -> DashResult<()> {
104
107
} ) ;
105
108
106
109
debug ! ( "rfcbot command is processed" ) ;
110
+ }
107
111
108
- } else {
112
+ if !any {
109
113
ok_or ! ( resolve_applicable_feedback_requests( & author, & issue, comment) ,
110
114
why => error!( "Unable to resolve feedback requests for comment id {}: {:?}" ,
111
115
comment. id, why) ) ;
@@ -578,6 +582,32 @@ fn parse_command_text<'a>(command: &'a str, subcommand: &'a str) -> &'a str {
578
582
/// Parses all subcommands under the fcp command.
579
583
/// If `fcp_context` is set to false, `@rfcbot <subcommand>`
580
584
/// was passed and not `@rfcbot fcp <subcommand>`.
585
+ ///
586
+ /// @rfcbot accepts roughly the following grammar:
587
+ ///
588
+ /// merge ::= "merge" | "merged" | "merging" | "merges" ;
589
+ /// close ::= "close" | "closed" | "closing" | "closes" ;
590
+ /// postpone ::= "postpone" | "postponed" | "postponing" | "postpones" ;
591
+ /// cancel ::= "cancel | "canceled" | "canceling" | "cancels" ;
592
+ /// review ::= "reviewed" | "review" | "reviewing" | "reviews" ;
593
+ /// concern ::= "concern" | "concerned" | "concerning" | "concerns" ;
594
+ /// resolve ::= "resolve" | "resolved" | "resolving" | "resolves" ;
595
+ ///
596
+ /// line_remainder ::= .+$ ;
597
+ /// ws_separated ::= ... ;
598
+ ///
599
+ /// subcommand ::= merge | close | postpone | cancel | review
600
+ /// | concern line_remainder
601
+ /// | resolve line_remainder
602
+ /// ;
603
+ ///
604
+ /// invocation ::= "fcp" subcommand
605
+ /// | "pr" subcommand
606
+ /// | "f?" ws_separated
607
+ /// | subcommand
608
+ /// ;
609
+ ///
610
+ /// grammar ::= "@rfcbot" ":"? invocation ;
581
611
fn parse_fcp_subcommand < ' a > (
582
612
command : & ' a str ,
583
613
subcommand : & ' a str ,
@@ -876,20 +906,20 @@ impl<'a> RfcBotCommand<'a> {
876
906
Ok ( ( ) )
877
907
}
878
908
879
- pub fn from_str ( command : & ' a str ) -> DashResult < RfcBotCommand < ' a > > {
880
- // get the tokens for the command line (starts with a bot mention)
881
- let command = command
882
- . lines ( )
883
- . find ( |& l| l. starts_with ( RFC_BOT_MENTION ) )
884
- . ok_or ( DashError :: Misc ( None ) ) ?
885
- . trim_left_matches ( RFC_BOT_MENTION )
886
- . trim_left_matches ( ':' )
887
- . trim ( ) ;
888
-
889
- let mut tokens = command. split_whitespace ( ) ;
909
+ pub fn from_str_all ( command : & ' a str ) -> impl Iterator < Item = RfcBotCommand < ' a > > {
910
+ // Get the tokens for each command line (starts with a bot mention)
911
+ command. lines ( )
912
+ . filter ( |& l| l. starts_with ( RFC_BOT_MENTION ) )
913
+ . map ( Self :: from_invocation_line)
914
+ . filter_map ( Result :: ok)
915
+ }
890
916
917
+ fn from_invocation_line ( command : & ' a str ) -> DashResult < RfcBotCommand < ' a > > {
918
+ let mut tokens = command. trim_left_matches ( RFC_BOT_MENTION )
919
+ . trim_left_matches ( ':' )
920
+ . trim ( )
921
+ . split_whitespace ( ) ;
891
922
let invocation = tokens. next ( ) . ok_or ( DashError :: Misc ( None ) ) ?;
892
-
893
923
match invocation {
894
924
"fcp" | "pr" => {
895
925
let subcommand = tokens. next ( ) . ok_or ( DashError :: Misc ( None ) ) ?;
@@ -913,6 +943,7 @@ impl<'a> RfcBotCommand<'a> {
913
943
_ => parse_fcp_subcommand ( command, invocation, false ) ,
914
944
}
915
945
}
946
+
916
947
}
917
948
918
949
struct RfcBotComment < ' a > {
@@ -1120,6 +1151,34 @@ impl<'a> RfcBotComment<'a> {
1120
1151
mod test {
1121
1152
use super :: * ;
1122
1153
1154
+ #[ test]
1155
+ fn multiple_commands ( ) {
1156
+ let text = r#"
1157
+ someothertext
1158
+ @rfcbot: resolved CONCERN_NAME
1159
+ somemoretext
1160
+
1161
+ somemoretext
1162
+
1163
+ @rfcbot: fcp cancel
1164
+ foobar
1165
+ @rfcbot concern foobar
1166
+ "# ;
1167
+
1168
+ let cmd = RfcBotCommand :: from_str_all ( text) . collect :: < Vec < _ > > ( ) ;
1169
+ assert_eq ! ( cmd, vec![
1170
+ RfcBotCommand :: ResolveConcern ( "CONCERN_NAME" ) ,
1171
+ RfcBotCommand :: FcpCancel ,
1172
+ RfcBotCommand :: NewConcern ( "foobar" ) ,
1173
+ ] ) ;
1174
+ }
1175
+
1176
+ fn ensure_take_singleton < I : Iterator > ( mut iter : I ) -> I :: Item {
1177
+ let singleton = iter. next ( ) . unwrap ( ) ;
1178
+ assert ! ( iter. next( ) . is_none( ) ) ;
1179
+ singleton
1180
+ }
1181
+
1123
1182
macro_rules! justification {
1124
1183
( ) => { "\n \n Some justification here." } ;
1125
1184
}
@@ -1148,8 +1207,11 @@ somemoretext")
1148
1207
let body = concat!( "@rfcbot: " , $cmd) ;
1149
1208
let body_no_colon = concat!( "@rfcbot " , $cmd) ;
1150
1209
1151
- let with_colon = RfcBotCommand :: from_str( body) . unwrap( ) ;
1152
- let without_colon = RfcBotCommand :: from_str( body_no_colon) . unwrap( ) ;
1210
+ let with_colon =
1211
+ ensure_take_singleton( RfcBotCommand :: from_str_all( body) ) ;
1212
+
1213
+ let without_colon =
1214
+ ensure_take_singleton( RfcBotCommand :: from_str_all( body_no_colon) ) ;
1153
1215
1154
1216
assert_eq!( with_colon, without_colon) ;
1155
1217
assert_eq!( with_colon, expected) ;
@@ -1220,8 +1282,9 @@ somemoretext
1220
1282
1221
1283
somemoretext" ;
1222
1284
1223
- let with_colon = RfcBotCommand :: from_str ( body) . unwrap ( ) ;
1224
- let without_colon = RfcBotCommand :: from_str ( body_no_colon) . unwrap ( ) ;
1285
+ let with_colon = ensure_take_singleton ( RfcBotCommand :: from_str_all ( body) ) ;
1286
+ let without_colon =
1287
+ ensure_take_singleton ( RfcBotCommand :: from_str_all ( body_no_colon) ) ;
1225
1288
1226
1289
assert_eq ! ( with_colon, without_colon) ;
1227
1290
assert_eq ! ( with_colon, RfcBotCommand :: ResolveConcern ( "CONCERN_NAME" ) ) ;
0 commit comments