command.rs 4.7 KB

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