mirror of
https://github.com/Noratrieb/advent-of-code.git
synced 2026-01-14 17:45:02 +01:00
114 lines
3.6 KiB
Rust
114 lines
3.6 KiB
Rust
use std::{borrow::Cow, process};
|
|
|
|
use clap::{value_parser, Arg, ArgMatches, Command};
|
|
|
|
use crate::{Day, Variant};
|
|
|
|
pub fn main<D: Day>(default_input: &str) -> ! {
|
|
let mut part1 = Command::new("part1").about("Runs the part 1 program");
|
|
let mut part2 = Command::new("part2").about("Runs the part 2 program");
|
|
|
|
part1 = create_variant_subcommands(part1, &D::part1().variants);
|
|
part2 = create_variant_subcommands(part2, &D::part2().variants);
|
|
|
|
let mut typename = std::any::type_name::<D>().split("::").collect::<Vec<_>>();
|
|
let typename = typename.pop().unwrap();
|
|
let cmd = Command::new(typename.to_lowercase())
|
|
.about(format!(
|
|
"Program to run the AOC answer for day {}",
|
|
typename.strip_prefix("Day").unwrap()
|
|
))
|
|
.subcommand_required(true)
|
|
.subcommand(part1)
|
|
.subcommand(part2);
|
|
|
|
let matches = cmd.clone().get_matches();
|
|
|
|
match matches.subcommand() {
|
|
Some(("part1", matches)) => {
|
|
let variants = D::part1().variants;
|
|
dispatch_root_subcommand::<D>(default_input, &variants, matches);
|
|
}
|
|
Some(("part2", matches)) => {
|
|
let variants = D::part2().variants;
|
|
dispatch_root_subcommand::<D>(default_input, &variants, matches);
|
|
}
|
|
_ => {
|
|
unreachable!("subcommand_required")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn create_variant_subcommands(mut part: Command, variants: &[Variant]) -> Command {
|
|
let iter_arg = Arg::new("iter")
|
|
.long("iter")
|
|
.value_parser(value_parser!(usize))
|
|
.default_value("1");
|
|
|
|
if variants.len() > 1 {
|
|
part = part.subcommand_required(true);
|
|
|
|
variants
|
|
.iter()
|
|
.map(|v| {
|
|
Command::new(v.name)
|
|
.about(format!("Run the {} variant", v.name))
|
|
.arg(Arg::new("input").short('i').long("input"))
|
|
.arg(iter_arg.clone())
|
|
})
|
|
.for_each(|cmd| part = part.clone().subcommand(cmd));
|
|
} else {
|
|
part = part
|
|
.arg(Arg::new("input").short('i').long("input"))
|
|
.arg(iter_arg);
|
|
}
|
|
|
|
part
|
|
}
|
|
|
|
fn dispatch_root_subcommand<D: Day>(
|
|
default_input: &str,
|
|
variants: &[Variant],
|
|
matches: &ArgMatches,
|
|
) -> ! {
|
|
if variants.len() > 1 {
|
|
let (subcommand, matches) = matches.subcommand().unwrap();
|
|
let iter = matches.get_one::<usize>("iter").unwrap();
|
|
let variant = variants.iter().find(|v| v.name == subcommand).unwrap();
|
|
let input = get_input(matches, default_input);
|
|
execute::<D>(variant, &input, *iter);
|
|
} else {
|
|
let iter = matches.get_one::<usize>("iter").unwrap();
|
|
let input = get_input(matches, default_input);
|
|
execute::<D>(&variants[0], &input, *iter);
|
|
}
|
|
}
|
|
|
|
fn execute<D: Day>(variant: &Variant, input: &str, iter: usize) -> ! {
|
|
use std::io::Write;
|
|
let input = D::pad_input(input);
|
|
let mut result = 0;
|
|
for _ in 0..iter {
|
|
result = (variant.f)(&input);
|
|
}
|
|
let err = writeln!(std::io::stdout(), "{result}");
|
|
if let Err(err) = err {
|
|
if err.kind() != std::io::ErrorKind::BrokenPipe {
|
|
eprintln!("error: {err}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
process::exit(0);
|
|
}
|
|
|
|
fn get_input<'a>(matches: &ArgMatches, default: &'a str) -> Cow<'a, str> {
|
|
matches
|
|
.get_one::<String>("input")
|
|
.map(|input| {
|
|
Cow::Owned(std::fs::read_to_string(input).unwrap_or_else(|err| {
|
|
eprintln!("error: failed to read file {input}: {err}");
|
|
process::exit(1);
|
|
}))
|
|
})
|
|
.unwrap_or(Cow::Borrowed(default))
|
|
}
|