nominate.rs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. //! The beta nomination command parser.
  2. //!
  3. //! The grammar is as follows:
  4. //!
  5. //! ```text
  6. //! Command:
  7. //! `@bot beta-nominate <team>`.
  8. //! `@bot nominate <team>`.
  9. //! `@bot beta-accept`.
  10. //! `@bot beta-approve`.
  11. //! ```
  12. //!
  13. //! This constrains to just one team; users should issue the command multiple
  14. //! times if they want to nominate for more than one team. This is to encourage
  15. //! descriptions of what to do targeted at each team, rather than a general
  16. //! summary.
  17. use crate::error::Error;
  18. use crate::token::{Token, Tokenizer};
  19. use std::fmt;
  20. #[derive(PartialEq, Eq, Debug)]
  21. pub struct NominateCommand {
  22. pub team: String,
  23. pub style: Style,
  24. }
  25. #[derive(Debug, PartialEq, Eq, Copy, Clone)]
  26. pub enum Style {
  27. Beta,
  28. BetaApprove,
  29. Decision,
  30. }
  31. #[derive(PartialEq, Eq, Debug)]
  32. pub enum ParseError {
  33. ExpectedEnd,
  34. NoTeam,
  35. }
  36. impl std::error::Error for ParseError {}
  37. impl fmt::Display for ParseError {
  38. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  39. match self {
  40. ParseError::ExpectedEnd => write!(f, "expected end of command"),
  41. ParseError::NoTeam => write!(f, "no team specified"),
  42. }
  43. }
  44. }
  45. impl NominateCommand {
  46. pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
  47. let mut toks = input.clone();
  48. let style = match toks.peek_token()? {
  49. Some(Token::Word("beta-nominate")) => Style::Beta,
  50. Some(Token::Word("nominate")) => Style::Decision,
  51. Some(Token::Word("beta-accept")) => Style::BetaApprove,
  52. Some(Token::Word("beta-approve")) => Style::BetaApprove,
  53. None | Some(_) => return Ok(None),
  54. };
  55. toks.next_token()?;
  56. let team = if style != Style::BetaApprove {
  57. if let Some(Token::Word(team)) = toks.next_token()? {
  58. team.to_owned()
  59. } else {
  60. return Err(toks.error(ParseError::NoTeam));
  61. }
  62. } else {
  63. String::new()
  64. };
  65. if let Some(Token::Dot) | Some(Token::EndOfLine) = toks.peek_token()? {
  66. toks.next_token()?;
  67. *input = toks;
  68. return Ok(Some(NominateCommand { team, style }));
  69. } else {
  70. return Err(toks.error(ParseError::ExpectedEnd));
  71. }
  72. }
  73. }
  74. #[cfg(test)]
  75. fn parse<'a>(input: &'a str) -> Result<Option<NominateCommand>, Error<'a>> {
  76. let mut toks = Tokenizer::new(input);
  77. Ok(NominateCommand::parse(&mut toks)?)
  78. }
  79. #[test]
  80. fn test_1() {
  81. assert_eq!(
  82. parse("nominate compiler."),
  83. Ok(Some(NominateCommand {
  84. team: "compiler".into(),
  85. style: Style::Decision,
  86. }))
  87. );
  88. }
  89. #[test]
  90. fn test_2() {
  91. assert_eq!(
  92. parse("beta-nominate compiler."),
  93. Ok(Some(NominateCommand {
  94. team: "compiler".into(),
  95. style: Style::Beta,
  96. }))
  97. );
  98. }
  99. #[test]
  100. fn test_3() {
  101. use std::error::Error;
  102. assert_eq!(
  103. parse("nominate foo foo")
  104. .unwrap_err()
  105. .source()
  106. .unwrap()
  107. .downcast_ref(),
  108. Some(&ParseError::ExpectedEnd),
  109. );
  110. }
  111. #[test]
  112. fn test_4() {
  113. use std::error::Error;
  114. assert_eq!(
  115. parse("nominate")
  116. .unwrap_err()
  117. .source()
  118. .unwrap()
  119. .downcast_ref(),
  120. Some(&ParseError::NoTeam),
  121. );
  122. }