aoc_toolbox/
lib.rs

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}