command.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. use core::fmt;
  2. use std::{error, io};
  3. use std::io::{stdin, stdout, Write};
  4. use std::str::FromStr;
  5. use chrono::Duration;
  6. pub enum CommandType {
  7. Test,
  8. Run,
  9. Submit,
  10. NextPart,
  11. Puzzle,
  12. Leaderboard,
  13. Day,
  14. Year,
  15. Help,
  16. Quit,
  17. }
  18. pub struct Command {
  19. pub command_type: CommandType,
  20. pub arguments: Vec<String>,
  21. }
  22. #[derive(Debug, Clone, PartialEq, Eq)]
  23. pub struct CommandParseErr(());
  24. impl fmt::Display for CommandParseErr {
  25. fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
  26. fmt.write_str("invalid CommandType syntax")
  27. }
  28. }
  29. impl error::Error for CommandParseErr {}
  30. impl FromStr for CommandType {
  31. type Err = CommandParseErr;
  32. fn from_str(s: &str) -> Result<Self, Self::Err> {
  33. match s.to_lowercase().replace(" ", "").as_str() {
  34. "test" | "t" => Ok(CommandType::Test),
  35. "run" | "r" => Ok(CommandType::Run),
  36. "submit" | "s" => Ok(CommandType::Submit),
  37. "nextpart" | "n" => Ok(CommandType::NextPart),
  38. "puzzle" | "p" => Ok(CommandType::Puzzle),
  39. "leaderboard" | "l" => Ok(CommandType::Leaderboard),
  40. "changeday" | "day" | "d" => Ok(CommandType::Day),
  41. "changeyear" | "year" | "y" => Ok(CommandType::Year),
  42. "help" | "h" | "?" => Ok(CommandType::Help),
  43. "quit" | "q" => Ok(CommandType::Quit),
  44. _ => Err(CommandParseErr(()))
  45. }
  46. }
  47. }
  48. impl FromStr for Command {
  49. type Err = CommandParseErr;
  50. fn from_str(s: &str) -> Result<Self, Self::Err> {
  51. let mut arguments = s.split_whitespace();
  52. let command_type = arguments.next().ok_or(CommandParseErr(()))?.parse::<CommandType>()?;
  53. let arguments = arguments.map(ToOwned::to_owned).collect();
  54. return Ok(Command {
  55. command_type,
  56. arguments,
  57. });
  58. }
  59. }
  60. pub struct Terminal {
  61. pub strings: TerminalStrings,
  62. }
  63. pub struct TerminalStrings {
  64. pub emph_style: ansi_term::Style,
  65. help: String,
  66. y_n_true: String,
  67. y_n_false: String,
  68. y_n_none: String,
  69. }
  70. impl Terminal {
  71. pub fn new() -> Self {
  72. Terminal {
  73. strings: TerminalStrings::new(),
  74. }
  75. }
  76. pub fn yes_no(&self, default: Option<bool>, question: Option<&str>) -> io::Result<Option<bool>> {
  77. print!("{}{}> ", question.unwrap_or(""), self.strings.yes_no_text(default));
  78. stdout().flush()?;
  79. return Self::read_yes_no(default);
  80. }
  81. pub fn read_yes_no(default: Option<bool>) -> io::Result<Option<bool>> {
  82. let mut input = String::new();
  83. if stdin().read_line(&mut input)? == 0 {
  84. return Ok(None);
  85. }
  86. return Ok(
  87. match input.as_str().strip_suffix("\n").unwrap() {
  88. "y" | "Y" => Some(true),
  89. "n" | "N" => Some(false),
  90. _ => default
  91. }
  92. );
  93. }
  94. pub fn options(&self, default: Option<&str>, options: &[&str], question: Option<&str>) -> io::Result<Option<String>> {
  95. let mut s = String::new();
  96. let options: &[String] = &options.iter()
  97. .map(|o| {
  98. let (i, c) = (*o).char_indices().next().unwrap();
  99. let (emph_c, rest) = (self.strings.emph_style.paint(c.to_string().to_uppercase()).to_string(), &o[(i+1)..]);
  100. s.push_str(format!("{}{}/", emph_c, rest).as_str());
  101. return c.to_lowercase().to_string();
  102. })
  103. .collect::<Vec<String>>()[..];
  104. print!("{}({})> ",
  105. question.unwrap_or(""),
  106. s.trim_end_matches("/"),
  107. );
  108. stdout().flush()?;
  109. return Self::read_options(default, options);
  110. }
  111. pub fn read_options(default: Option<&str>, options: &[String]) -> io::Result<Option<String>> {
  112. let mut input = String::new();
  113. if stdin().read_line(&mut input)? == 0 {
  114. return Ok(None);
  115. }
  116. let input = input.to_lowercase();
  117. let mut chars = input.trim_end().chars();
  118. return Ok(chars.next().map(|c| c.to_lowercase().to_string()).filter(|c| options.contains(&c)).or(default.map(|d| d.to_owned())));
  119. }
  120. pub fn help(&self) {
  121. println!("{}", self.strings.help)
  122. }
  123. }
  124. impl TerminalStrings {
  125. fn new() -> Self {
  126. let emph_style = ansi_term::Style::new().bold().underline();
  127. TerminalStrings {
  128. help: format!("Choose action: {}est, {}un, {}ubmit, {}ext Part, open {}uzzle, open {}eaderboard, change {}ay, change {}ear, {}elp, {}uit",
  129. emph_style.paint("T"),
  130. emph_style.paint("R"),
  131. emph_style.paint("S"),
  132. emph_style.paint("N"),
  133. emph_style.paint("P"),
  134. emph_style.paint("L"),
  135. emph_style.paint("D"),
  136. emph_style.paint("Y"),
  137. emph_style.paint("H"),
  138. emph_style.paint("Q")),
  139. y_n_true: format!("{}/{} ", emph_style.paint("Y"), emph_style.paint("n")),
  140. y_n_false: format!("{}/{} ", emph_style.paint("y"), emph_style.paint("N")),
  141. y_n_none: format!("{}/{} ", emph_style.paint("y"), emph_style.paint("n")),
  142. emph_style,
  143. }
  144. }
  145. pub fn yes_no_text(&self, default: Option<bool>) -> &String {
  146. match default {
  147. Some(true) => &self.y_n_true,
  148. Some(false) => &self.y_n_false,
  149. None => &self.y_n_none,
  150. }
  151. }
  152. }
  153. pub fn format_duration(duration: Duration) -> String {
  154. if duration.num_weeks() != 0 {
  155. return format!("{} weeks", duration.num_weeks());
  156. }
  157. if duration.num_days() != 0 {
  158. return format!("{} days", duration.num_days());
  159. }
  160. if duration.num_hours() != 0 {
  161. return format!("{} hours", duration.num_hours());
  162. }
  163. if duration.num_minutes() != 0 {
  164. return format!("{} minutes", duration.num_minutes());
  165. }
  166. if duration.num_seconds() != 0 {
  167. return format!("{} seconds", duration.num_seconds());
  168. }
  169. return "now".to_owned();
  170. }