From 0000001094eba12515fdfc08abcbedb7d7dc3e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Mon, 28 Nov 2022 19:06:17 +0100 Subject: [PATCH] feat: prepare, solve, solve all --- .cargo/config | 3 ++ .env.example | 2 + .gitignore | 18 +++++-- Cargo.lock | 16 ++++++ Cargo.toml | 11 ++++ README.md | 69 +++++++++++++++++++++++++ src/bin/prepare.rs | 109 +++++++++++++++++++++++++++++++++++++++ src/helpers.rs | 0 src/lib.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 46 +++++++++++++++++ 10 files changed, 394 insertions(+), 5 deletions(-) create mode 100644 .cargo/config create mode 100644 .env.example create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/bin/prepare.rs create mode 100644 src/helpers.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 000000000..d54dc61 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,3 @@ +[alias] +prepare = "run --bin prepare --" +solve = "run --bin" diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..6d859af --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +TOKEN=secret +YEAR=2022 diff --git a/.gitignore b/.gitignore index 088ba6b..b4e7e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,18 @@ # Generated by Cargo # will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +debug/ +target/ # These are backup files generated by rustfmt **/*.rs.bk + +# aoc-template +# env vars +.env + +# downloaded inputs +src/inputs/ +!src/inputs/keep + +src/test_inputs/ +!src/test_inputs/keep diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..2713f18 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aoc" +version = "0.1.0" +dependencies = [ + "pico-args", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..440285a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "aoc" +version = "0.1.0" +edition = "2021" +authors = ["Matej JaneΕΎič "] +default-run = "aoc" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pico-args = "0.5.0" diff --git a/README.md b/README.md new file mode 100644 index 000000000..18ed0d3 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# Advent-of-Code {YEAR} +*This is a dumbed down version of [fspoettel/advent-of-code-rust](https://github.com/fspoettel/advent-of-code-rust/blob/main/Cargo.toml) with some extra features* + +## CLI +### Prepare + +```sh +# example: `cargo prepare 1` +cargo prepare + +# output: +# Created module "src/bin/01.rs" +# Created empty input file "src/inputs/01.txt" +# Created empty example file "src/examples/01.txt" +# --- +# πŸŽ„ Type `cargo solve 01` to run your solution. +``` + +### Solve +```sh +# example: `cargo solve 01` +cargo solve + +# output: +# Running `target/debug/01` +# πŸŽ„ Part 1 πŸŽ„ +# +# 6 (elapsed: 37.03Β΅s) +# +# πŸŽ„ Part 2 πŸŽ„ +# +# 9 (elapsed: 33.18Β΅s) +``` +Displayed timings show the raw execution time of your solution without overhead (e.g. file reads). To run an optimized version for benchmarking, append the `--release` flag. + + +### Solve all + +```sh +cargo all + +# output: +# Running `target/release/aoc` +# ---------- +# | Day 01 | +# ---------- +# πŸŽ„ Part 1 πŸŽ„ +# +# 0 (elapsed: 170.00Β΅s) +# +# πŸŽ„ Part 2 πŸŽ„ +# +# 0 (elapsed: 30.00Β΅s) +# <...other days...> +# Total: 0.20ms +``` + +`all` is an alias for `cargo run`. To run an optimized version for benchmarking, append the `--release` flag. + +### Run against test inputs +run all solutions +```sh +cargo test +``` +run for a given day +```sh +cargo test --bin +``` + diff --git a/src/bin/prepare.rs b/src/bin/prepare.rs new file mode 100644 index 000000000..7daeb30 --- /dev/null +++ b/src/bin/prepare.rs @@ -0,0 +1,109 @@ +/* + * This file contains template code. + * There is no need to edit this file unless you want to change template functionality. + */ +use std::{ + fs::{File, OpenOptions}, + io::Write, + process, +}; + +const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> Option { + None +} +pub fn part_two(input: &str) -> Option { + None +} +fn main() { + let input = &aoc::read_file("inputs", DAY); + aoc::solve!(1, part_one, input); + aoc::solve!(2, part_two, input); +} +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + let input = aoc::read_file("test_inputs", DAY); + assert_eq!(part_one(&input), None); + } + #[test] + fn test_part_two() { + let input = aoc::read_file("test_inputs", DAY); + assert_eq!(part_two(&input), None); + } +} +"###; + +fn parse_args() -> Result { + let mut args = pico_args::Arguments::from_env(); + args.free_from_str() +} + +fn safe_create_file(path: &str) -> Result { + OpenOptions::new().write(true).create_new(true).open(path) +} + +fn create_file(path: &str) -> Result { + OpenOptions::new().write(true).create(true).open(path) +} + +fn main() { + let day = match parse_args() { + Ok(day) => day, + Err(_) => { + eprintln!("Need to specify a day (as integer). example: `cargo prepare 7`"); + process::exit(1); + } + }; + + let day_padded = format!("{:02}", day); + + let input_path = format!("src/inputs/{}.txt", day_padded); + let example_path = format!("src/test_inputs/{}.txt", day_padded); + let module_path = format!("src/bin/{}.rs", day_padded); + + let mut file = match safe_create_file(&module_path) { + Ok(file) => file, + Err(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(_) => { + println!("Created module file \"{}\"", &module_path); + } + Err(e) => { + eprintln!("Failed to write module contents: {}", e); + process::exit(1); + } + } + + match create_file(&input_path) { + Ok(_) => { + println!("Created empty input file \"{}\"", &input_path); + } + Err(e) => { + eprintln!("Failed to create input file: {}", e); + process::exit(1); + } + } + + match create_file(&example_path) { + Ok(_) => { + println!("Created empty example file \"{}\"", &example_path); + } + Err(e) => { + eprintln!("Failed to create example file: {}", e); + process::exit(1); + } + } + + println!("---"); + println!( + "πŸŽ„ Type `cargo solve {}` to run your solution.", + &day_padded + ); +} diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 000000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..f15e303 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,125 @@ +/* + * This file contains template code. + * There is no need to edit this file unless you want to change template functionality. + * Prefer `./helpers.rs` if you want to extract code from your solutions. + */ +use std::env; +use std::fs; + +pub mod helpers; + +pub const ANSI_ITALIC: &str = "\x1b[3m"; +pub const ANSI_BOLD: &str = "\x1b[1m"; +pub const ANSI_RESET: &str = "\x1b[0m"; + +#[macro_export] +macro_rules! solve { + ($part:expr, $solver:ident, $input:expr) => {{ + use aoc::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET}; + use std::fmt::Display; + use std::time::Instant; + + fn print_result(func: impl FnOnce(&str) -> Option, input: &str) { + let timer = Instant::now(); + let result = func(input); + let elapsed = timer.elapsed(); + match result { + Some(result) => { + println!( + "{} {}(elapsed: {:.2?}){}", + result, ANSI_ITALIC, elapsed, ANSI_RESET + ); + } + None => { + println!("not solved.") + } + } + } + + println!("πŸŽ„ {}Part {}{} πŸŽ„", ANSI_BOLD, $part, ANSI_RESET); + print_result($solver, $input); + }}; +} + +pub fn read_file(folder: &str, day: u8) -> String { + let cwd = env::current_dir().unwrap(); + + let filepath = cwd.join("src").join(folder).join(format!("{:02}.txt", day)); + + let f = fs::read_to_string(filepath); + f.expect("could not open input file") +} + +fn parse_time(val: &str, postfix: &str) -> f64 { + val.split(postfix).next().unwrap().parse().unwrap() +} + +pub fn parse_exec_time(output: &str) -> f64 { + output.lines().fold(0_f64, |acc, l| { + if !l.contains("elapsed:") { + acc + } else { + let timing = l.split("(elapsed: ").last().unwrap(); + // use `contains` istd. of `ends_with`: string may contain ANSI escape sequences. + // for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200 + if timing.contains("ns)") { + acc // range below rounding precision. + } else if timing.contains("Β΅s)") { + acc + parse_time(timing, "Β΅s") / 1000_f64 + } else if timing.contains("ms)") { + acc + parse_time(timing, "ms") + } else if timing.contains("s)") { + acc + parse_time(timing, "s") * 1000_f64 + } else { + acc + } + } + }) +} + +/// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333 +#[cfg(test)] +macro_rules! assert_approx_eq { + ($a:expr, $b:expr) => {{ + let (a, b) = (&$a, &$b); + assert!( + (*a - *b).abs() < 1.0e-6, + "{} is not approximately equal to {}", + *a, + *b + ); + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_exec_time() { + assert_approx_eq!( + parse_exec_time(&format!( + "πŸŽ„ Part 1 πŸŽ„\n0 (elapsed: 74.13ns){}\nπŸŽ„ Part 2 πŸŽ„\n0 (elapsed: 50.00ns){}", + ANSI_RESET, ANSI_RESET + )), + 0_f64 + ); + + assert_approx_eq!( + parse_exec_time("πŸŽ„ Part 1 πŸŽ„\n0 (elapsed: 755Β΅s)\nπŸŽ„ Part 2 πŸŽ„\n0 (elapsed: 700Β΅s)"), + 1.455_f64 + ); + + assert_approx_eq!( + parse_exec_time("πŸŽ„ Part 1 πŸŽ„\n0 (elapsed: 70Β΅s)\nπŸŽ„ Part 2 πŸŽ„\n0 (elapsed: 1.45ms)"), + 1.52_f64 + ); + + assert_approx_eq!( + parse_exec_time( + "πŸŽ„ Part 1 πŸŽ„\n0 (elapsed: 10.3s)\nπŸŽ„ Part 2 πŸŽ„\n0 (elapsed: 100.50ms)" + ), + 10400.50_f64 + ); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..fd13201 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,46 @@ +/* + * This file contains template code. + * There is no need to edit this file unless you want to change template functionality. + */ +use aoc::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET}; +use std::process::Command; + +fn main() { + let total: f64 = (1..=25) + .map(|day| { + let day = format!("{:02}", day); + + let cmd = Command::new("cargo") + .args(["run", "--release", "--bin", &day]) + .output() + .unwrap(); + + println!("----------"); + println!("{}| Day {} |{}", ANSI_BOLD, day, ANSI_RESET); + println!("----------"); + + let output = String::from_utf8(cmd.stdout).unwrap(); + let is_empty = output.is_empty(); + + println!( + "{}", + if is_empty { + "Not solved." + } else { + output.trim() + } + ); + + if is_empty { + 0_f64 + } else { + aoc::parse_exec_time(&output) + } + }) + .sum(); + + println!( + "{}Total:{} {}{:.2}ms{}", + ANSI_BOLD, ANSI_RESET, ANSI_ITALIC, total, ANSI_RESET + ); +}