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");
        }
    }
}