diff --git a/src/main.rs b/src/main.rs index e4750f5..91a042c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,10 @@ -use advent_of_code::template::commands::{ - all::all_handler, download::download_handler, read::read_handler, scaffold::scaffold_handler, - solve::solve_handler, -}; -use args::{parse_args, AppArgs}; +use advent_of_code::template::commands::{all, download, read, scaffold, solve}; +use args::{parse, AppArguments}; mod args { use std::process; - pub enum AppArgs { + pub enum AppArguments { Download { day: u8, }, @@ -29,31 +26,31 @@ mod args { }, } - pub fn parse_args() -> Result> { + pub fn parse() -> Result> { let mut args = pico_args::Arguments::from_env(); let app_args = match args.subcommand()?.as_deref() { - Some("all") => AppArgs::All { + Some("all") => AppArguments::All { release: args.contains("--release"), time: args.contains("--time"), }, - Some("download") => AppArgs::Download { + Some("download") => AppArguments::Download { day: args.free_from_str()?, }, - Some("read") => AppArgs::Read { + Some("read") => AppArguments::Read { day: args.free_from_str()?, }, - Some("scaffold") => AppArgs::Scaffold { + Some("scaffold") => AppArguments::Scaffold { day: args.free_from_str()?, }, - Some("solve") => AppArgs::Solve { + Some("solve") => AppArguments::Solve { day: args.free_from_str()?, release: args.contains("--release"), submit: args.opt_value_from_str("--submit")?, time: args.contains("--time"), }, Some(x) => { - eprintln!("Unknown command: {}", x); + eprintln!("Unknown command: {x}"); process::exit(1); } None => { @@ -64,7 +61,7 @@ mod args { let remaining = args.finish(); if !remaining.is_empty() { - eprintln!("Warning: unknown argument(s): {:?}.", remaining); + eprintln!("Warning: unknown argument(s): {remaining:?}."); } Ok(app_args) @@ -72,22 +69,22 @@ mod args { } fn main() { - match parse_args() { + match parse() { Err(err) => { - eprintln!("Error: {}", err); + eprintln!("Error: {err}"); std::process::exit(1); } Ok(args) => match args { - AppArgs::All { release, time } => all_handler(release, time), - AppArgs::Download { day } => download_handler(day), - AppArgs::Read { day } => read_handler(day), - AppArgs::Scaffold { day } => scaffold_handler(day), - AppArgs::Solve { + AppArguments::All { release, time } => all::handle(release, time), + AppArguments::Download { day } => download::handle(day), + AppArguments::Read { day } => read::handle(day), + AppArguments::Scaffold { day } => scaffold::handle(day), + AppArguments::Solve { day, release, time, submit, - } => solve_handler(day, release, time, submit), + } => solve::handle(day, release, time, submit), }, }; } diff --git a/src/template/aoc_cli.rs b/src/template/aoc_cli.rs index 31ac7ac..08ce9fe 100644 --- a/src/template/aoc_cli.rs +++ b/src/template/aoc_cli.rs @@ -5,35 +5,35 @@ use std::{ }; #[derive(Debug)] -pub enum AocCliError { +pub enum AocCommandError { CommandNotFound, CommandNotCallable, BadExitStatus(Output), IoError, } -impl Display for AocCliError { +impl Display for AocCommandError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AocCliError::CommandNotFound => write!(f, "aoc-cli is not present in environment."), - AocCliError::CommandNotCallable => write!(f, "aoc-cli could not be called."), - AocCliError::BadExitStatus(_) => { + AocCommandError::CommandNotFound => write!(f, "aoc-cli is not present in environment."), + AocCommandError::CommandNotCallable => write!(f, "aoc-cli could not be called."), + AocCommandError::BadExitStatus(_) => { write!(f, "aoc-cli exited with a non-zero status.") } - AocCliError::IoError => write!(f, "could not write output files to file system."), + AocCommandError::IoError => write!(f, "could not write output files to file system."), } } } -pub fn check() -> Result<(), AocCliError> { +pub fn check() -> Result<(), AocCommandError> { Command::new("aoc") .arg("-V") .output() - .map_err(|_| AocCliError::CommandNotFound)?; + .map_err(|_| AocCommandError::CommandNotFound)?; Ok(()) } -pub fn read(day: u8) -> Result { +pub fn read(day: u8) -> Result { let puzzle_path = get_puzzle_path(day); let args = build_args( @@ -49,7 +49,7 @@ pub fn read(day: u8) -> Result { call_aoc_cli(&args) } -pub fn download(day: u8) -> Result { +pub fn download(day: u8) -> Result { let input_path = get_input_path(day); let puzzle_path = get_puzzle_path(day); @@ -72,7 +72,7 @@ pub fn download(day: u8) -> Result { Ok(output) } -pub fn submit(day: u8, part: u8, result: &str) -> Result { +pub fn submit(day: u8, part: u8, result: &str) -> Result { // workaround: the argument order is inverted for submit. let mut args = build_args("submit", &[], day); args.push(part.to_string()); @@ -81,13 +81,13 @@ pub fn submit(day: u8, part: u8, result: &str) -> Result { } fn get_input_path(day: u8) -> String { - let day_padded = format!("{:02}", day); - format!("data/inputs/{}.txt", day_padded) + let day_padded = format!("{day:02}"); + format!("data/inputs/{day_padded}.txt") } fn get_puzzle_path(day: u8) -> String { - let day_padded = format!("{:02}", day); - format!("data/puzzles/{}.md", day_padded) + let day_padded = format!("{day:02}"); + format!("data/puzzles/{day_padded}.md") } fn get_year() -> Option { @@ -110,18 +110,18 @@ fn build_args(command: &str, args: &[String], day: u8) -> Vec { cmd_args } -fn call_aoc_cli(args: &[String]) -> Result { +fn call_aoc_cli(args: &[String]) -> Result { // println!("Calling >aoc with: {}", args.join(" ")); let output = Command::new("aoc") .args(args) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .output() - .map_err(|_| AocCliError::CommandNotCallable)?; + .map_err(|_| AocCommandError::CommandNotCallable)?; if output.status.success() { Ok(output) } else { - Err(AocCliError::BadExitStatus(output)) + Err(AocCommandError::BadExitStatus(output)) } } diff --git a/src/template/commands/all.rs b/src/template/commands/all.rs index 87fc2c9..7214b4a 100644 --- a/src/template/commands/all.rs +++ b/src/template/commands/all.rs @@ -5,7 +5,7 @@ use crate::template::{ ANSI_BOLD, ANSI_ITALIC, ANSI_RESET, }; -pub fn all_handler(is_release: bool, is_timed: bool) { +pub fn handle(is_release: bool, is_timed: bool) { let mut timings: Vec = vec![]; (1..=25).for_each(|day| { @@ -13,7 +13,7 @@ pub fn all_handler(is_release: bool, is_timed: bool) { println!(); } - println!("{}Day {}{}", ANSI_BOLD, day, ANSI_RESET); + println!("{ANSI_BOLD}Day {day}{ANSI_RESET}"); println!("------"); let output = child_commands::run_solution(day, is_timed, is_release).unwrap(); @@ -27,16 +27,13 @@ pub fn all_handler(is_release: bool, is_timed: bool) { }); if is_timed { - let total_millis = timings.iter().map(|x| x.total_nanos).sum::() / 1000000_f64; + let total_millis = timings.iter().map(|x| x.total_nanos).sum::() / 1_000_000_f64; - println!( - "\n{}Total:{} {}{:.2}ms{}", - ANSI_BOLD, ANSI_RESET, ANSI_ITALIC, total_millis, ANSI_RESET - ); + println!("\n{ANSI_BOLD}Total:{ANSI_RESET} {ANSI_ITALIC}{total_millis:.2}ms{ANSI_RESET}"); if is_release { match readme_benchmarks::update(timings, total_millis) { - Ok(_) => println!("Successfully updated README with benchmarks."), + Ok(()) => println!("Successfully updated README with benchmarks."), Err(_) => { eprintln!("Failed to update readme with benchmarks."); } @@ -58,9 +55,10 @@ impl From for Error { } } +#[must_use] pub fn get_path_for_bin(day: usize) -> String { - let day_padded = format!("{:02}", day); - format!("./src/bin/{}.rs", day_padded) + let day_padded = format!("{day:02}"); + format!("./src/bin/{day_padded}.rs") } /// All solutions live in isolated binaries. @@ -80,7 +78,7 @@ mod child_commands { is_timed: bool, is_release: bool, ) -> Result, Error> { - let day_padded = format!("{:02}", day); + let day_padded = format!("{day:02}"); // skip command invocation for days that have not been scaffolded yet. if !Path::new(&get_path_for_bin(day)).exists() { @@ -121,7 +119,7 @@ mod child_commands { for line in stdout.lines() { let line = line.unwrap(); - println!("{}", line); + println!("{line}"); output.push(line); } @@ -146,12 +144,9 @@ mod child_commands { return None; } - let (timing_str, nanos) = match parse_time(l) { - Some(v) => v, - None => { - eprintln!("Could not parse timings from line: {l}"); - return None; - } + let Some((timing_str, nanos)) = parse_time(l) else { + eprintln!("Could not parse timings from line: {l}"); + return None; }; let part = l.split(':').next()?; @@ -188,8 +183,8 @@ mod child_commands { let parsed_timing = match str_timing { s if s.contains("ns") => s.split("ns").next()?.parse::().ok(), s if s.contains("µs") => parse_to_float(s, "µs").map(|x| x * 1000_f64), - s if s.contains("ms") => parse_to_float(s, "ms").map(|x| x * 1000000_f64), - s => parse_to_float(s, "s").map(|x| x * 1000000000_f64), + s if s.contains("ms") => parse_to_float(s, "ms").map(|x| x * 1_000_000_f64), + s => parse_to_float(s, "s").map(|x| x * 1_000_000_000_f64), }?; Some((str_timing, parsed_timing)) diff --git a/src/template/commands/download.rs b/src/template/commands/download.rs index bd8e2c7..56beead 100644 --- a/src/template/commands/download.rs +++ b/src/template/commands/download.rs @@ -1,14 +1,14 @@ use crate::template::aoc_cli; use std::process; -pub fn download_handler(day: u8) { +pub fn handle(day: u8) { if aoc_cli::check().is_err() { eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); process::exit(1); } if let Err(e) = aoc_cli::download(day) { - eprintln!("failed to call aoc-cli: {}", e); + eprintln!("failed to call aoc-cli: {e}"); process::exit(1); }; } diff --git a/src/template/commands/read.rs b/src/template/commands/read.rs index 98810c8..65edcde 100644 --- a/src/template/commands/read.rs +++ b/src/template/commands/read.rs @@ -2,14 +2,14 @@ use std::process; use crate::template::aoc_cli; -pub fn read_handler(day: u8) { +pub fn handle(day: u8) { if aoc_cli::check().is_err() { eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); process::exit(1); } if let Err(e) = aoc_cli::read(day) { - eprintln!("failed to call aoc-cli: {}", e); + eprintln!("failed to call aoc-cli: {e}"); process::exit(1); }; } diff --git a/src/template/commands/scaffold.rs b/src/template/commands/scaffold.rs index 8673a5e..6a3d9a1 100644 --- a/src/template/commands/scaffold.rs +++ b/src/template/commands/scaffold.rs @@ -40,27 +40,27 @@ fn create_file(path: &str) -> Result { OpenOptions::new().write(true).create(true).open(path) } -pub fn scaffold_handler(day: u8) { - let day_padded = format!("{:02}", day); +pub fn handle(day: u8) { + let day_padded = format!("{day:02}"); - let input_path = format!("data/inputs/{}.txt", day_padded); - let example_path = format!("data/examples/{}.txt", day_padded); - let module_path = format!("src/bin/{}.rs", day_padded); + let input_path = format!("data/inputs/{day_padded}.txt"); + let example_path = format!("data/examples/{day_padded}.txt"); + let module_path = format!("src/bin/{day_padded}.rs"); let mut file = match safe_create_file(&module_path) { Ok(file) => file, Err(e) => { - eprintln!("Failed to create module file: {}", e); + eprintln!("Failed to create module file: {e}"); process::exit(1); } }; match file.write_all(MODULE_TEMPLATE.replace("DAY", &day.to_string()).as_bytes()) { - Ok(_) => { + Ok(()) => { println!("Created module file \"{}\"", &module_path); } Err(e) => { - eprintln!("Failed to write module contents: {}", e); + eprintln!("Failed to write module contents: {e}"); process::exit(1); } } @@ -70,7 +70,7 @@ pub fn scaffold_handler(day: u8) { println!("Created empty input file \"{}\"", &input_path); } Err(e) => { - eprintln!("Failed to create input file: {}", e); + eprintln!("Failed to create input file: {e}"); process::exit(1); } } @@ -80,7 +80,7 @@ pub fn scaffold_handler(day: u8) { println!("Created empty example file \"{}\"", &example_path); } Err(e) => { - eprintln!("Failed to create example file: {}", e); + eprintln!("Failed to create example file: {e}"); process::exit(1); } } diff --git a/src/template/commands/solve.rs b/src/template/commands/solve.rs index b9a7d6a..8c65702 100644 --- a/src/template/commands/solve.rs +++ b/src/template/commands/solve.rs @@ -1,7 +1,7 @@ use std::process::{Command, Stdio}; -pub fn solve_handler(day: u8, release: bool, time: bool, submit_part: Option) { - let day_padded = format!("{:02}", day); +pub fn handle(day: u8, release: bool, time: bool, submit_part: Option) { + let day_padded = format!("{day:02}"); let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day_padded]; @@ -13,7 +13,7 @@ pub fn solve_handler(day: u8, release: bool, time: bool, submit_part: Option if let Some(submit_part) = submit_part { cmd_args.push("--submit".to_string()); - cmd_args.push(submit_part.to_string()) + cmd_args.push(submit_part.to_string()); } if time { diff --git a/src/template/mod.rs b/src/template/mod.rs index 16fb741..faddf0a 100644 --- a/src/template/mod.rs +++ b/src/template/mod.rs @@ -10,12 +10,12 @@ pub const ANSI_BOLD: &str = "\x1b[1m"; pub const ANSI_RESET: &str = "\x1b[0m"; /// Helper function that reads a text file to a string. -pub fn read_file(folder: &str, day: u8) -> String { +#[must_use] pub fn read_file(folder: &str, day: u8) -> String { let cwd = env::current_dir().unwrap(); let filepath = cwd .join("data") .join(folder) - .join(format!("{:02}.txt", day)); + .join(format!("{day:02}.txt")); let f = fs::read_to_string(filepath); f.expect("could not open input file") } diff --git a/src/template/readme_benchmarks.rs b/src/template/readme_benchmarks.rs index 49750c4..6f11b5a 100644 --- a/src/template/readme_benchmarks.rs +++ b/src/template/readme_benchmarks.rs @@ -29,9 +29,9 @@ pub struct TablePosition { pos_end: usize, } -pub fn get_path_for_bin(day: usize) -> String { - let day_padded = format!("{:02}", day); - format!("./src/bin/{}.rs", day_padded) +#[must_use] pub fn get_path_for_bin(day: usize) -> String { + let day_padded = format!("{day:02}"); + format!("./src/bin/{day_padded}.rs") } fn locate_table(readme: &str) -> Result { @@ -62,12 +62,12 @@ fn construct_table(prefix: &str, timings: Vec, total_millis: f64) -> St let mut lines: Vec = vec![ MARKER.into(), header, - "".into(), + String::new(), "| Day | Part 1 | Part 2 |".into(), "| :---: | :---: | :---: |".into(), ]; - timings.into_iter().for_each(|timing| { + for timing in timings { let path = get_path_for_bin(timing.day); lines.push(format!( "| [Day {}]({}) | `{}` | `{}` |", @@ -76,10 +76,10 @@ fn construct_table(prefix: &str, timings: Vec, total_millis: f64) -> St timing.part_1.unwrap_or_else(|| "-".into()), timing.part_2.unwrap_or_else(|| "-".into()) )); - }); + } - lines.push("".into()); - lines.push(format!("**Total: {:.2}ms**", total_millis)); + lines.push(String::new()); + lines.push(format!("**Total: {total_millis:.2}ms**")); lines.push(MARKER.into()); lines.join("\n") diff --git a/src/template/runner.rs b/src/template/runner.rs index 5ccde99..6557ee5 100644 --- a/src/template/runner.rs +++ b/src/template/runner.rs @@ -9,7 +9,7 @@ use std::{cmp, env, process}; use super::ANSI_BOLD; pub fn run_part(func: impl Fn(I) -> Option, input: I, day: u8, part: u8) { - let part_str = format!("Part {}", part); + let part_str = format!("Part {part}"); let (result, duration, samples) = run_timed(func, input, |result| print_result(result, &part_str, "")); @@ -35,9 +35,10 @@ fn run_timed( hook(&result); - let run = match std::env::args().any(|x| x == "--time") { - true => bench(func, input, &base_time), - false => (base_time, 1), + let run = if std::env::args().any(|x| x == "--time") { + bench(func, input, &base_time) + } else { + (base_time, 1) }; (result, run.0, run.1) @@ -46,7 +47,7 @@ fn run_timed( fn bench(func: impl Fn(I) -> T, input: I, base_time: &Duration) -> (Duration, u128) { let mut stdout = stdout(); - print!(" > {}benching{}", ANSI_ITALIC, ANSI_RESET); + print!(" > {ANSI_ITALIC}benching{ANSI_RESET}"); let _ = stdout.flush(); let bench_iterations = cmp::min( @@ -68,20 +69,25 @@ fn bench(func: impl Fn(I) -> T, input: I, base_time: &Duration) -> } ( + #[allow(clippy::cast_possible_truncation)] Duration::from_nanos(average_duration(&timers) as u64), bench_iterations, ) } fn average_duration(numbers: &[Duration]) -> u128 { - numbers.iter().map(|d| d.as_nanos()).sum::() / numbers.len() as u128 + numbers + .iter() + .map(std::time::Duration::as_nanos) + .sum::() + / numbers.len() as u128 } fn format_duration(duration: &Duration, samples: u128) -> String { if samples == 1 { - format!(" ({:.1?})", duration) + format!(" ({duration:.1?})") } else { - format!(" ({:.1?} @ {} samples)", duration, samples) + format!(" ({duration:.1?} @ {samples} samples)") } } @@ -91,33 +97,30 @@ fn print_result(result: &Option, part: &str, duration_str: &str) match result { Some(result) => { if result.to_string().contains('\n') { - let str = format!("{}: ▼ {}", part, duration_str); + let str = format!("{part}: ▼ {duration_str}"); if is_intermediate_result { - print!("{}", str); + print!("{str}"); } else { print!("\r"); - println!("{}", str); - println!("{}", result); + println!("{str}"); + println!("{result}"); } } else { - let str = format!( - "{}: {}{}{}{}", - part, ANSI_BOLD, result, ANSI_RESET, duration_str - ); + let str = format!("{part}: {ANSI_BOLD}{result}{ANSI_RESET}{duration_str}"); if is_intermediate_result { - print!("{}", str); + print!("{str}"); } else { print!("\r"); - println!("{}", str); + println!("{str}"); } } } None => { if is_intermediate_result { - print!("{}: ✖", part); + print!("{part}: ✖"); } else { print!("\r"); - println!("{}: ✖ ", part); + println!("{part}: ✖ "); } } } @@ -130,7 +133,7 @@ fn submit_result( result: T, day: u8, part: u8, -) -> Option> { +) -> Option> { let args: Vec = env::args().collect(); if !args.contains(&"--submit".into()) { @@ -143,12 +146,10 @@ fn submit_result( } let part_index = args.iter().position(|x| x == "--submit").unwrap() + 1; - let part_submit = match args[part_index].parse::() { - Ok(x) => x, - Err(_) => { - eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1"); - process::exit(1); - } + + let Ok(part_submit) = args[part_index].parse::() else { + eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1"); + process::exit(1); }; if part_submit != part {