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