1use anyhow::{bail, Result};
2use colored::*;
3use std::collections::HashMap;
4use std::time::{Duration, Instant};
5
6pub use aoc_toolbox_derive::{aoc_main, aoc_solver};
7
8pub use clap;
9pub use clap::Parser;
10pub use std::error::Error;
11
12pub mod utils;
13
14enum Measure {
15 Real(Instant),
16 Mock,
17}
18
19impl Measure {
20 fn step(&self) -> Option<Duration> {
21 match self {
22 Measure::Real(start) => Some(Instant::now().duration_since(*start)),
23 Measure::Mock => None,
24 }
25 }
26}
27
28type SolverFn = fn() -> String;
29struct Solver<'a> {
30 solver: SolverFn,
31 result: Option<&'a str>,
32}
33
34pub struct Aoc<'a> {
35 year: u32,
36 with_duration: bool,
37 solvers: HashMap<&'a str, HashMap<&'a str, Solver<'a>>>,
38}
39
40impl<'a> Aoc<'a> {
41 pub fn new(year: u32, with_duration: bool) -> Self {
42 Aoc {
43 year,
44 with_duration,
45 solvers: HashMap::new(),
46 }
47 }
48
49 pub fn add_solver(
50 &mut self,
51 day: &'a str,
52 part: &'a str,
53 result: Option<&'a str>,
54 solver: SolverFn,
55 ) {
56 let day = self.solvers.entry(day).or_default();
57 day.insert(part, Solver { solver, result });
58 }
59
60 fn visit_solvers<F>(&self, visit: F)
61 where
62 F: Fn(&'a str, Option<(&'a str, &Solver)>),
63 {
64 let mut day_keys: Vec<&&str> = self.solvers.keys().collect();
65 day_keys.sort_unstable();
66 for day in day_keys {
67 visit(day, None);
68 let parts = self.solvers.get(day).unwrap();
69 let mut part_keys: Vec<&&str> = parts.keys().collect();
70 part_keys.sort_unstable();
71 for part in part_keys {
72 let solver = parts.get(part).unwrap();
73 visit(day, Some((part, solver)));
74 }
75 }
76 }
77
78 fn pretty_format(&self, day: &str, part: &str, solver: &Solver) -> String {
79 let measure = if self.with_duration {
80 Measure::Real(Instant::now())
81 } else {
82 Measure::Mock
83 };
84
85 let result = (solver.solver)();
86 let duration = measure.step();
87
88 let valid = if let Some(expected) = solver.result {
89 result == expected
90 } else {
91 false
92 };
93
94 if self.with_duration {
95 let duration = duration.unwrap();
96 format!(
97 "{}::{} [{:>4}s {:>4}ms] {} {}",
98 day.blue(),
99 if valid { part.green() } else { part.red() },
100 duration.as_secs(),
101 duration.subsec_millis(),
102 "................".cyan(),
103 result,
104 )
105 } else {
106 format!(
107 "{}::{} {} {}",
108 day.blue(),
109 if valid { part.green() } else { part.red() },
110 "................".cyan(),
111 result
112 )
113 }
114 }
115
116 fn run_all(&self) -> Result<()> {
117 self.visit_solvers(|day, part_solver| match part_solver {
118 None => println!("{}{}", "# ".yellow(), day.yellow()),
119 Some((part, solver)) => {
120 println!(" - {}", self.pretty_format(day, part, solver));
121 }
122 });
123
124 Ok(())
125 }
126
127 fn run_day(&self, day: &str) -> Result<()> {
128 self.visit_solvers(|visit_day, part_solver| {
129 if visit_day == day {
130 if let Some((part, solver)) = part_solver {
131 println!("{}", self.pretty_format(day, part, solver));
132 }
133 }
134 });
135
136 Ok(())
137 }
138
139 fn run_part(&self, day: &str, part: &str) -> Result<()> {
140 self.visit_solvers(|visit_day, part_solver| {
141 if visit_day == day {
142 match part_solver {
143 Some((visit_part, solver)) if visit_part == part => {
144 println!("{}", self.pretty_format(day, part, solver));
145 }
146 _ => {}
147 }
148 }
149 });
150
151 Ok(())
152 }
153
154 pub fn list(&self) {
155 self.visit_solvers(|day, part_solver| {
156 if let Some((part, _)) = part_solver {
157 println!("{}::{}", day.blue(), part.green())
158 }
159 });
160 }
161
162 pub fn run(&self, label: String) -> Result<()> {
163 println!("{} {}", "Year".green().bold(), self.year);
164
165 match label.split("::").collect::<Vec<&str>>().as_slice() {
166 ["all"] => self.run_all(),
167 [day] => self.run_day(day),
168 [day, part] => self.run_part(day, part),
169 _ => bail!("Invalid label, must be like <day>::<part>"),
170 }?;
171
172 Ok(())
173 }
174}