exec.rs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. use std::{fs, io, process};
  2. use std::error::Error;
  3. use std::io::{Read, stderr, stdin, stdout, Write};
  4. use std::str::FromStr;
  5. use crate::aoc_day::{AocDay, Part};
  6. use crate::command::{Command, CommandType, format_duration, Terminal};
  7. use crate::web::WebContext;
  8. pub struct Runner<> {
  9. pub day: AocDay,
  10. pub part: Part,
  11. pub terminal: Terminal,
  12. pub web_context: WebContext,
  13. }
  14. // TODO replace TODO with issues
  15. impl Runner {
  16. pub fn new(day: AocDay, part: Part, web_context: WebContext) -> Self {
  17. Self {
  18. day,
  19. part,
  20. web_context,
  21. terminal: Terminal::new(),
  22. }
  23. }
  24. pub fn close(self) -> io::Result<()> {
  25. self.web_context.close()
  26. }
  27. pub fn make_missing_files_if_open(&mut self) -> Result<(), Box<dyn Error>> {
  28. let Self {
  29. ref day,
  30. ref mut terminal,
  31. ref mut web_context,
  32. ..
  33. } = self;
  34. if day.is_open() {
  35. if !day.has_source_code() {
  36. let mut child = process::Command::new("gucci")
  37. .args(&[
  38. "-s",
  39. format!("day=day{}", day.day).as_str(),
  40. "templates/day.rs.tpl"
  41. ])
  42. .stdout(process::Stdio::from(fs::File::create(day.source_code_path())?))
  43. .stderr(process::Stdio::inherit())
  44. .spawn()?;
  45. if !child.wait()?.success() {
  46. return Err("Error while running gucci".into());
  47. }
  48. process::Command::new("git").args(&["add", day.source_code_path().as_str()]).status()?;
  49. }
  50. if !day.has_input_file() {
  51. let answer = terminal.yes_no(Some(true), Some("Fetch input file from Advent of Code website? "))?;
  52. let res: Result<(), Box<dyn Error>> = match answer {
  53. Some(true) => {
  54. println!("Fetching...");
  55. web_context.curl_request_to_named_file(
  56. day.input_file_url().as_str(),
  57. day.input_file_path(),
  58. )?;
  59. Ok(())
  60. }
  61. Some(false) | None => Ok(())
  62. };
  63. res?;
  64. }
  65. if !day.has_test_input_file() {
  66. fs::File::create(day.test_input_file_path())?;
  67. process::Command::new("git").args(&["add", day.test_input_file_path().as_str()]).status()?;
  68. }
  69. }
  70. return Ok(());
  71. }
  72. pub fn test(&self) -> Result<bool, Box<dyn Error>> {
  73. let output = process::Command::new("cargo")
  74. .args(&[
  75. "test",
  76. "-q",
  77. format!("test_day{}", self.day.day).as_str(), // TODO specify which part to test
  78. "--bin",
  79. format!("day{}", self.day.day).as_str(),
  80. "--color=always",
  81. ])
  82. .output()?;
  83. eprintln!("{}", String::from_utf8(output.stderr)?);
  84. println!("{}", String::from_utf8(output.stdout)?);
  85. return Ok(output.status.success());
  86. }
  87. pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
  88. let test_succeeded = self.test()?;
  89. if test_succeeded || self.terminal.yes_no(Some(false), Some("Test failed, run anyways? "))?.unwrap_or(false) {
  90. let output_file = format!("outputs/day{}-{}.txt", self.day.day, self.part.as_str());
  91. let output = process::Command::new("cargo")
  92. .args(&[
  93. "run",
  94. "-q",
  95. "--bin", format!("day{}", self.day.day).as_str(),
  96. "--",
  97. "--part", self.part.as_digit().to_string().as_str(),
  98. "--output-file", self.day.output_file_path(&self.part).as_str(),
  99. "--color=always",
  100. ])
  101. .output()?; // TODO Spawn and do fancy terminal things
  102. eprintln!("{}", String::from_utf8(output.stderr)?);
  103. println!("{}", String::from_utf8(output.stdout)?);
  104. if output.status.success() {
  105. let mut output_file = fs::File::open(output_file)?;
  106. let mut output = String::new();
  107. output_file.read_to_string(&mut output)?;
  108. println!("For day {} part {} got result {}",
  109. self.terminal.strings.emph_style.paint(self.day.day.to_string()),
  110. self.terminal.strings.emph_style.paint(&self.part.to_string()),
  111. self.terminal.strings.emph_style.paint(&output)
  112. );
  113. // TODO Query puzzle status from AOC before submitting
  114. if self.terminal.yes_no(Some(true), Some("Submit to Advent of Code? "))?
  115. .unwrap_or(false) {
  116. self.do_submit(output)?;
  117. }
  118. }
  119. }
  120. return Ok(());
  121. }
  122. fn submit(&mut self) -> Result<(), Box<dyn Error>> {
  123. let output_age = self.day.output_file_age(&self.part)?;
  124. if let Some(output_age) = output_age {
  125. let mut output = String::new();
  126. fs::File::open(self.day.output_file_path(&self.part))?.read_to_string(&mut output)?;
  127. let answer = self.terminal.options(Some("r"), &["Rerun", "Submit", "Cancel"],
  128. Some(format!("Found output {} with age {} ",
  129. self.terminal.strings.emph_style.paint(&output),
  130. format_duration(output_age)).as_str()))?;
  131. println!("{:?}", &answer);
  132. if let Some(s) = answer {
  133. match s.as_str() {
  134. "r" => self.run()?,
  135. "s" => self.do_submit(output)?,
  136. _ => (),
  137. }
  138. }
  139. } else {
  140. self.run()?
  141. }
  142. Ok(())
  143. }
  144. fn do_submit(&mut self, submission: String) -> Result<(), Box<dyn Error>> {
  145. // TODO get tls session from curl and wrap around lynx, OR parse output file into text or minimal html
  146. let data = format!("level={}&answer={}", self.part.as_digit(), submission);
  147. self.web_context.curl_post_to_lynx(self.day.submit_url().as_str(), data.as_bytes())
  148. }
  149. pub fn reset_part(&mut self) {
  150. self.part = Part::ONE
  151. }
  152. pub fn switch_part(&mut self) {
  153. match self.part {
  154. Part::ONE => self.part = Part::TWO,
  155. Part::TWO => self.part = Part::ONE,
  156. }
  157. }
  158. pub fn change_day(&mut self, args: &[&str]) -> Result<(), String> {
  159. let day = args.first()
  160. .ok_or("Expected 1 argument got 0")?
  161. .parse::<u8>()
  162. .map_err(|e| format!("Invalid argument 1: {}", e))?;
  163. if day <= 25 {
  164. self.day.day = day;
  165. Ok(())
  166. } else {
  167. Err(format!("AoC is played on 1-25 December, {} December is not a valid day.", day))
  168. }
  169. }
  170. pub fn change_year(&mut self, args: &[&str]) -> Result<(), String> {
  171. let year = args.first()
  172. .ok_or("Expected 1 argument got 0")?
  173. .parse::<u16>()
  174. .map_err(|e| format!("Invalid argument 1: {}", e))?;
  175. if year >= 2015 {
  176. self.day.year = year;
  177. Ok(())
  178. } else {
  179. Err(format!("AoC started in 2015, {} is not a valid year.", year))
  180. }
  181. }
  182. pub fn print_day(&self) {
  183. let ordinal_suffix = if self.day.day >= 10 && self.day.day <= 20 {
  184. "th"
  185. } else {
  186. match self.day.day % 10 {
  187. 1 => "st",
  188. 2 => "nd",
  189. 3 => "rd",
  190. _ => "th"
  191. }
  192. };
  193. println!("Current Advent of Code Day is the {}{} of {}", self.day.day, ordinal_suffix, self.day.year); // TODO make impl Display for AOCDay
  194. }
  195. pub fn start_runner(&mut self) -> Result<(), Box<dyn Error>> {
  196. self.print_day();
  197. self.terminal.help();
  198. self.make_missing_files_if_open()?;
  199. loop {
  200. stderr().flush()?;
  201. print!("> ");
  202. stdout().flush()?;
  203. let mut line = String::new();
  204. stdin().read_line(&mut line)?;
  205. let args: Vec<&str> = line.trim().split_whitespace().collect();
  206. match args.first().map(|s| Command::from_str(*s).map_err(|e| (s, e))) {
  207. None => (),
  208. Some(Err((s, _))) => eprintln!("Unknown command \"{}\"", *s),
  209. Some(Ok(command)) => {
  210. let args = &args[1..];
  211. match command.command_type {
  212. CommandType::Test => self.test().map(|_| ()),
  213. CommandType::Run => self.run(),
  214. CommandType::Submit => self.submit(),
  215. CommandType::NextPart => Ok(self.switch_part()),
  216. CommandType::Puzzle => Ok(self.web_context.lynx(self.day.puzzle_url())?),
  217. CommandType::Day => Ok({
  218. self.change_day(args)?;
  219. self.reset_part();
  220. self.print_day();
  221. self.make_missing_files_if_open()?;
  222. }),
  223. CommandType::Year => Ok({
  224. self.change_year(args)?;
  225. self.print_day();
  226. }),
  227. CommandType::Leaderboard => Ok(self.web_context.lynx(self.day.leaderboard_url())?),
  228. CommandType::Help => Ok(self.terminal.help()),
  229. CommandType::Quit => break,
  230. }?;
  231. }
  232. }
  233. }
  234. return Ok(());
  235. }
  236. }