diff --git a/Cargo.lock b/Cargo.lock index e7a3585..05c86e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,10 +8,10 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", - "colonnade", "derive_more", "env_logger", "log", + "prettytable-rs", "ron", "serde", "solutions_advent_of_code_2023", @@ -97,6 +97,12 @@ dependencies = [ "serde", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.16" @@ -137,16 +143,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "colonnade" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833230a1f610c0f3418a80b03c387be9de15b1172b3785b702961ead07e3e475" -dependencies = [ - "strip-ansi-escapes", - "unicode-segmentation", -] - [[package]] name = "colorchoice" version = "1.0.2" @@ -159,7 +155,7 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ - "encode_unicode", + "encode_unicode 0.3.6", "lazy_static", "libc", "windows-sys", @@ -174,6 +170,27 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -196,12 +213,39 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "env_filter" version = "0.1.2" @@ -225,12 +269,29 @@ dependencies = [ "log", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "humantime" version = "2.1.0" @@ -249,12 +310,29 @@ dependencies = [ "similar", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "lazy_static" version = "1.5.0" @@ -267,6 +345,16 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -285,6 +373,20 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "prettytable-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" +dependencies = [ + "csv", + "encode_unicode 1.0.0", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -303,6 +405,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.6" @@ -344,6 +457,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "serde" version = "1.0.209" @@ -380,15 +505,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "strip-ansi-escapes" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" -dependencies = [ - "vte", -] - [[package]] name = "strsim" version = "0.11.1" @@ -406,6 +522,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.63" @@ -438,6 +565,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "unicode-xid" version = "0.2.5" @@ -451,24 +584,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "vte" -version = "0.11.1" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "utf8parse", - "vte_generate_state_changes", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "vte_generate_state_changes" -version = "0.1.2" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" -dependencies = [ - "proc-macro2", - "quote", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 74454c5..232596b 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,4 +15,4 @@ env_logger = "0.11.5" log = "0.4.22" anyhow = "1.0.86" ron = "0.8.1" -colonnade = "1.3.3" +prettytable-rs = "0.10.0" diff --git a/crates/cli/src/benchmarking.rs b/crates/cli/src/benchmarking.rs index 9ffb343..eb555eb 100644 --- a/crates/cli/src/benchmarking.rs +++ b/crates/cli/src/benchmarking.rs @@ -1,8 +1,7 @@ pub use conducted_benchmark::ConductedBenchmark; pub use file_to_benchmark::FileToBenchmark; -use row_builder::COLUMN_NUMBER; -use row_building::MatrixReport; +use prettytable::Table; mod conducted_benchmark; mod file_to_benchmark; @@ -15,12 +14,11 @@ use crate::{ AppError, AppResult, }; use anyhow::anyhow; -use colonnade::Colonnade; use std::time::{Duration, Instant}; pub type BenchmarkResult = Result; -pub fn execute_benchmark(args: &BenchmarkCli) -> AppResult { +pub fn execute_benchmark(args: &BenchmarkCli) -> AppResult { let loaded = load_benchmarks(args)?; let benchmarked = loaded.into_iter().map(solve_and_keep_track_of_runtime); @@ -32,19 +30,18 @@ pub fn execute_benchmark(args: &BenchmarkCli) -> AppResult { count += 1; }); let average = calc_average(sum, count); - let header = row_building::create_header(); - let table: MatrixReport = after_header + let mut table = Table::new(); + let rows = after_header .into_iter() .chain(row_building::create_sum_row(sum)) .chain(row_building::create_average_row(average)) - .chain(header) - .collect(); - let lines = Colonnade::new(COLUMN_NUMBER, 100) - .unwrap() - .tabulate(table)?; + .chain(row_building::create_header()); + for next_row in rows { + table.add_row(next_row); + } - Ok(lines.join("\n")) + Ok(table) } fn solve_and_keep_track_of_runtime( diff --git a/crates/cli/src/benchmarking/row_builder.rs b/crates/cli/src/benchmarking/row_builder.rs index 2e4d36a..bf33731 100644 --- a/crates/cli/src/benchmarking/row_builder.rs +++ b/crates/cli/src/benchmarking/row_builder.rs @@ -1,8 +1,10 @@ -use std::{path::PathBuf, time::Duration}; +use std::{borrow::Cow, path::PathBuf, time::Duration}; const AVERAGE: &str = "Average"; const TOTAL: &str = "total"; +use prettytable::{color, Row}; + use crate::{ benchmarking::ConductedBenchmark, cli::{GivenDay, GivenTask}, @@ -10,16 +12,16 @@ use crate::{ solving_given::NoSolutionFound, }; -pub const COLUMN_NUMBER: usize = 6; +type OptionalContent = Option>; #[derive(Debug, Default)] pub struct RowBuilder { - day: Option, - task: Option, - taken_time: Option, - actual_result: Option, - expected_result: Option, - path_to_input: Option, + day: OptionalContent, + task: OptionalContent, + taken_time: OptionalContent, + actual_result: OptionalContent, + expected_result: OptionalContent, + path_to_input: OptionalContent, } impl From for RowBuilder { @@ -46,7 +48,16 @@ impl From for RowBuilder { ) } } +fn into_borrowed_cell(some_thing: &'static str) -> OptionalContent { + Some(Cow::Borrowed(some_thing)) +} +fn into_owned_cell(some_thing: impl ToString) -> OptionalContent { + Some(Cow::Owned(some_thing.to_string())) +} +fn into_owned_cell_with(some_thing: T, on_convert: impl Fn(T) -> String) -> OptionalContent { + Some(Cow::Owned(on_convert(some_thing))) +} impl RowBuilder { pub fn new( day: GivenDay, @@ -57,17 +68,22 @@ impl RowBuilder { path: PathBuf, ) -> Self { Self { - day: Some(day.to_string()), - task: Some(task.to_string()), - taken_time: Some(taken_time), - actual_result: Some(actual_result), - expected_result: Some(expected_result), - path_to_input: Some(path.to_string_lossy().to_string()), + day: into_owned_cell(day), + task: into_owned_cell(task), + taken_time: into_owned_cell_with( + taken_time, + ConductedBenchmark::convert_duration_to_secs_and_mili_txt, + ), + actual_result: into_owned_cell(actual_result), + expected_result: into_owned_cell(expected_result), + path_to_input: into_owned_cell_with(path, |path| path.to_string_lossy().to_string()), } } pub fn no_day_found(wrong_day: GivenDay) -> Self { - let day = Some(format!("Solution found for day {}", wrong_day)); + let day = into_owned_cell_with(wrong_day, |to_convert| { + format!("Solution found for day {}", to_convert) + }); Self { day, ..Default::default() @@ -75,8 +91,10 @@ impl RowBuilder { } pub fn no_task_found(right_day: GivenDay, wrong_task: GivenTask) -> Self { - let day = Some(right_day.to_string()); - let task = Some(format!("No solution found for task {}", wrong_task)); + let day = into_owned_cell(right_day); + let task = into_owned_cell_with(wrong_task, |to_convert| { + format!("No solution found for task {}", to_convert) + }); Self { day, task, @@ -84,6 +102,17 @@ impl RowBuilder { } } + pub fn header() -> Self { + Self { + day: into_borrowed_cell("Day"), + task: into_borrowed_cell("Task"), + taken_time: into_borrowed_cell("How long (Seconds:Nano)"), + actual_result: into_borrowed_cell("Actual"), + expected_result: into_borrowed_cell("Expected"), + path_to_input: into_borrowed_cell("Used input file"), + } + } + pub fn average(average: Duration) -> Self { Self::new_aggreate(AVERAGE, average) } @@ -92,28 +121,49 @@ impl RowBuilder { Self::new_aggreate(TOTAL, total) } - pub fn into_row(self) -> Vec { - fn this_or_placeholder(value: Option) -> String { - value.unwrap_or_else(|| constants::PLACEHOLDER_IN_BENCHMARK_REPORTS.to_string()) + fn this_or_placeholder(value: OptionalContent) -> prettytable::Cell { + let value = value.unwrap_or(Cow::Borrowed(constants::PLACEHOLDER_IN_BENCHMARK_REPORTS)); + prettytable::Cell::new(&value) + } + fn color_red_if_non_static_value(actual_result: OptionalContent) -> prettytable::Cell { + let has_different_acutual_than_expected_and_is_no_label = actual_result + .as_ref() + .is_some_and(|might_be_borrow| matches!(might_be_borrow, Cow::Owned(_))); + let tmp = Self::this_or_placeholder(actual_result); + if has_different_acutual_than_expected_and_is_no_label { + tmp.with_style(prettytable::Attr::ForegroundColor(color::RED)) + } else { + tmp } + } + pub fn into_row(self) -> Row { + let might_colored_red_actual = Self::color_red_if_non_static_value(self.actual_result); let (day, task, taken_time, actual_result, expected_result, path) = ( - this_or_placeholder(self.day), - this_or_placeholder(self.task), - this_or_placeholder( - self.taken_time - .map(ConductedBenchmark::convert_duration_to_secs_and_mili_txt), - ), - this_or_placeholder(self.actual_result), - this_or_placeholder(self.expected_result), - this_or_placeholder(self.path_to_input), + Self::this_or_placeholder(self.day), + Self::this_or_placeholder(self.task), + Self::this_or_placeholder(self.taken_time), + might_colored_red_actual, + Self::this_or_placeholder(self.expected_result), + Self::this_or_placeholder(self.path_to_input), ); - vec![day, task, taken_time, actual_result, expected_result, path] + + prettytable::Row::new(vec![ + day, + task, + taken_time, + actual_result, + expected_result, + path, + ]) } fn new_aggreate(label: &str, aggregate: Duration) -> Self { - let (day, task) = (Some(label.to_string()), Some(label.to_string())); - let taken_time = Some(aggregate); + let (day, task) = (into_owned_cell(label), into_owned_cell(label)); + let taken_time = into_owned_cell_with( + aggregate, + ConductedBenchmark::convert_duration_to_secs_and_mili_txt, + ); Self { day, task, diff --git a/crates/cli/src/benchmarking/row_building.rs b/crates/cli/src/benchmarking/row_building.rs index 3061d1a..33f2b51 100644 --- a/crates/cli/src/benchmarking/row_building.rs +++ b/crates/cli/src/benchmarking/row_building.rs @@ -1,18 +1,13 @@ -pub type MatrixReport = Vec>; -pub type SingleRow = std::iter::Once>; +pub type MatrixReport = Vec; +pub type SingleRow = std::iter::Once; + +use prettytable::Row; use super::{row_builder::RowBuilder, BenchmarkResult}; use std::time::Duration; -pub fn create_header() -> std::iter::Once> { - std::iter::once(vec![ - "Day".to_string(), - "Task".to_string(), - "How long (Seconds:Mili)".to_string(), - "Expected".to_string(), - "Actual".to_string(), - "Used input file".to_string(), - ]) +pub fn create_header() -> SingleRow { + std::iter::once(RowBuilder::header().into_row()) } pub fn create_sum_row(sum: Duration) -> SingleRow { diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 4752be4..f2f5d05 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,3 +1,7 @@ +use std::fmt::Display; + +use prettytable::Table; + pub mod benchmarking; pub mod cli; pub mod constants; @@ -6,3 +10,27 @@ pub mod solving_given; pub type AppError = anyhow::Error; pub type AppResult = anyhow::Result; + +#[derive(Debug)] +pub enum AppOutput { + JustString(String), + PrettyTable(Table), +} + +impl AppOutput { + pub fn print_std_out(&self) { + match self { + AppOutput::JustString(string) => println!("{}", string), + AppOutput::PrettyTable(table) => table.printstd(), + } + } +} + +impl Display for AppOutput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AppOutput::JustString(string) => f.write_str(string), + AppOutput::PrettyTable(table) => f.write_str(&table.to_string()), + } + } +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2a589c4..858f9c3 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -4,7 +4,7 @@ use advent_of_code_2023::{ benchmarking::execute_benchmark, cli::{AppCliArgs, AppCliSubcommands}, solving_given::solve_given_from_cli, - AppResult, + AppOutput, AppResult, }; use clap::Parser; @@ -14,7 +14,7 @@ fn main() -> ExitCode { let solution = handle_command(&args); match solution { Ok(found_solution) => { - println!("{}", found_solution); + found_solution.print_std_out(); ExitCode::SUCCESS } Err(error) => { @@ -24,9 +24,13 @@ fn main() -> ExitCode { } } -fn handle_command(args: &AppCliArgs) -> AppResult { +fn handle_command(args: &AppCliArgs) -> AppResult { match args.sub_command() { - AppCliSubcommands::Solve(to_solve) => solve_given_from_cli(to_solve), - AppCliSubcommands::Benchmark(benchmark) => execute_benchmark(benchmark), + AppCliSubcommands::Solve(to_solve) => { + solve_given_from_cli(to_solve).map(AppOutput::JustString) + } + AppCliSubcommands::Benchmark(benchmark) => { + execute_benchmark(benchmark).map(AppOutput::PrettyTable) + } } } diff --git a/crates/solutions/src/day6.rs b/crates/solutions/src/day6.rs index 92b5c87..c98a6c5 100644 --- a/crates/solutions/src/day6.rs +++ b/crates/solutions/src/day6.rs @@ -14,6 +14,7 @@ pub fn solve_task_1(input: &str) -> String { } how_many_times_won.to_string() } + pub fn solve_task_2(input: &str) -> String { let parsed = parsing::parsing_part_2(input);