use std::{borrow::Cow, process}; use clap::{value_parser, Arg, ArgMatches, Command}; use crate::{Day, Variant}; pub fn main(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::().split("::").collect::>(); 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::(default_input, &variants, matches); } Some(("part2", matches)) => { let variants = D::part2().variants; dispatch_root_subcommand::(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( default_input: &str, variants: &[Variant], matches: &ArgMatches, ) -> ! { if variants.len() > 1 { let (subcommand, matches) = matches.subcommand().unwrap(); let iter = matches.get_one::("iter").unwrap(); let variant = variants.iter().find(|v| v.name == subcommand).unwrap(); let input = get_input(matches, default_input); execute::(variant, &input, *iter); } else { let iter = matches.get_one::("iter").unwrap(); let input = get_input(matches, default_input); execute::(&variants[0], &input, *iter); } } fn execute(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::("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)) }