From 363c2e105b760c8bddca7122bbcee14127b2f4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sp=C3=B6ttel?= <1682504+fspoettel@users.noreply.github.com> Date: Mon, 5 Dec 2022 23:09:38 +0100 Subject: [PATCH 1/3] feat: download puzzle descriptions * add support for downloading puzzle descriptions in `cargo download` * add `cargo read` command to read puzzles in terminal * extract `aoc_cli` module * use `aoc-cli`'s new overwrite option to remove tmp file logic --- .cargo/config | 5 ++- README.md | 35 +++++++++++---- src/bin/download.rs | 77 ++++----------------------------- src/bin/read.rs | 46 ++++++++++++++++++++ src/lib.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 78 deletions(-) create mode 100644 src/bin/read.rs diff --git a/.cargo/config b/.cargo/config index 6731f8f..03ad300 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,6 +1,7 @@ [alias] -scaffold = "run --bin scaffold -- " -download = "run --bin download -- " +scaffold = "run --bin scaffold --quiet --release -- " +download = "run --bin download --quiet --release -- " +read = "run --bin read --quiet --release -- " solve = "run --bin" all = "run" diff --git a/README.md b/README.md index cf1b392..41580dd 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Every [solution](https://github.com/fspoettel/advent-of-code-rust/blob/main/src/ When editing a solution, `rust-analyzer` will display buttons for running / debugging unit tests above the unit test blocks. -### Download input for a day +### Download input & description for a day > **Note** > This command requires [installing the aoc-cli crate](#download-puzzle-inputs-via-aoc-cli). @@ -60,18 +60,20 @@ When editing a solution, `rust-analyzer` will display buttons for running / debu cargo download # output: -# Downloading input with aoc-cli... -# Loaded session cookie from "/home/felix/.adventofcode.session". -# Downloading input for day 1, 2021... -# Saving puzzle input to "/tmp/tmp.MBdcAdL9Iw/input"... +# Loaded session cookie from "/Users//.adventofcode.session". +# Fetching puzzle for day 1, 2022... +# Saving puzzle description to "src/puzzles/01.md"... +# Downloading input for day 1, 2022... +# Saving puzzle input to "src/inputs/01.txt"... # Done! # --- -# 🎄 Successfully wrote input to "src/inputs/01.txt"! +# 🎄 Successfully wrote input to "src/inputs/01.txt". +# 🎄 Successfully wrote puzzle to "src/puzzles/01.md". ``` To download inputs for previous years, append the `--year/-y` flag. _(example: `cargo download 1 --year 2020`)_ -Puzzle inputs are not checked into git. [Reasoning](https://old.reddit.com/r/adventofcode/comments/k99rod/sharing_input_data_were_we_requested_not_to/gf2ukkf/?context=3). +Puzzle descriptions are stored in `src/puzzles` as markdown files. Puzzle inputs are not checked into git. [Reasoning](https://old.reddit.com/r/adventofcode/comments/k99rod/sharing_input_data_were_we_requested_not_to/gf2ukkf/?context=3). ### Run solutions for a day @@ -139,11 +141,28 @@ cargo fmt cargo clippy ``` +### Read puzzle description in terminal + +> **Note** +> This command requires [installing the aoc-cli crate](#download-puzzle-inputs-via-aoc-cli). + +```sh +# example: `cargo read 1` +cargo read + +# output: +# Loaded session cookie from "/Users//.adventofcode.session". +# Fetching puzzle for day 1, 2022... +# ...the input... +``` + +To read inputs for previous years, append the `--year/-y` flag. _(example: `cargo read 1 --year 2020`)_ + ## Optional template features ### Download puzzle inputs via aoc-cli -1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.5.0`. +1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.6 2. Create an `.adventofcode.session` file in your home directory and paste your session cookie[^1] into it. To get this, press F12 anywhere on the Advent of Code website to open your browser developer tools. Look in your Cookies under the Application or Storage tab, and copy out the `session` cookie value. Once installed, you can use the [download command](#download-input-for-a-day). diff --git a/src/bin/download.rs b/src/bin/download.rs index edb567d..6632dc6 100644 --- a/src/bin/download.rs +++ b/src/bin/download.rs @@ -2,14 +2,12 @@ * This file contains template code. * There is no need to edit this file unless you want to change template functionality. */ -use std::io::Write; -use std::path::PathBuf; -use std::{env::temp_dir, io, process::Command}; -use std::{fs, process}; +use advent_of_code::aoc_cli; +use std::process; struct Args { day: u8, - year: Option, + year: Option, } fn parse_args() -> Result { @@ -20,86 +18,29 @@ fn parse_args() -> Result { }) } -fn remove_file(path: &PathBuf) { - #[allow(unused_must_use)] - { - fs::remove_file(path); - } -} - -fn exit_with_status(status: i32, path: &PathBuf) -> ! { - remove_file(path); - process::exit(status); -} - fn main() { - // acquire a temp file path to write aoc-cli output to. - // aoc-cli expects this file not to be present - delete just in case. - let mut tmp_file_path = temp_dir(); - tmp_file_path.push("aoc_input_tmp"); - remove_file(&tmp_file_path); - let args = match parse_args() { Ok(args) => args, Err(e) => { eprintln!("Failed to process arguments: {}", e); - exit_with_status(1, &tmp_file_path); + process::exit(1); } }; - let day_padded = format!("{:02}", args.day); - let input_path = format!("src/inputs/{}.txt", day_padded); - - // check if aoc binary exists and is callable. - if Command::new("aoc").arg("-V").output().is_err() { + if aoc_cli::check().is_err() { eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); - exit_with_status(1, &tmp_file_path); + process::exit(1); } - let mut cmd_args = vec![]; - - if let Some(year) = args.year { - cmd_args.push("--year".into()); - cmd_args.push(year.to_string()); - } - - cmd_args.append(&mut vec![ - "--input-file".into(), - tmp_file_path.to_string_lossy().to_string(), - "--day".into(), - args.day.to_string(), - "download".into(), - ]); - - println!("Downloading input with >aoc {}", cmd_args.join(" ")); - - match Command::new("aoc").args(cmd_args).output() { + match aoc_cli::download(args.day, args.year) { Ok(cmd_output) => { - io::stdout() - .write_all(&cmd_output.stdout) - .expect("could not write cmd stdout to pipe."); - io::stderr() - .write_all(&cmd_output.stderr) - .expect("could not write cmd stderr to pipe."); if !cmd_output.status.success() { - exit_with_status(1, &tmp_file_path); + process::exit(1); } } Err(e) => { eprintln!("failed to spawn aoc-cli: {}", e); - exit_with_status(1, &tmp_file_path); - } - } - - match fs::copy(&tmp_file_path, &input_path) { - Ok(_) => { - println!("---"); - println!("🎄 Successfully wrote input to \"{}\".", &input_path); - exit_with_status(0, &tmp_file_path); - } - Err(e) => { - eprintln!("could not copy downloaded input to input file: {}", e); - exit_with_status(1, &tmp_file_path); + process::exit(1); } } } diff --git a/src/bin/read.rs b/src/bin/read.rs new file mode 100644 index 0000000..d0a4321 --- /dev/null +++ b/src/bin/read.rs @@ -0,0 +1,46 @@ +/* + * This file contains template code. + * There is no need to edit this file unless you want to change template functionality. + */ +use advent_of_code::aoc_cli; +use std::process; + +struct Args { + day: u8, + year: Option, +} + +fn parse_args() -> Result { + let mut args = pico_args::Arguments::from_env(); + Ok(Args { + day: args.free_from_str()?, + year: args.opt_value_from_str(["-y", "--year"])?, + }) +} + +fn main() { + let args = match parse_args() { + Ok(args) => args, + Err(e) => { + eprintln!("Failed to process arguments: {}", e); + process::exit(1); + } + }; + + if aoc_cli::check().is_err() { + eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); + process::exit(1); + } + + match aoc_cli::read(args.day, args.year) { + Ok(cmd_output) => { + if !cmd_output.status.success() { + process::exit(1); + } + } + Err(e) => { + eprintln!("failed to spawn aoc-cli: {}", e); + process::exit(1); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 23acd28..ed58a10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,3 +123,104 @@ mod tests { ); } } + +pub mod aoc_cli { + use std::{ + fmt::Display, + fs::create_dir_all, + process::{Command, Output, Stdio}, + }; + + pub enum AocCliError { + CommandNotFound, + CommandFailed, + IoError, + } + + impl Display for AocCliError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AocCliError::CommandNotFound => write!(f, "CommandNotFound"), + AocCliError::CommandFailed => write!(f, "CommandFailed"), + AocCliError::IoError => write!(f, "IoError"), + } + } + } + + pub fn check() -> Result<(), AocCliError> { + Command::new("aoc") + .arg("-V") + .output() + .map_err(|_| AocCliError::CommandNotFound)?; + Ok(()) + } + + pub fn read(day: u8, year: Option) -> Result { + // TODO: output local puzzle if present. + let args = build_args("read", &[], day, year); + call_aoc_cli(&args) + } + + pub fn download(day: u8, year: Option) -> Result { + let input_path = get_input_path(day); + + let puzzle_path = get_puzzle_path(day); + create_dir_all("src/puzzles").map_err(|_| AocCliError::IoError)?; + + let args = build_args( + "download", + &[ + "--overwrite".into(), + "--input-file".into(), + input_path.to_string(), + "--puzzle-file".into(), + puzzle_path.to_string(), + ], + day, + year, + ); + + let output = call_aoc_cli(&args)?; + println!("---"); + println!("🎄 Successfully wrote input to \"{}\".", &input_path); + println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path); + + Ok(output) + } + + fn get_input_path(day: u8) -> String { + let day_padded = format!("{:02}", day); + format!("src/inputs/{}.txt", day_padded) + } + + fn get_puzzle_path(day: u8) -> String { + let day_padded = format!("{:02}", day); + format!("src/puzzles/{}.md", day_padded) + } + + fn build_args(command: &str, args: &[String], day: u8, year: Option) -> Vec { + let mut cmd_args = args.to_vec(); + + if let Some(year) = year { + cmd_args.push("--year".into()); + cmd_args.push(year.to_string()); + } + + cmd_args.append(&mut vec!["--day".into(), day.to_string(), command.into()]); + + cmd_args + } + + fn call_aoc_cli(args: &[String]) -> Result { + if cfg!(debug_assertions) { + println!("Calling >aoc with: {}", args.join(" ")); + } + + Command::new("aoc") + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .map_err(|_| AocCliError::CommandFailed) + } +} From 50d426a11b3cd0d74e5344383318d876985b69fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sp=C3=B6ttel?= <1682504+fspoettel@users.noreply.github.com> Date: Mon, 5 Dec 2022 23:11:26 +0100 Subject: [PATCH 2/3] docs: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41580dd..0b6e53f 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ To read inputs for previous years, append the `--year/-y` flag. _(example: `carg ### Download puzzle inputs via aoc-cli -1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.6 +1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.6.0 2. Create an `.adventofcode.session` file in your home directory and paste your session cookie[^1] into it. To get this, press F12 anywhere on the Advent of Code website to open your browser developer tools. Look in your Cookies under the Application or Storage tab, and copy out the `session` cookie value. Once installed, you can use the [download command](#download-input-for-a-day). From b9e6f8823d0a714d3f7ff2cb50ba6781236bf6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sp=C3=B6ttel?= <1682504+fspoettel@users.noreply.github.com> Date: Tue, 6 Dec 2022 18:13:49 +0100 Subject: [PATCH 3/3] fix: improve error handling for aoc-cli errors --- README.md | 2 +- src/lib.rs | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0b6e53f..869f57d 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ To read inputs for previous years, append the `--year/-y` flag. _(example: `carg ### Download puzzle inputs via aoc-cli -1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.6.0 +1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.7.0` 2. Create an `.adventofcode.session` file in your home directory and paste your session cookie[^1] into it. To get this, press F12 anywhere on the Advent of Code website to open your browser developer tools. Look in your Cookies under the Application or Storage tab, and copy out the `session` cookie value. Once installed, you can use the [download command](#download-input-for-a-day). diff --git a/src/lib.rs b/src/lib.rs index ed58a10..09a7ae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,16 +133,18 @@ pub mod aoc_cli { pub enum AocCliError { CommandNotFound, - CommandFailed, + CommandNotCallable, + BadExitStatus(Output), IoError, } impl Display for AocCliError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AocCliError::CommandNotFound => write!(f, "CommandNotFound"), - AocCliError::CommandFailed => write!(f, "CommandFailed"), - AocCliError::IoError => write!(f, "IoError"), + AocCliError::CommandNotFound => write!(f, "aoc-cli is not present in environment."), + AocCliError::CommandNotCallable => write!(f, "aoc-cli could not be called."), + AocCliError::BadExitStatus(_) => write!(f, "aoc-cli exited with a non-zero status."), + AocCliError::IoError => write!(f, "could not write output files to file system."), } } } @@ -181,11 +183,16 @@ pub mod aoc_cli { ); let output = call_aoc_cli(&args)?; - println!("---"); - println!("🎄 Successfully wrote input to \"{}\".", &input_path); - println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path); - Ok(output) + if output.status.success() { + println!("---"); + println!("🎄 Successfully wrote input to \"{}\".", &input_path); + println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path); + Ok(output) + } else { + Err(AocCliError::BadExitStatus(output)) + } + } fn get_input_path(day: u8) -> String { @@ -221,6 +228,6 @@ pub mod aoc_cli { .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .output() - .map_err(|_| AocCliError::CommandFailed) + .map_err(|_| AocCliError::CommandNotCallable) } }