command.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. use crate::code_block::ColorCodeBlocks;
  2. use crate::error::Error;
  3. use crate::token::{Token, Tokenizer};
  4. pub mod assign;
  5. pub mod close;
  6. pub mod glacier;
  7. pub mod nominate;
  8. pub mod ping;
  9. pub mod prioritize;
  10. pub mod relabel;
  11. pub mod second;
  12. pub fn find_command_start(input: &str, bot: &str) -> Option<usize> {
  13. input.to_ascii_lowercase().find(&format!("@{}", bot))
  14. }
  15. #[derive(Debug, PartialEq)]
  16. pub enum Command<'a> {
  17. Relabel(Result<relabel::RelabelCommand, Error<'a>>),
  18. Assign(Result<assign::AssignCommand, Error<'a>>),
  19. Ping(Result<ping::PingCommand, Error<'a>>),
  20. Nominate(Result<nominate::NominateCommand, Error<'a>>),
  21. Prioritize(Result<prioritize::PrioritizeCommand, Error<'a>>),
  22. Second(Result<second::SecondCommand, Error<'a>>),
  23. Glacier(Result<glacier::GlacierCommand, Error<'a>>),
  24. Close(Result<close::CloseCommand, Error<'a>>),
  25. }
  26. #[derive(Debug)]
  27. pub struct Input<'a> {
  28. all: &'a str,
  29. parsed: usize,
  30. code: ColorCodeBlocks,
  31. // A list of possible bot names.
  32. bot: Vec<&'a str>,
  33. }
  34. fn parse_single_command<'a, T, F, M>(
  35. parse: F,
  36. mapper: M,
  37. tokenizer: &Tokenizer<'a>,
  38. ) -> Option<(Tokenizer<'a>, Command<'a>)>
  39. where
  40. F: FnOnce(&mut Tokenizer<'a>) -> Result<Option<T>, Error<'a>>,
  41. M: FnOnce(Result<T, Error<'a>>) -> Command<'a>,
  42. T: std::fmt::Debug,
  43. {
  44. let mut tok = tokenizer.clone();
  45. let res = parse(&mut tok);
  46. log::info!("parsed {:?} command: {:?}", std::any::type_name::<T>(), res);
  47. match res {
  48. Ok(None) => None,
  49. Ok(Some(v)) => Some((tok, mapper(Ok(v)))),
  50. Err(err) => Some((tok, mapper(Err(err)))),
  51. }
  52. }
  53. impl<'a> Input<'a> {
  54. pub fn new(input: &'a str, bot: Vec<&'a str>) -> Input<'a> {
  55. Input {
  56. all: input,
  57. parsed: 0,
  58. code: ColorCodeBlocks::new(input),
  59. bot,
  60. }
  61. }
  62. fn parse_command(&mut self) -> Option<Command<'a>> {
  63. let mut tok = Tokenizer::new(&self.all[self.parsed..]);
  64. let name_length = if let Ok(Some(Token::Word(bot_name))) = tok.next_token() {
  65. assert!(self
  66. .bot
  67. .iter()
  68. .any(|name| bot_name.eq_ignore_ascii_case(&format!("@{}", name))));
  69. bot_name.len()
  70. } else {
  71. panic!("no bot name?")
  72. };
  73. log::info!("identified potential command");
  74. let mut success = vec![];
  75. let original_tokenizer = tok.clone();
  76. success.extend(parse_single_command(
  77. relabel::RelabelCommand::parse,
  78. Command::Relabel,
  79. &original_tokenizer,
  80. ));
  81. success.extend(parse_single_command(
  82. assign::AssignCommand::parse,
  83. Command::Assign,
  84. &original_tokenizer,
  85. ));
  86. success.extend(parse_single_command(
  87. ping::PingCommand::parse,
  88. Command::Ping,
  89. &original_tokenizer,
  90. ));
  91. success.extend(parse_single_command(
  92. nominate::NominateCommand::parse,
  93. Command::Nominate,
  94. &original_tokenizer,
  95. ));
  96. success.extend(parse_single_command(
  97. prioritize::PrioritizeCommand::parse,
  98. Command::Prioritize,
  99. &original_tokenizer,
  100. ));
  101. success.extend(parse_single_command(
  102. second::SecondCommand::parse,
  103. Command::Second,
  104. &original_tokenizer,
  105. ));
  106. success.extend(parse_single_command(
  107. glacier::GlacierCommand::parse,
  108. Command::Glacier,
  109. &original_tokenizer,
  110. ));
  111. success.extend(parse_single_command(
  112. close::CloseCommand::parse,
  113. Command::Close,
  114. &original_tokenizer,
  115. ));
  116. if success.len() > 1 {
  117. panic!(
  118. "succeeded parsing {:?} to multiple commands: {:?}",
  119. &self.all[self.parsed..],
  120. success
  121. );
  122. }
  123. if self
  124. .code
  125. .overlaps_code((self.parsed)..(self.parsed + tok.position()))
  126. .is_some()
  127. {
  128. log::info!("command overlaps code; code: {:?}", self.code);
  129. return None;
  130. }
  131. let (mut tok, c) = success.pop()?;
  132. // if we errored out while parsing the command do not move the input forwards
  133. self.parsed += if c.is_ok() {
  134. tok.position()
  135. } else {
  136. name_length
  137. };
  138. Some(c)
  139. }
  140. }
  141. impl<'a> Iterator for Input<'a> {
  142. type Item = Command<'a>;
  143. fn next(&mut self) -> Option<Command<'a>> {
  144. loop {
  145. let start = self
  146. .bot
  147. .iter()
  148. .filter_map(|name| find_command_start(&self.all[self.parsed..], name))
  149. .min()?;
  150. self.parsed += start;
  151. if let Some(command) = self.parse_command() {
  152. return Some(command);
  153. }
  154. self.parsed += self.bot.len() + 1;
  155. }
  156. }
  157. }
  158. impl<'a> Command<'a> {
  159. pub fn is_ok(&self) -> bool {
  160. match self {
  161. Command::Relabel(r) => r.is_ok(),
  162. Command::Assign(r) => r.is_ok(),
  163. Command::Ping(r) => r.is_ok(),
  164. Command::Nominate(r) => r.is_ok(),
  165. Command::Prioritize(r) => r.is_ok(),
  166. Command::Second(r) => r.is_ok(),
  167. Command::Glacier(r) => r.is_ok(),
  168. Command::Close(r) => r.is_ok(),
  169. }
  170. }
  171. pub fn is_err(&self) -> bool {
  172. !self.is_ok()
  173. }
  174. }
  175. #[test]
  176. fn errors_outside_command_are_fine() {
  177. let input =
  178. "haha\" unterminated quotes @bot modify labels: +bug. Terminating after the command";
  179. let mut input = Input::new(input, vec!["bot"]);
  180. assert!(input.next().unwrap().is_ok());
  181. }
  182. #[test]
  183. fn code_1() {
  184. let input = "`@bot modify labels: +bug.`";
  185. let mut input = Input::new(input, vec!["bot"]);
  186. assert!(input.next().is_none());
  187. }
  188. #[test]
  189. fn code_2() {
  190. let input = "```
  191. @bot modify labels: +bug.
  192. ```";
  193. let mut input = Input::new(input, vec!["bot"]);
  194. assert!(input.next().is_none());
  195. }
  196. #[test]
  197. fn edit_1() {
  198. let input_old = "@bot modify labels: +bug.";
  199. let mut input_old = Input::new(input_old, vec!["bot"]);
  200. let input_new = "Adding labels: @bot modify labels: +bug. some other text";
  201. let mut input_new = Input::new(input_new, vec!["bot"]);
  202. assert_eq!(input_old.next(), input_new.next());
  203. }
  204. #[test]
  205. fn edit_2() {
  206. let input_old = "@bot modify label: +bug.";
  207. let mut input_old = Input::new(input_old, vec!["bot"]);
  208. let input_new = "@bot modify labels: +bug.";
  209. let mut input_new = Input::new(input_new, vec!["bot"]);
  210. assert_ne!(input_old.next(), input_new.next());
  211. }
  212. #[test]
  213. fn move_input_along() {
  214. let input = "@bot modify labels: +bug. Afterwards, delete the world.";
  215. let mut input = Input::new(input, vec!["bot"]);
  216. assert!(input.next().unwrap().is_ok());
  217. assert_eq!(&input.all[input.parsed..], " Afterwards, delete the world.");
  218. }
  219. #[test]
  220. fn move_input_along_1() {
  221. let input = "@bot modify labels\": +bug. Afterwards, delete the world.";
  222. let mut input = Input::new(input, vec!["bot"]);
  223. assert!(input.next().unwrap().is_err());
  224. // don't move input along if parsing the command fails
  225. assert_eq!(&input.all[..input.parsed], "@bot");
  226. }
  227. #[test]
  228. fn multiname() {
  229. let input = "@rustbot modify labels: +bug. Afterwards, delete the world. @triagebot prioritize";
  230. let mut input = Input::new(input, vec!["triagebot", "rustbot"]);
  231. assert!(dbg!(input.next().unwrap()).is_ok());
  232. assert_eq!(
  233. &input.all[input.parsed..],
  234. " Afterwards, delete the world. @triagebot prioritize"
  235. );
  236. assert!(input.next().unwrap().is_ok());
  237. assert!(input.next().is_none());
  238. }