refactor: fix most pedantic clippy warnings

closes #29

Co-authored-by: Andy Pymont <andypymont@gmail.com>
This commit is contained in:
Felix Spöttel 2023-10-31 19:46:31 +01:00
parent f46d1e209c
commit d7af3dca9e
10 changed files with 107 additions and 114 deletions

View file

@ -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<AppArgs, Box<dyn std::error::Error>> {
pub fn parse() -> Result<AppArguments, Box<dyn std::error::Error>> {
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),
},
};
}

View file

@ -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<Output, AocCliError> {
pub fn read(day: u8) -> Result<Output, AocCommandError> {
let puzzle_path = get_puzzle_path(day);
let args = build_args(
@ -49,7 +49,7 @@ pub fn read(day: u8) -> Result<Output, AocCliError> {
call_aoc_cli(&args)
}
pub fn download(day: u8) -> Result<Output, AocCliError> {
pub fn download(day: u8) -> Result<Output, AocCommandError> {
let input_path = get_input_path(day);
let puzzle_path = get_puzzle_path(day);
@ -72,7 +72,7 @@ pub fn download(day: u8) -> Result<Output, AocCliError> {
Ok(output)
}
pub fn submit(day: u8, part: u8, result: &str) -> Result<Output, AocCliError> {
pub fn submit(day: u8, part: u8, result: &str) -> Result<Output, AocCommandError> {
// 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<Output, AocCliError> {
}
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<u16> {
@ -110,18 +110,18 @@ fn build_args(command: &str, args: &[String], day: u8) -> Vec<String> {
cmd_args
}
fn call_aoc_cli(args: &[String]) -> Result<Output, AocCliError> {
fn call_aoc_cli(args: &[String]) -> Result<Output, AocCommandError> {
// 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))
}
}

View file

@ -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<Timings> = 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::<f64>() / 1000000_f64;
let total_millis = timings.iter().map(|x| x.total_nanos).sum::<f64>() / 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<std::io::Error> 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<Vec<String>, 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 => {
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::<f64>().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))

View file

@ -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);
};
}

View file

@ -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);
};
}

View file

@ -40,27 +40,27 @@ fn create_file(path: &str) -> Result<File, std::io::Error> {
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);
}
}

View file

@ -1,7 +1,7 @@
use std::process::{Command, Stdio};
pub fn solve_handler(day: u8, release: bool, time: bool, submit_part: Option<u8>) {
let day_padded = format!("{:02}", day);
pub fn handle(day: u8, release: bool, time: bool, submit_part: Option<u8>) {
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<u8>
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 {

View file

@ -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")
}

View file

@ -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<TablePosition, Error> {
@ -62,12 +62,12 @@ fn construct_table(prefix: &str, timings: Vec<Timings>, total_millis: f64) -> St
let mut lines: Vec<String> = 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<Timings>, 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")

View file

@ -9,7 +9,7 @@ use std::{cmp, env, process};
use super::ANSI_BOLD;
pub fn run_part<I: Clone, T: Display>(func: impl Fn(I) -> Option<T>, 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<I: Clone, T>(
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<I: Clone, T>(
fn bench<I: Clone, T>(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<I: Clone, T>(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::<u128>() / numbers.len() as u128
numbers
.iter()
.map(std::time::Duration::as_nanos)
.sum::<u128>()
/ 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<T: Display>(result: &Option<T>, 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<T: Display>(
result: T,
day: u8,
part: u8,
) -> Option<Result<Output, aoc_cli::AocCliError>> {
) -> Option<Result<Output, aoc_cli::AocCommandError>> {
let args: Vec<String> = env::args().collect();
if !args.contains(&"--submit".into()) {
@ -143,12 +146,10 @@ fn submit_result<T: Display>(
}
let part_index = args.iter().position(|x| x == "--submit").unwrap() + 1;
let part_submit = match args[part_index].parse::<u8>() {
Ok(x) => x,
Err(_) => {
let Ok(part_submit) = args[part_index].parse::<u8>() else {
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
process::exit(1);
}
};
if part_submit != part {