Skip to content

Commit 4d10812

Browse files
authored
feat: download puzzle descriptions (#21)
* 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 eliminate temp files
1 parent 7ef01ab commit 4d10812

File tree

5 files changed

+193
-78
lines changed

5 files changed

+193
-78
lines changed

.cargo/config

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[alias]
2-
scaffold = "run --bin scaffold -- "
3-
download = "run --bin download -- "
2+
scaffold = "run --bin scaffold --quiet --release -- "
3+
download = "run --bin download --quiet --release -- "
4+
read = "run --bin read --quiet --release -- "
45

56
solve = "run --bin"
67
all = "run"

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Every [solution](https://github.com/fspoettel/advent-of-code-rust/blob/main/src/
5050

5151
When editing a solution, `rust-analyzer` will display buttons for running / debugging unit tests above the unit test blocks.
5252

53-
### Download input for a day
53+
### Download input & description for a day
5454

5555
> **Note**
5656
> 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
6060
cargo download <day>
6161

6262
# output:
63-
# Downloading input with aoc-cli...
64-
# Loaded session cookie from "/home/felix/.adventofcode.session".
65-
# Downloading input for day 1, 2021...
66-
# Saving puzzle input to "/tmp/tmp.MBdcAdL9Iw/input"...
63+
# Loaded session cookie from "/Users/<snip>/.adventofcode.session".
64+
# Fetching puzzle for day 1, 2022...
65+
# Saving puzzle description to "src/puzzles/01.md"...
66+
# Downloading input for day 1, 2022...
67+
# Saving puzzle input to "src/inputs/01.txt"...
6768
# Done!
6869
# ---
69-
# 🎄 Successfully wrote input to "src/inputs/01.txt"!
70+
# 🎄 Successfully wrote input to "src/inputs/01.txt".
71+
# 🎄 Successfully wrote puzzle to "src/puzzles/01.md".
7072
```
7173

7274
To download inputs for previous years, append the `--year/-y` flag. _(example: `cargo download 1 --year 2020`)_
7375

74-
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).
76+
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).
7577

7678
### Run solutions for a day
7779

@@ -139,11 +141,28 @@ cargo fmt
139141
cargo clippy
140142
```
141143

144+
### Read puzzle description in terminal
145+
146+
> **Note**
147+
> This command requires [installing the aoc-cli crate](#download-puzzle-inputs-via-aoc-cli).
148+
149+
```sh
150+
# example: `cargo read 1`
151+
cargo read <day>
152+
153+
# output:
154+
# Loaded session cookie from "/Users/<snip>/.adventofcode.session".
155+
# Fetching puzzle for day 1, 2022...
156+
# ...the input...
157+
```
158+
159+
To read inputs for previous years, append the `--year/-y` flag. _(example: `cargo read 1 --year 2020`)_
160+
142161
## Optional template features
143162

144163
### Download puzzle inputs via aoc-cli
145164

146-
1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.5.0`.
165+
1. Install [`aoc-cli`](https://github.com/scarvalhojr/aoc-cli/) via cargo: `cargo install aoc-cli --version 0.7.0`
147166
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.
148167

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

src/bin/download.rs

Lines changed: 9 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
* This file contains template code.
33
* There is no need to edit this file unless you want to change template functionality.
44
*/
5-
use std::io::Write;
6-
use std::path::PathBuf;
7-
use std::{env::temp_dir, io, process::Command};
8-
use std::{fs, process};
5+
use advent_of_code::aoc_cli;
6+
use std::process;
97

108
struct Args {
119
day: u8,
12-
year: Option<i16>,
10+
year: Option<u16>,
1311
}
1412

1513
fn parse_args() -> Result<Args, pico_args::Error> {
@@ -20,86 +18,29 @@ fn parse_args() -> Result<Args, pico_args::Error> {
2018
})
2119
}
2220

23-
fn remove_file(path: &PathBuf) {
24-
#[allow(unused_must_use)]
25-
{
26-
fs::remove_file(path);
27-
}
28-
}
29-
30-
fn exit_with_status(status: i32, path: &PathBuf) -> ! {
31-
remove_file(path);
32-
process::exit(status);
33-
}
34-
3521
fn main() {
36-
// acquire a temp file path to write aoc-cli output to.
37-
// aoc-cli expects this file not to be present - delete just in case.
38-
let mut tmp_file_path = temp_dir();
39-
tmp_file_path.push("aoc_input_tmp");
40-
remove_file(&tmp_file_path);
41-
4222
let args = match parse_args() {
4323
Ok(args) => args,
4424
Err(e) => {
4525
eprintln!("Failed to process arguments: {}", e);
46-
exit_with_status(1, &tmp_file_path);
26+
process::exit(1);
4727
}
4828
};
4929

50-
let day_padded = format!("{:02}", args.day);
51-
let input_path = format!("src/inputs/{}.txt", day_padded);
52-
53-
// check if aoc binary exists and is callable.
54-
if Command::new("aoc").arg("-V").output().is_err() {
30+
if aoc_cli::check().is_err() {
5531
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
56-
exit_with_status(1, &tmp_file_path);
32+
process::exit(1);
5733
}
5834

59-
let mut cmd_args = vec![];
60-
61-
if let Some(year) = args.year {
62-
cmd_args.push("--year".into());
63-
cmd_args.push(year.to_string());
64-
}
65-
66-
cmd_args.append(&mut vec![
67-
"--input-file".into(),
68-
tmp_file_path.to_string_lossy().to_string(),
69-
"--day".into(),
70-
args.day.to_string(),
71-
"download".into(),
72-
]);
73-
74-
println!("Downloading input with >aoc {}", cmd_args.join(" "));
75-
76-
match Command::new("aoc").args(cmd_args).output() {
35+
match aoc_cli::download(args.day, args.year) {
7736
Ok(cmd_output) => {
78-
io::stdout()
79-
.write_all(&cmd_output.stdout)
80-
.expect("could not write cmd stdout to pipe.");
81-
io::stderr()
82-
.write_all(&cmd_output.stderr)
83-
.expect("could not write cmd stderr to pipe.");
8437
if !cmd_output.status.success() {
85-
exit_with_status(1, &tmp_file_path);
38+
process::exit(1);
8639
}
8740
}
8841
Err(e) => {
8942
eprintln!("failed to spawn aoc-cli: {}", e);
90-
exit_with_status(1, &tmp_file_path);
91-
}
92-
}
93-
94-
match fs::copy(&tmp_file_path, &input_path) {
95-
Ok(_) => {
96-
println!("---");
97-
println!("🎄 Successfully wrote input to \"{}\".", &input_path);
98-
exit_with_status(0, &tmp_file_path);
99-
}
100-
Err(e) => {
101-
eprintln!("could not copy downloaded input to input file: {}", e);
102-
exit_with_status(1, &tmp_file_path);
43+
process::exit(1);
10344
}
10445
}
10546
}

src/bin/read.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
use advent_of_code::aoc_cli;
6+
use std::process;
7+
8+
struct Args {
9+
day: u8,
10+
year: Option<u16>,
11+
}
12+
13+
fn parse_args() -> Result<Args, pico_args::Error> {
14+
let mut args = pico_args::Arguments::from_env();
15+
Ok(Args {
16+
day: args.free_from_str()?,
17+
year: args.opt_value_from_str(["-y", "--year"])?,
18+
})
19+
}
20+
21+
fn main() {
22+
let args = match parse_args() {
23+
Ok(args) => args,
24+
Err(e) => {
25+
eprintln!("Failed to process arguments: {}", e);
26+
process::exit(1);
27+
}
28+
};
29+
30+
if aoc_cli::check().is_err() {
31+
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
32+
process::exit(1);
33+
}
34+
35+
match aoc_cli::read(args.day, args.year) {
36+
Ok(cmd_output) => {
37+
if !cmd_output.status.success() {
38+
process::exit(1);
39+
}
40+
}
41+
Err(e) => {
42+
eprintln!("failed to spawn aoc-cli: {}", e);
43+
process::exit(1);
44+
}
45+
}
46+
}

src/lib.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,111 @@ mod tests {
123123
);
124124
}
125125
}
126+
127+
pub mod aoc_cli {
128+
use std::{
129+
fmt::Display,
130+
fs::create_dir_all,
131+
process::{Command, Output, Stdio},
132+
};
133+
134+
pub enum AocCliError {
135+
CommandNotFound,
136+
CommandNotCallable,
137+
BadExitStatus(Output),
138+
IoError,
139+
}
140+
141+
impl Display for AocCliError {
142+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143+
match self {
144+
AocCliError::CommandNotFound => write!(f, "aoc-cli is not present in environment."),
145+
AocCliError::CommandNotCallable => write!(f, "aoc-cli could not be called."),
146+
AocCliError::BadExitStatus(_) => write!(f, "aoc-cli exited with a non-zero status."),
147+
AocCliError::IoError => write!(f, "could not write output files to file system."),
148+
}
149+
}
150+
}
151+
152+
pub fn check() -> Result<(), AocCliError> {
153+
Command::new("aoc")
154+
.arg("-V")
155+
.output()
156+
.map_err(|_| AocCliError::CommandNotFound)?;
157+
Ok(())
158+
}
159+
160+
pub fn read(day: u8, year: Option<u16>) -> Result<Output, AocCliError> {
161+
// TODO: output local puzzle if present.
162+
let args = build_args("read", &[], day, year);
163+
call_aoc_cli(&args)
164+
}
165+
166+
pub fn download(day: u8, year: Option<u16>) -> Result<Output, AocCliError> {
167+
let input_path = get_input_path(day);
168+
169+
let puzzle_path = get_puzzle_path(day);
170+
create_dir_all("src/puzzles").map_err(|_| AocCliError::IoError)?;
171+
172+
let args = build_args(
173+
"download",
174+
&[
175+
"--overwrite".into(),
176+
"--input-file".into(),
177+
input_path.to_string(),
178+
"--puzzle-file".into(),
179+
puzzle_path.to_string(),
180+
],
181+
day,
182+
year,
183+
);
184+
185+
let output = call_aoc_cli(&args)?;
186+
187+
if output.status.success() {
188+
println!("---");
189+
println!("🎄 Successfully wrote input to \"{}\".", &input_path);
190+
println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path);
191+
Ok(output)
192+
} else {
193+
Err(AocCliError::BadExitStatus(output))
194+
}
195+
196+
}
197+
198+
fn get_input_path(day: u8) -> String {
199+
let day_padded = format!("{:02}", day);
200+
format!("src/inputs/{}.txt", day_padded)
201+
}
202+
203+
fn get_puzzle_path(day: u8) -> String {
204+
let day_padded = format!("{:02}", day);
205+
format!("src/puzzles/{}.md", day_padded)
206+
}
207+
208+
fn build_args(command: &str, args: &[String], day: u8, year: Option<u16>) -> Vec<String> {
209+
let mut cmd_args = args.to_vec();
210+
211+
if let Some(year) = year {
212+
cmd_args.push("--year".into());
213+
cmd_args.push(year.to_string());
214+
}
215+
216+
cmd_args.append(&mut vec!["--day".into(), day.to_string(), command.into()]);
217+
218+
cmd_args
219+
}
220+
221+
fn call_aoc_cli(args: &[String]) -> Result<Output, AocCliError> {
222+
if cfg!(debug_assertions) {
223+
println!("Calling >aoc with: {}", args.join(" "));
224+
}
225+
226+
Command::new("aoc")
227+
.args(args)
228+
.stdout(Stdio::inherit())
229+
.stderr(Stdio::inherit())
230+
.output()
231+
.map_err(|_| AocCliError::CommandNotCallable)
232+
}
233+
}

0 commit comments

Comments
 (0)