command.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. use crate::code_block::ColorCodeBlocks;
  2. use crate::error::Error;
  3. use crate::token::{Token, Tokenizer};
  4. pub mod assign;
  5. pub mod glacier;
  6. pub mod nominate;
  7. pub mod ping;
  8. pub mod prioritize;
  9. pub mod relabel;
  10. pub mod second;
  11. pub fn find_commmand_start(input: &str, bot: &str) -> Option<usize> {
  12. input.find(&format!("@{}", bot))
  13. }
  14. #[derive(Debug)]
  15. pub enum Command<'a> {
  16. Relabel(Result<relabel::RelabelCommand, Error<'a>>),
  17. Assign(Result<assign::AssignCommand, Error<'a>>),
  18. Ping(Result<ping::PingCommand, Error<'a>>),
  19. Nominate(Result<nominate::NominateCommand, Error<'a>>),
  20. Prioritize(Result<prioritize::PrioritizeCommand, Error<'a>>),
  21. Second(Result<second::SecondCommand, Error<'a>>),
  22. Glacier(Result<glacier::GlacierCommand, Error<'a>>),
  23. None,
  24. }
  25. #[derive(Debug)]
  26. pub struct Input<'a> {
  27. all: &'a str,
  28. parsed: usize,
  29. code: ColorCodeBlocks,
  30. bot: &'a str,
  31. }
  32. fn parse_single_command<'a, T, F, M>(
  33. parse: F,
  34. mapper: M,
  35. tokenizer: &Tokenizer<'a>,
  36. ) -> Option<(Tokenizer<'a>, Command<'a>)>
  37. where
  38. F: FnOnce(&mut Tokenizer<'a>) -> Result<Option<T>, Error<'a>>,
  39. M: FnOnce(Result<T, Error<'a>>) -> Command<'a>,
  40. T: std::fmt::Debug,
  41. {
  42. let mut tok = tokenizer.clone();
  43. let res = parse(&mut tok);
  44. log::info!("parsed {:?} command: {:?}", std::any::type_name::<T>(), res);
  45. match res {
  46. Ok(None) => None,
  47. Ok(Some(v)) => Some((tok, mapper(Ok(v)))),
  48. Err(err) => Some((tok, mapper(Err(err)))),
  49. }
  50. }
  51. impl<'a> Input<'a> {
  52. pub fn new(input: &'a str, bot: &'a str) -> Input<'a> {
  53. Input {
  54. all: input,
  55. parsed: 0,
  56. code: ColorCodeBlocks::new(input),
  57. bot,
  58. }
  59. }
  60. pub fn parse_command(&mut self) -> Command<'a> {
  61. let start = match find_commmand_start(&self.all[self.parsed..], self.bot) {
  62. Some(pos) => pos,
  63. None => return Command::None,
  64. };
  65. self.parsed += start;
  66. let mut tok = Tokenizer::new(&self.all[self.parsed..]);
  67. assert_eq!(
  68. tok.next_token().unwrap(),
  69. Some(Token::Word(&format!("@{}", self.bot)))
  70. );
  71. log::info!("identified potential command");
  72. let mut success = vec![];
  73. let original_tokenizer = tok.clone();
  74. success.extend(parse_single_command(
  75. relabel::RelabelCommand::parse,
  76. Command::Relabel,
  77. &original_tokenizer,
  78. ));
  79. success.extend(parse_single_command(
  80. assign::AssignCommand::parse,
  81. Command::Assign,
  82. &original_tokenizer,
  83. ));
  84. success.extend(parse_single_command(
  85. ping::PingCommand::parse,
  86. Command::Ping,
  87. &original_tokenizer,
  88. ));
  89. success.extend(parse_single_command(
  90. nominate::NominateCommand::parse,
  91. Command::Nominate,
  92. &original_tokenizer,
  93. ));
  94. success.extend(parse_single_command(
  95. prioritize::PrioritizeCommand::parse,
  96. Command::Prioritize,
  97. &original_tokenizer,
  98. ));
  99. success.extend(parse_single_command(
  100. second::SecondCommand::parse,
  101. Command::Second,
  102. &original_tokenizer,
  103. ));
  104. success.extend(parse_single_command(
  105. glacier::GlacierCommand::parse,
  106. Command::Glacier,
  107. &original_tokenizer,
  108. ));
  109. if success.len() > 1 {
  110. panic!(
  111. "succeeded parsing {:?} to multiple commands: {:?}",
  112. &self.all[self.parsed..],
  113. success
  114. );
  115. }
  116. if self
  117. .code
  118. .overlaps_code((self.parsed)..(self.parsed + tok.position()))
  119. .is_some()
  120. {
  121. log::info!("command overlaps code; code: {:?}", self.code);
  122. return Command::None;
  123. }
  124. match success.pop() {
  125. Some((mut tok, c)) => {
  126. // if we errored out while parsing the command do not move the input forwards
  127. if c.is_ok() {
  128. self.parsed += tok.position();
  129. }
  130. c
  131. }
  132. None => Command::None,
  133. }
  134. }
  135. }
  136. impl<'a> Command<'a> {
  137. pub fn is_ok(&self) -> bool {
  138. match self {
  139. Command::Relabel(r) => r.is_ok(),
  140. Command::Assign(r) => r.is_ok(),
  141. Command::Ping(r) => r.is_ok(),
  142. Command::Nominate(r) => r.is_ok(),
  143. Command::Prioritize(r) => r.is_ok(),
  144. Command::Second(r) => r.is_ok(),
  145. Command::Glacier(r) => r.is_ok(),
  146. Command::None => true,
  147. }
  148. }
  149. pub fn is_err(&self) -> bool {
  150. !self.is_ok()
  151. }
  152. pub fn is_none(&self) -> bool {
  153. match self {
  154. Command::None => true,
  155. _ => false,
  156. }
  157. }
  158. }
  159. #[test]
  160. fn errors_outside_command_are_fine() {
  161. let input =
  162. "haha\" unterminated quotes @bot modify labels: +bug. Terminating after the command";
  163. let mut input = Input::new(input, "bot");
  164. assert!(input.parse_command().is_ok());
  165. }
  166. #[test]
  167. fn code_1() {
  168. let input = "`@bot modify labels: +bug.`";
  169. let mut input = Input::new(input, "bot");
  170. assert!(input.parse_command().is_none());
  171. }
  172. #[test]
  173. fn code_2() {
  174. let input = "```
  175. @bot modify labels: +bug.
  176. ```";
  177. let mut input = Input::new(input, "bot");
  178. assert!(input.parse_command().is_none());
  179. }
  180. #[test]
  181. fn move_input_along() {
  182. let input = "@bot modify labels: +bug. Afterwards, delete the world.";
  183. let mut input = Input::new(input, "bot");
  184. let parsed = input.parse_command();
  185. assert!(parsed.is_ok());
  186. assert_eq!(&input.all[input.parsed..], " Afterwards, delete the world.");
  187. }
  188. #[test]
  189. fn move_input_along_1() {
  190. let input = "@bot modify labels\": +bug. Afterwards, delete the world.";
  191. let mut input = Input::new(input, "bot");
  192. assert!(input.parse_command().is_err());
  193. // don't move input along if parsing the command fails
  194. assert_eq!(input.parsed, 0);
  195. }