aoc/
lib.rs

1//! A library for running [Advent of Code](https://adventofcode.com) solutions using [aocli](https://github.com/sncxyz/aocli),
2//! and for parsing Advent of Code inputs.
3//!
4//! # Examples
5//! ```
6//! use aoc::Parse;
7//!
8//! let line_1 = "first: 85, then: +192, finally: -64";
9//! let line_2 = "first: -157, then: 4, finally: 1000";
10//!
11//! fn parse_line(line: &str) -> [i32; 3] {
12//!     let mut parser = line.as_parser();
13//!     [
14//!         parser.between("first: ", ", "),
15//!         parser.between("then: ", ", "),
16//!         parser.after("finally: "),
17//!     ]
18//!     .map(Parse::parse_uw)
19//! }
20//!
21//! assert_eq!(line_1.ints::<3, i32>(), [85, 192, -64]);
22//! assert_eq!(parse_line(line_1), [85, 192, -64]);
23//!
24//! assert_eq!(line_2.ints::<3, i32>(), [-157, 4, 1000]);
25//! assert_eq!(parse_line(line_2), [-157, 4, 1000]);
26//! ```
27mod input;
28mod parse;
29
30use std::{env, fs, path::Path, time::Instant};
31
32pub use input::{Input, Lines};
33pub use parse::{Ints, IterUnwrap, Parse, Parser, UInts};
34
35type Part<T> = fn(Input) -> T;
36
37/// A macro for running with [aocli](https://github.com/sncxyz/aocli).
38///
39/// Inserts `fn main` and passes your part 1 and part 2 functions to the `aoclib` library where applicable.
40///
41/// - `aoc::parts!();` if neither part is implemented
42/// - `aoc::parts!(1);` if only part 1 is implemented (`fn part_1`)
43/// - `aoc::parts!(2);` if only part 2 is implemented (`fn part_2`)
44/// - `aoc::parts!(1, 2);` if both parts are implemented
45#[macro_export]
46macro_rules! parts {
47    () => {
48        fn main() {
49            aoc::run::<u8, u8>(None, None);
50        }
51    };
52    (1) => {
53        fn main() {
54            aoc::run::<_, u8>(Some(part_1), None);
55        }
56    };
57    (2) => {
58        fn main() {
59            aoc::run::<u8, _>(None, Some(part_2));
60        }
61    };
62    (1, 2) => {
63        fn main() {
64            aoc::run(Some(part_1), Some(part_2));
65        }
66    };
67}
68
69/// The function that `aoc::parts!` inserts into `fn main`.
70///
71/// Runs one of the parts with one of the puzzle inputs, depending on the command line arguments passed.
72///
73/// Writes the puzzle answer and timing to files in `/data/[input]/[part]/out` so that `aocli` can read them.
74pub fn run<T1, T2>(part_1: Option<Part<T1>>, part_2: Option<Part<T2>>)
75where
76    T1: ToString,
77    T2: ToString,
78{
79    let args: Vec<_> = env::args().collect();
80    if args.len() != 3 {
81        panic!("incorrect number of arguments");
82    }
83    let data = args[1].as_str();
84    let mut data_path = Path::new("data").join(data);
85    if !data_path.is_dir() {
86        panic!("no data directory");
87    }
88    let part = args[2].as_str();
89    if part != "1" && part != "2" {
90        panic!("invalid part argument");
91    }
92    let out_path = data_path.join(part).join("out");
93    if out_path.try_exists().unwrap() {
94        if !out_path.is_dir() {
95            panic!("unexpected out file found");
96        }
97    } else {
98        fs::create_dir_all(&out_path).unwrap();
99    }
100    data_path.push("input");
101    let unimplemented_path = out_path.join("unimplemented");
102    if part == "1" {
103        let Some(part_1) = part_1 else {
104            fs::write(unimplemented_path, "").unwrap();
105            return;
106        };
107        implemented(&unimplemented_path);
108        run_part(&data_path, &out_path, part_1);
109    } else {
110        let Some(part_2) = part_2 else {
111            fs::write(unimplemented_path, "").unwrap();
112            return;
113        };
114        implemented(&unimplemented_path);
115        run_part(&data_path, &out_path, part_2);
116    }
117}
118
119fn run_part<T>(data_path: &Path, out_path: &Path, part_n: Part<T>)
120where
121    T: ToString,
122{
123    if !data_path.is_file() {
124        panic!("no input file");
125    }
126    let input = fs::read_to_string(data_path).unwrap();
127    let input = input.trim_end();
128    if input.is_empty() {
129        panic!("input file is empty");
130    }
131    let lines: Vec<_> = input.lines().collect();
132    let input = Input::new(input, &lines);
133    let start = Instant::now();
134    let answer = part_n(input);
135    let time = start.elapsed().as_nanos();
136    let answer = answer.to_string();
137    fs::write(out_path.join("answer"), answer).unwrap();
138    fs::write(out_path.join("time"), time.to_string()).unwrap();
139}
140
141fn implemented(path: &Path) {
142    if path.try_exists().unwrap() {
143        if path.is_file() {
144            fs::remove_file(path).unwrap();
145        } else {
146            panic!("unexpected unimplemented directory found");
147        }
148    }
149}