Skip to content

Commit d10ec05

Browse files
authored
feat: add --submit <part> option to cargo solve (#25)
* remove `--year` flag in favor of `.config/config.toml` * cleanup option handling for `cargo solve`
1 parent 1c8ea27 commit d10ec05

File tree

7 files changed

+145
-29
lines changed

7 files changed

+145
-29
lines changed

Diff for: .cargo/config renamed to .cargo/config.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ scaffold = "run --bin scaffold --quiet --release -- "
33
download = "run --bin download --quiet --release -- "
44
read = "run --bin read --quiet --release -- "
55

6-
solve = "run --bin"
6+
solve = "run --bin solve --quiet --release -- "
77
all = "run"
8+
9+
[env]
10+
AOC_YEAR = "2022"

Diff for: README.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This template supports all major OS (macOS, Linux, Windows).
1717
1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github.
1818
2. Click [Use this template](https://github.com/fspoettel/advent-of-code-rust/generate) and create your repository.
1919
3. Clone your repository to your computer.
20+
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.
2021

2122
### Setup rust 💻
2223

@@ -71,8 +72,6 @@ cargo download <day>
7172
# 🎄 Successfully wrote puzzle to "src/puzzles/01.md".
7273
```
7374

74-
To download inputs for previous years, append the `--year/-y` flag. _(example: `cargo download 1 --year 2020`)_
75-
7675
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).
7776

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

9796
Displayed _timings_ show the raw execution time of your solution without overhead (e.g. file reads).
9897

98+
#### Submitting solutions
99+
100+
> **Note**
101+
> This requires [installing the aoc-cli crate](#download-puzzle-inputs-via-aoc-cli).
102+
103+
In order to submit part of a solution for checking, append the `--submit <part>` option to the `solve` command.
104+
99105
### Run all solutions
100106

101107
```sh
@@ -140,6 +146,7 @@ cargo fmt
140146
```sh
141147
cargo clippy
142148
```
149+
## Optional template features
143150

144151
### Read puzzle description in terminal
145152

@@ -156,13 +163,9 @@ cargo read <day>
156163
# ...the input...
157164
```
158165

159-
To read inputs for previous years, append the `--year/-y` flag. _(example: `cargo read 1 --year 2020`)_
160-
161-
## Optional template features
162-
163166
### Download puzzle inputs via aoc-cli
164167

165-
1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.7.0`
168+
1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.12.0`
166169
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.
167170

168171
Once installed, you can use the [download command](#download-input--description-for-a-day).

Diff for: src/bin/download.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ use std::process;
77

88
struct Args {
99
day: u8,
10-
year: Option<u16>,
1110
}
1211

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

@@ -32,7 +30,7 @@ fn main() {
3230
process::exit(1);
3331
}
3432

35-
match aoc_cli::download(args.day, args.year) {
33+
match aoc_cli::download(args.day) {
3634
Ok(cmd_output) => {
3735
if !cmd_output.status.success() {
3836
process::exit(1);

Diff for: src/bin/read.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ use std::process;
77

88
struct Args {
99
day: u8,
10-
year: Option<u16>,
1110
}
1211

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

@@ -32,7 +30,7 @@ fn main() {
3230
process::exit(1);
3331
}
3432

35-
match aoc_cli::read(args.day, args.year) {
33+
match aoc_cli::read(args.day) {
3634
Ok(cmd_output) => {
3735
if !cmd_output.status.success() {
3836
process::exit(1);

Diff for: src/bin/scaffold.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
process,
99
};
1010

11-
const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> Option<u32> {
11+
const MODULE_TEMPLATE: &str = r#"pub fn part_one(input: &str) -> Option<u32> {
1212
None
1313
}
1414
@@ -18,8 +18,8 @@ pub fn part_two(input: &str) -> Option<u32> {
1818
1919
fn main() {
2020
let input = &advent_of_code::read_file("inputs", DAY);
21-
advent_of_code::solve!(1, part_one, input);
22-
advent_of_code::solve!(2, part_two, input);
21+
advent_of_code::solve!(DAY, 1, part_one, input);
22+
advent_of_code::solve!(DAY, 2, part_two, input);
2323
}
2424
2525
#[cfg(test)]
@@ -38,7 +38,7 @@ mod tests {
3838
assert_eq!(part_two(&input), None);
3939
}
4040
}
41-
"###;
41+
"#;
4242

4343
fn parse_args() -> Result<u8, pico_args::Error> {
4444
let mut args = pico_args::Arguments::from_env();

Diff for: src/bin/solve.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* This file contains template code.
3+
* There is no need to edit this file unless you want to change template functionality.
4+
*/
5+
6+
use std::process::{self, Command, Stdio};
7+
8+
struct Args {
9+
day: u8,
10+
release: bool,
11+
submit: Option<u8>,
12+
}
13+
14+
fn parse_args() -> Result<Args, pico_args::Error> {
15+
let mut args = pico_args::Arguments::from_env();
16+
Ok(Args {
17+
day: args.free_from_str()?,
18+
release: args.contains("--release"),
19+
submit: args.opt_value_from_str("--submit")?,
20+
})
21+
}
22+
23+
fn run_solution(day: u8, release: bool, submit_part: Option<u8>) -> Result<(), std::io::Error> {
24+
let day_padded = format!("{:02}", day);
25+
26+
let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day_padded];
27+
28+
if release {
29+
cmd_args.push("--release".to_string());
30+
}
31+
32+
if let Some(submit_part) = submit_part {
33+
cmd_args.push("--".to_string());
34+
cmd_args.push("--submit".to_string());
35+
cmd_args.push(submit_part.to_string())
36+
}
37+
38+
let mut cmd = Command::new("cargo")
39+
.args(&cmd_args)
40+
.stdout(Stdio::inherit())
41+
.stderr(Stdio::inherit())
42+
.spawn()?;
43+
44+
cmd.wait()?;
45+
Ok(())
46+
}
47+
48+
fn main() {
49+
let args = match parse_args() {
50+
Ok(args) => args,
51+
Err(e) => {
52+
eprintln!("Failed to process arguments: {}", e);
53+
process::exit(1);
54+
}
55+
};
56+
57+
run_solution(args.day, args.release, args.submit).unwrap();
58+
}

Diff for: src/lib.rs

+67-11
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,42 @@ pub const ANSI_RESET: &str = "\x1b[0m";
1414

1515
#[macro_export]
1616
macro_rules! solve {
17-
($part:expr, $solver:ident, $input:expr) => {{
18-
use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET};
17+
($day:expr, $part:expr, $solver:ident, $input:expr) => {{
18+
use advent_of_code::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET, aoc_cli};
1919
use std::fmt::Display;
2020
use std::time::Instant;
21+
use std::env;
22+
use std::process;
23+
24+
fn submit_if_requested<T: Display>(result: T) {
25+
let args: Vec<String> = env::args().collect();
26+
27+
if args.contains(&"--submit".into()) {
28+
if aoc_cli::check().is_err() {
29+
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
30+
process::exit(1);
31+
}
32+
33+
if args.len() < 3 {
34+
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
35+
process::exit(1);
36+
}
37+
38+
let part_index = args.iter().position(|x| x == "--submit").unwrap() + 1;
39+
let part_submit = match args[part_index].parse::<u8>() {
40+
Ok(x) => x,
41+
Err(_) => {
42+
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
43+
process::exit(1);
44+
}
45+
};
46+
47+
if part_submit == $part {
48+
println!("Submitting puzzle answer for part {}...", $part);
49+
aoc_cli::submit($day, $part, result).unwrap();
50+
}
51+
}
52+
}
2153

2254
fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str) {
2355
let timer = Instant::now();
@@ -29,6 +61,7 @@ macro_rules! solve {
2961
"{} {}(elapsed: {:.2?}){}",
3062
result, ANSI_ITALIC, elapsed, ANSI_RESET
3163
);
64+
submit_if_requested(result);
3265
}
3366
None => {
3467
println!("not solved.")
@@ -127,10 +160,10 @@ mod tests {
127160
pub mod aoc_cli {
128161
use std::{
129162
fmt::Display,
130-
fs::create_dir_all,
131163
process::{Command, Output, Stdio},
132164
};
133165

166+
#[derive(Debug)]
134167
pub enum AocCliError {
135168
CommandNotFound,
136169
CommandNotCallable,
@@ -159,17 +192,26 @@ pub mod aoc_cli {
159192
Ok(())
160193
}
161194

162-
pub fn read(day: u8, year: Option<u16>) -> Result<Output, AocCliError> {
195+
pub fn read(day: u8) -> Result<Output, AocCliError> {
163196
// TODO: output local puzzle if present.
164-
let args = build_args("read", &[], day, year);
197+
let puzzle_path = get_puzzle_path(day);
198+
199+
let args = build_args(
200+
"read",
201+
&[
202+
"--description-only".into(),
203+
"--puzzle-file".into(),
204+
puzzle_path,
205+
],
206+
day,
207+
);
208+
165209
call_aoc_cli(&args)
166210
}
167211

168-
pub fn download(day: u8, year: Option<u16>) -> Result<Output, AocCliError> {
212+
pub fn download(day: u8) -> Result<Output, AocCliError> {
169213
let input_path = get_input_path(day);
170-
171214
let puzzle_path = get_puzzle_path(day);
172-
create_dir_all("src/puzzles").map_err(|_| AocCliError::IoError)?;
173215

174216
let args = build_args(
175217
"download",
@@ -181,7 +223,6 @@ pub mod aoc_cli {
181223
puzzle_path.to_string(),
182224
],
183225
day,
184-
year,
185226
);
186227

187228
let output = call_aoc_cli(&args)?;
@@ -196,6 +237,14 @@ pub mod aoc_cli {
196237
}
197238
}
198239

240+
pub fn submit<T: Display>(day: u8, part: u8, result: T) -> Result<Output, AocCliError> {
241+
// workaround: the argument order is inverted for submit.
242+
let mut args = build_args("submit", &[], day);
243+
args.push(part.to_string());
244+
args.push(result.to_string());
245+
call_aoc_cli(&args)
246+
}
247+
199248
fn get_input_path(day: u8) -> String {
200249
let day_padded = format!("{day:02}");
201250
format!("src/inputs/{day_padded}.txt")
@@ -206,10 +255,17 @@ pub mod aoc_cli {
206255
format!("src/puzzles/{day_padded}.md")
207256
}
208257

209-
fn build_args(command: &str, args: &[String], day: u8, year: Option<u16>) -> Vec<String> {
258+
fn get_year() -> Option<u16> {
259+
match std::env::var("AOC_YEAR") {
260+
Ok(x) => x.parse().ok().or(None),
261+
Err(_) => None,
262+
}
263+
}
264+
265+
fn build_args(command: &str, args: &[String], day: u8) -> Vec<String> {
210266
let mut cmd_args = args.to_vec();
211267

212-
if let Some(year) = year {
268+
if let Some(year) = get_year() {
213269
cmd_args.push("--year".into());
214270
cmd_args.push(year.to_string());
215271
}

0 commit comments

Comments
 (0)