feat: add --submit <part> option to cargo solve (#25)

* remove `--year` flag in favor of `.config/config.toml`
* cleanup option handling for `cargo solve`
This commit is contained in:
Felix Spöttel 2023-10-21 14:46:19 +02:00 committed by GitHub
parent 1c8ea27eae
commit d10ec0573e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 145 additions and 29 deletions

View file

@ -3,5 +3,8 @@ scaffold = "run --bin scaffold --quiet --release -- "
download = "run --bin download --quiet --release -- " download = "run --bin download --quiet --release -- "
read = "run --bin read --quiet --release -- " read = "run --bin read --quiet --release -- "
solve = "run --bin" solve = "run --bin solve --quiet --release -- "
all = "run" all = "run"
[env]
AOC_YEAR = "2022"

View file

@ -17,6 +17,7 @@ This template supports all major OS (macOS, Linux, Windows).
1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github. 1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github.
2. Click [Use this template](https://github.com/fspoettel/advent-of-code-rust/generate) and create your repository. 2. Click [Use this template](https://github.com/fspoettel/advent-of-code-rust/generate) and create your repository.
3. Clone your repository to your computer. 3. Clone your repository to your computer.
4. If you are solving a previous year's aoc and want to use the `aoc-cli` integration, change the `AOC_YEAR` variable in `.cargo/config.toml` to reflect that.
### Setup rust 💻 ### Setup rust 💻
@ -71,8 +72,6 @@ cargo download <day>
# 🎄 Successfully wrote puzzle to "src/puzzles/01.md". # 🎄 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 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). 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 ### Run solutions for a day
@ -96,6 +95,13 @@ cargo solve <day>
Displayed _timings_ show the raw execution time of your solution without overhead (e.g. file reads). Displayed _timings_ show the raw execution time of your solution without overhead (e.g. file reads).
#### Submitting solutions
> **Note**
> This requires [installing the aoc-cli crate](#download-puzzle-inputs-via-aoc-cli).
In order to submit part of a solution for checking, append the `--submit <part>` option to the `solve` command.
### Run all solutions ### Run all solutions
```sh ```sh
@ -140,6 +146,7 @@ cargo fmt
```sh ```sh
cargo clippy cargo clippy
``` ```
## Optional template features
### Read puzzle description in terminal ### Read puzzle description in terminal
@ -156,13 +163,9 @@ cargo read <day>
# ...the input... # ...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 ### Download puzzle inputs via aoc-cli
1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.7.0` 1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.12.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. 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--description-for-a-day). Once installed, you can use the [download command](#download-input--description-for-a-day).

View file

@ -7,14 +7,12 @@ use std::process;
struct Args { struct Args {
day: u8, day: u8,
year: Option<u16>,
} }
fn parse_args() -> Result<Args, pico_args::Error> { fn parse_args() -> Result<Args, pico_args::Error> {
let mut args = pico_args::Arguments::from_env(); let mut args = pico_args::Arguments::from_env();
Ok(Args { Ok(Args {
day: args.free_from_str()?, day: args.free_from_str()?,
year: args.opt_value_from_str(["-y", "--year"])?,
}) })
} }
@ -32,7 +30,7 @@ fn main() {
process::exit(1); process::exit(1);
} }
match aoc_cli::download(args.day, args.year) { match aoc_cli::download(args.day) {
Ok(cmd_output) => { Ok(cmd_output) => {
if !cmd_output.status.success() { if !cmd_output.status.success() {
process::exit(1); process::exit(1);

View file

@ -7,14 +7,12 @@ use std::process;
struct Args { struct Args {
day: u8, day: u8,
year: Option<u16>,
} }
fn parse_args() -> Result<Args, pico_args::Error> { fn parse_args() -> Result<Args, pico_args::Error> {
let mut args = pico_args::Arguments::from_env(); let mut args = pico_args::Arguments::from_env();
Ok(Args { Ok(Args {
day: args.free_from_str()?, day: args.free_from_str()?,
year: args.opt_value_from_str(["-y", "--year"])?,
}) })
} }
@ -32,7 +30,7 @@ fn main() {
process::exit(1); process::exit(1);
} }
match aoc_cli::read(args.day, args.year) { match aoc_cli::read(args.day) {
Ok(cmd_output) => { Ok(cmd_output) => {
if !cmd_output.status.success() { if !cmd_output.status.success() {
process::exit(1); process::exit(1);

View file

@ -8,7 +8,7 @@ use std::{
process, process,
}; };
const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> Option<u32> { const MODULE_TEMPLATE: &str = r#"pub fn part_one(input: &str) -> Option<u32> {
None None
} }
@ -18,8 +18,8 @@ pub fn part_two(input: &str) -> Option<u32> {
fn main() { fn main() {
let input = &advent_of_code::read_file("inputs", DAY); let input = &advent_of_code::read_file("inputs", DAY);
advent_of_code::solve!(1, part_one, input); advent_of_code::solve!(DAY, 1, part_one, input);
advent_of_code::solve!(2, part_two, input); advent_of_code::solve!(DAY, 2, part_two, input);
} }
#[cfg(test)] #[cfg(test)]
@ -38,7 +38,7 @@ mod tests {
assert_eq!(part_two(&input), None); assert_eq!(part_two(&input), None);
} }
} }
"###; "#;
fn parse_args() -> Result<u8, pico_args::Error> { fn parse_args() -> Result<u8, pico_args::Error> {
let mut args = pico_args::Arguments::from_env(); let mut args = pico_args::Arguments::from_env();

58
src/bin/solve.rs Normal file
View file

@ -0,0 +1,58 @@
/*
* This file contains template code.
* There is no need to edit this file unless you want to change template functionality.
*/
use std::process::{self, Command, Stdio};
struct Args {
day: u8,
release: bool,
submit: Option<u8>,
}
fn parse_args() -> Result<Args, pico_args::Error> {
let mut args = pico_args::Arguments::from_env();
Ok(Args {
day: args.free_from_str()?,
release: args.contains("--release"),
submit: args.opt_value_from_str("--submit")?,
})
}
fn run_solution(day: u8, release: bool, submit_part: Option<u8>) -> Result<(), std::io::Error> {
let day_padded = format!("{:02}", day);
let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day_padded];
if release {
cmd_args.push("--release".to_string());
}
if let Some(submit_part) = submit_part {
cmd_args.push("--".to_string());
cmd_args.push("--submit".to_string());
cmd_args.push(submit_part.to_string())
}
let mut cmd = Command::new("cargo")
.args(&cmd_args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
cmd.wait()?;
Ok(())
}
fn main() {
let args = match parse_args() {
Ok(args) => args,
Err(e) => {
eprintln!("Failed to process arguments: {}", e);
process::exit(1);
}
};
run_solution(args.day, args.release, args.submit).unwrap();
}

View file

@ -14,10 +14,42 @@ pub const ANSI_RESET: &str = "\x1b[0m";
#[macro_export] #[macro_export]
macro_rules! solve { macro_rules! solve {
($part:expr, $solver:ident, $input:expr) => {{ ($day:expr, $part:expr, $solver:ident, $input:expr) => {{
use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET}; use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET, aoc_cli};
use std::fmt::Display; use std::fmt::Display;
use std::time::Instant; use std::time::Instant;
use std::env;
use std::process;
fn submit_if_requested<T: Display>(result: T) {
let args: Vec<String> = env::args().collect();
if args.contains(&"--submit".into()) {
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);
}
if args.len() < 3 {
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
process::exit(1);
}
let part_index = args.iter().position(|x| x == "--submit").unwrap() + 1;
let part_submit = match args[part_index].parse::<u8>() {
Ok(x) => x,
Err(_) => {
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
process::exit(1);
}
};
if part_submit == $part {
println!("Submitting puzzle answer for part {}...", $part);
aoc_cli::submit($day, $part, result).unwrap();
}
}
}
fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str) { fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str) {
let timer = Instant::now(); let timer = Instant::now();
@ -29,6 +61,7 @@ macro_rules! solve {
"{} {}(elapsed: {:.2?}){}", "{} {}(elapsed: {:.2?}){}",
result, ANSI_ITALIC, elapsed, ANSI_RESET result, ANSI_ITALIC, elapsed, ANSI_RESET
); );
submit_if_requested(result);
} }
None => { None => {
println!("not solved.") println!("not solved.")
@ -127,10 +160,10 @@ mod tests {
pub mod aoc_cli { pub mod aoc_cli {
use std::{ use std::{
fmt::Display, fmt::Display,
fs::create_dir_all,
process::{Command, Output, Stdio}, process::{Command, Output, Stdio},
}; };
#[derive(Debug)]
pub enum AocCliError { pub enum AocCliError {
CommandNotFound, CommandNotFound,
CommandNotCallable, CommandNotCallable,
@ -159,17 +192,26 @@ pub mod aoc_cli {
Ok(()) Ok(())
} }
pub fn read(day: u8, year: Option<u16>) -> Result<Output, AocCliError> { pub fn read(day: u8) -> Result<Output, AocCliError> {
// TODO: output local puzzle if present. // TODO: output local puzzle if present.
let args = build_args("read", &[], day, year); let puzzle_path = get_puzzle_path(day);
let args = build_args(
"read",
&[
"--description-only".into(),
"--puzzle-file".into(),
puzzle_path,
],
day,
);
call_aoc_cli(&args) call_aoc_cli(&args)
} }
pub fn download(day: u8, year: Option<u16>) -> Result<Output, AocCliError> { pub fn download(day: u8) -> Result<Output, AocCliError> {
let input_path = get_input_path(day); let input_path = get_input_path(day);
let puzzle_path = get_puzzle_path(day); let puzzle_path = get_puzzle_path(day);
create_dir_all("src/puzzles").map_err(|_| AocCliError::IoError)?;
let args = build_args( let args = build_args(
"download", "download",
@ -181,7 +223,6 @@ pub mod aoc_cli {
puzzle_path.to_string(), puzzle_path.to_string(),
], ],
day, day,
year,
); );
let output = call_aoc_cli(&args)?; let output = call_aoc_cli(&args)?;
@ -196,6 +237,14 @@ pub mod aoc_cli {
} }
} }
pub fn submit<T: Display>(day: u8, part: u8, result: T) -> Result<Output, AocCliError> {
// workaround: the argument order is inverted for submit.
let mut args = build_args("submit", &[], day);
args.push(part.to_string());
args.push(result.to_string());
call_aoc_cli(&args)
}
fn get_input_path(day: u8) -> String { fn get_input_path(day: u8) -> String {
let day_padded = format!("{day:02}"); let day_padded = format!("{day:02}");
format!("src/inputs/{day_padded}.txt") format!("src/inputs/{day_padded}.txt")
@ -206,10 +255,17 @@ pub mod aoc_cli {
format!("src/puzzles/{day_padded}.md") format!("src/puzzles/{day_padded}.md")
} }
fn build_args(command: &str, args: &[String], day: u8, year: Option<u16>) -> Vec<String> { fn get_year() -> Option<u16> {
match std::env::var("AOC_YEAR") {
Ok(x) => x.parse().ok().or(None),
Err(_) => None,
}
}
fn build_args(command: &str, args: &[String], day: u8) -> Vec<String> {
let mut cmd_args = args.to_vec(); let mut cmd_args = args.to_vec();
if let Some(year) = year { if let Some(year) = get_year() {
cmd_args.push("--year".into()); cmd_args.push("--year".into());
cmd_args.push(year.to_string()); cmd_args.push(year.to_string());
} }