Skip to content

Commit

Permalink
feat: add --submit <part> option to cargo solve (#25)
Browse files Browse the repository at this point in the history
* remove `--year` flag in favor of `.config/config.toml`
* cleanup option handling for `cargo solve`
  • Loading branch information
fspoettel authored Oct 21, 2023
1 parent 1c8ea27 commit d10ec05
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 29 deletions.
5 changes: 4 additions & 1 deletion .cargo/config → .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ scaffold = "run --bin scaffold --quiet --release -- "
download = "run --bin download --quiet --release -- "
read = "run --bin read --quiet --release -- "

solve = "run --bin"
solve = "run --bin solve --quiet --release -- "
all = "run"

[env]
AOC_YEAR = "2022"
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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.
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 💻

Expand Down Expand Up @@ -71,8 +72,6 @@ cargo download <day>
# 🎄 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).

### Run solutions for a day
Expand All @@ -96,6 +95,13 @@ cargo solve <day>

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

```sh
Expand Down Expand Up @@ -140,6 +146,7 @@ cargo fmt
```sh
cargo clippy
```
## Optional template features

### Read puzzle description in terminal

Expand All @@ -156,13 +163,9 @@ cargo read <day>
# ...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.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.

Once installed, you can use the [download command](#download-input--description-for-a-day).
Expand Down
4 changes: 1 addition & 3 deletions src/bin/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ use std::process;

struct Args {
day: u8,
year: Option<u16>,
}

fn parse_args() -> Result<Args, pico_args::Error> {
let mut args = pico_args::Arguments::from_env();
Ok(Args {
day: args.free_from_str()?,
year: args.opt_value_from_str(["-y", "--year"])?,
})
}

Expand All @@ -32,7 +30,7 @@ fn main() {
process::exit(1);
}

match aoc_cli::download(args.day, args.year) {
match aoc_cli::download(args.day) {
Ok(cmd_output) => {
if !cmd_output.status.success() {
process::exit(1);
Expand Down
4 changes: 1 addition & 3 deletions src/bin/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ use std::process;

struct Args {
day: u8,
year: Option<u16>,
}

fn parse_args() -> Result<Args, pico_args::Error> {
let mut args = pico_args::Arguments::from_env();
Ok(Args {
day: args.free_from_str()?,
year: args.opt_value_from_str(["-y", "--year"])?,
})
}

Expand All @@ -32,7 +30,7 @@ fn main() {
process::exit(1);
}

match aoc_cli::read(args.day, args.year) {
match aoc_cli::read(args.day) {
Ok(cmd_output) => {
if !cmd_output.status.success() {
process::exit(1);
Expand Down
8 changes: 4 additions & 4 deletions src/bin/scaffold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{
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
}
Expand All @@ -18,8 +18,8 @@ pub fn part_two(input: &str) -> Option<u32> {
fn main() {
let input = &advent_of_code::read_file("inputs", DAY);
advent_of_code::solve!(1, part_one, input);
advent_of_code::solve!(2, part_two, input);
advent_of_code::solve!(DAY, 1, part_one, input);
advent_of_code::solve!(DAY, 2, part_two, input);
}
#[cfg(test)]
Expand All @@ -38,7 +38,7 @@ mod tests {
assert_eq!(part_two(&input), None);
}
}
"###;
"#;

fn parse_args() -> Result<u8, pico_args::Error> {
let mut args = pico_args::Arguments::from_env();
Expand Down
58 changes: 58 additions & 0 deletions src/bin/solve.rs
Original file line number Diff line number Diff line change
@@ -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();
}
78 changes: 67 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,42 @@ pub const ANSI_RESET: &str = "\x1b[0m";

#[macro_export]
macro_rules! solve {
($part:expr, $solver:ident, $input:expr) => {{
use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET};
($day:expr, $part:expr, $solver:ident, $input:expr) => {{
use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET, aoc_cli};
use std::fmt::Display;
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) {
let timer = Instant::now();
Expand All @@ -29,6 +61,7 @@ macro_rules! solve {
"{} {}(elapsed: {:.2?}){}",
result, ANSI_ITALIC, elapsed, ANSI_RESET
);
submit_if_requested(result);
}
None => {
println!("not solved.")
Expand Down Expand Up @@ -127,10 +160,10 @@ mod tests {
pub mod aoc_cli {
use std::{
fmt::Display,
fs::create_dir_all,
process::{Command, Output, Stdio},
};

#[derive(Debug)]
pub enum AocCliError {
CommandNotFound,
CommandNotCallable,
Expand Down Expand Up @@ -159,17 +192,26 @@ pub mod aoc_cli {
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.
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)
}

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 puzzle_path = get_puzzle_path(day);
create_dir_all("src/puzzles").map_err(|_| AocCliError::IoError)?;

let args = build_args(
"download",
Expand All @@ -181,7 +223,6 @@ pub mod aoc_cli {
puzzle_path.to_string(),
],
day,
year,
);

let output = call_aoc_cli(&args)?;
Expand All @@ -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 {
let day_padded = format!("{day:02}");
format!("src/inputs/{day_padded}.txt")
Expand All @@ -206,10 +255,17 @@ pub mod aoc_cli {
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();

if let Some(year) = year {
if let Some(year) = get_year() {
cmd_args.push("--year".into());
cmd_args.push(year.to_string());
}
Expand Down

0 comments on commit d10ec05

Please sign in to comment.