123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- use std::{fs, io, process};
- use std::error::Error;
- use std::io::{Read, stderr, stdin, stdout, Write};
- use std::str::FromStr;
- use crate::aoc_day::{AocDay, Part};
- use crate::command::{Command, CommandType, format_duration, Terminal};
- use crate::web::WebContext;
- pub struct Runner<> {
- pub day: AocDay,
- pub part: Part,
- pub terminal: Terminal,
- pub web_context: WebContext,
- }
- // TODO replace TODO with issues
- impl Runner {
- pub fn new(day: AocDay, part: Part, web_context: WebContext) -> Self {
- Self {
- day,
- part,
- web_context,
- terminal: Terminal::new(),
- }
- }
- pub fn close(self) -> io::Result<()> {
- self.web_context.close()
- }
- pub fn make_missing_files_if_open(&mut self) -> Result<(), Box<dyn Error>> {
- let Self {
- ref day,
- ref mut terminal,
- ref mut web_context,
- ..
- } = self;
- if day.is_open() {
- if !day.has_source_code() {
- let mut child = process::Command::new("gucci")
- .args(&[
- "-s",
- format!("day=day{}", day.day).as_str(),
- "templates/day.rs.tpl"
- ])
- .stdout(process::Stdio::from(fs::File::create(day.source_code_path())?))
- .stderr(process::Stdio::inherit())
- .spawn()?;
- if !child.wait()?.success() {
- return Err("Error while running gucci".into());
- }
- process::Command::new("git").args(&["add", day.source_code_path().as_str()]).status()?;
- }
- if !day.has_input_file() {
- let answer = terminal.yes_no(Some(true), Some("Fetch input file from Advent of Code website? "))?;
- let res: Result<(), Box<dyn Error>> = match answer {
- Some(true) => {
- println!("Fetching...");
- web_context.curl_request_to_named_file(
- day.input_file_url().as_str(),
- day.input_file_path(),
- )?;
- Ok(())
- }
- Some(false) | None => Ok(())
- };
- res?;
- }
- if !day.has_test_input_file() {
- fs::File::create(day.test_input_file_path())?;
- process::Command::new("git").args(&["add", day.test_input_file_path().as_str()]).status()?;
- }
- }
- return Ok(());
- }
- pub fn test(&self) -> Result<bool, Box<dyn Error>> {
- let output = process::Command::new("cargo")
- .args(&[
- "test",
- "-q",
- format!("test_day{}", self.day.day).as_str(), // TODO specify which part to test
- "--bin",
- format!("day{}", self.day.day).as_str(),
- "--color=always",
- ])
- .output()?;
- eprintln!("{}", String::from_utf8(output.stderr)?);
- println!("{}", String::from_utf8(output.stdout)?);
- return Ok(output.status.success());
- }
- pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
- let test_succeeded = self.test()?;
- if test_succeeded || self.terminal.yes_no(Some(false), Some("Test failed, run anyways? "))?.unwrap_or(false) {
- let output_file = format!("outputs/day{}-{}.txt", self.day.day, self.part.as_str());
- let output = process::Command::new("cargo")
- .args(&[
- "run",
- "-q",
- "--bin", format!("day{}", self.day.day).as_str(),
- "--",
- "--part", self.part.as_digit().to_string().as_str(),
- "--output-file", self.day.output_file_path(&self.part).as_str(),
- "--color=always",
- ])
- .output()?; // TODO Spawn and do fancy terminal things
- eprintln!("{}", String::from_utf8(output.stderr)?);
- println!("{}", String::from_utf8(output.stdout)?);
- if output.status.success() {
- let mut output_file = fs::File::open(output_file)?;
- let mut output = String::new();
- output_file.read_to_string(&mut output)?;
- println!("For day {} part {} got result {}",
- self.terminal.strings.emph_style.paint(self.day.day.to_string()),
- self.terminal.strings.emph_style.paint(&self.part.to_string()),
- self.terminal.strings.emph_style.paint(&output)
- );
- // TODO Query puzzle status from AOC before submitting
- if self.terminal.yes_no(Some(true), Some("Submit to Advent of Code? "))?
- .unwrap_or(false) {
- self.do_submit(output)?;
- }
- }
- }
- return Ok(());
- }
- fn submit(&mut self) -> Result<(), Box<dyn Error>> {
- let output_age = self.day.output_file_age(&self.part)?;
- if let Some(output_age) = output_age {
- let mut output = String::new();
- fs::File::open(self.day.output_file_path(&self.part))?.read_to_string(&mut output)?;
- let answer = self.terminal.options(Some("r"), &["Rerun", "Submit", "Cancel"],
- Some(format!("Found output {} with age {} ",
- self.terminal.strings.emph_style.paint(&output),
- format_duration(output_age)).as_str()))?;
- println!("{:?}", &answer);
- if let Some(s) = answer {
- match s.as_str() {
- "r" => self.run()?,
- "s" => self.do_submit(output)?,
- _ => (),
- }
- }
- } else {
- self.run()?
- }
- Ok(())
- }
- fn do_submit(&mut self, submission: String) -> Result<(), Box<dyn Error>> {
- // TODO get tls session from curl and wrap around lynx, OR parse output file into text or minimal html
- let data = format!("level={}&answer={}", self.part.as_digit(), submission);
- self.web_context.curl_post_to_lynx(self.day.submit_url().as_str(), data.as_bytes())
- }
-
- pub fn reset_part(&mut self) {
- self.part = Part::ONE
- }
- pub fn switch_part(&mut self) {
- match self.part {
- Part::ONE => self.part = Part::TWO,
- Part::TWO => self.part = Part::ONE,
- }
- }
- pub fn change_day(&mut self, args: &[&str]) -> Result<(), String> {
- let day = args.first()
- .ok_or("Expected 1 argument got 0")?
- .parse::<u8>()
- .map_err(|e| format!("Invalid argument 1: {}", e))?;
- if day <= 25 {
- self.day.day = day;
- Ok(())
- } else {
- Err(format!("AoC is played on 1-25 December, {} December is not a valid day.", day))
- }
- }
- pub fn change_year(&mut self, args: &[&str]) -> Result<(), String> {
- let year = args.first()
- .ok_or("Expected 1 argument got 0")?
- .parse::<u16>()
- .map_err(|e| format!("Invalid argument 1: {}", e))?;
- if year >= 2015 {
- self.day.year = year;
- Ok(())
- } else {
- Err(format!("AoC started in 2015, {} is not a valid year.", year))
- }
- }
- pub fn print_day(&self) {
- let ordinal_suffix = if self.day.day >= 10 && self.day.day <= 20 {
- "th"
- } else {
- match self.day.day % 10 {
- 1 => "st",
- 2 => "nd",
- 3 => "rd",
- _ => "th"
- }
- };
- println!("Current Advent of Code Day is the {}{} of {}", self.day.day, ordinal_suffix, self.day.year); // TODO make impl Display for AOCDay
- }
- pub fn start_runner(&mut self) -> Result<(), Box<dyn Error>> {
- self.print_day();
- self.terminal.help();
- self.make_missing_files_if_open()?;
- loop {
- stderr().flush()?;
- print!("> ");
- stdout().flush()?;
- let mut line = String::new();
- stdin().read_line(&mut line)?;
- let args: Vec<&str> = line.trim().split_whitespace().collect();
- match args.first().map(|s| Command::from_str(*s).map_err(|e| (s, e))) {
- None => (),
- Some(Err((s, _))) => eprintln!("Unknown command \"{}\"", *s),
- Some(Ok(command)) => {
- let args = &args[1..];
- match command.command_type {
- CommandType::Test => self.test().map(|_| ()),
- CommandType::Run => self.run(),
- CommandType::Submit => self.submit(),
- CommandType::NextPart => Ok(self.switch_part()),
- CommandType::Puzzle => Ok(self.web_context.lynx(self.day.puzzle_url())?),
- CommandType::Day => Ok({
- self.change_day(args)?;
- self.reset_part();
- self.print_day();
- self.make_missing_files_if_open()?;
- }),
- CommandType::Year => Ok({
- self.change_year(args)?;
- self.print_day();
- }),
- CommandType::Leaderboard => Ok(self.web_context.lynx(self.day.leaderboard_url())?),
- CommandType::Help => Ok(self.terminal.help()),
- CommandType::Quit => break,
- }?;
- }
- }
- }
- return Ok(());
- }
- }
|