command.rs 5.1 KB

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