From da18209bdc1cd4ee68a7139ffa6d15ac16e3b741 Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Sat, 14 Dec 2024 17:25:25 -0800 Subject: [PATCH 1/8] initial commit of root src/ changes from my personal repository --- .cargo/config.toml | 5 +- src/bin/.keep | 0 src/main.rs | 35 ++++++- src/template.txt | 8 +- src/template/aoc_cli.rs | 15 +-- src/template/commands/attempt.rs | 37 +++++++ src/template/commands/mod.rs | 37 +++++++ src/template/commands/new_year.rs | 161 ++++++++++++++++++++++++++++++ src/template/commands/scaffold.rs | 12 ++- src/template/commands/set_year.rs | 58 +++++++++++ src/template/commands/solve.rs | 10 +- src/template/mod.rs | 27 +++-- src/template/readme_benchmarks.rs | 3 +- src/template/run_multi.rs | 10 +- 14 files changed, 387 insertions(+), 31 deletions(-) delete mode 100644 src/bin/.keep create mode 100644 src/template/commands/attempt.rs create mode 100644 src/template/commands/new_year.rs create mode 100644 src/template/commands/set_year.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index cf0d80d..2f82933 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,7 +3,10 @@ today = "run --quiet --release --features today -- today" scaffold = "run --quiet --release -- scaffold" download = "run --quiet --release -- download" read = "run --quiet --release -- read" - +set-year = "run --quiet --release -- set-year" +new-year = "run --quiet --release -- new-year" +get-year = "run --quiet --release -- get-year" +try = "run --quiet --release -- try" solve = "run --quiet --release -- solve" all = "run --quiet --release -- all" time = "run --quiet --release -- time" diff --git a/src/bin/.keep b/src/bin/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.rs b/src/main.rs index 2a360fc..e4e1300 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ -use advent_of_code::template::commands::{all, download, read, scaffold, solve, time}; +use advent_of_code::template::commands::{ + all, attempt, download, new_year, read, scaffold, set_year, solve, time, +}; use args::{parse, AppArguments}; #[cfg(feature = "today")] @@ -28,6 +30,11 @@ mod args { dhat: bool, submit: Option, }, + Try { + day: Day, + release: bool, + dhat: bool, + }, All { release: bool, }, @@ -36,6 +43,13 @@ mod args { day: Option, store: bool, }, + NewYear { + year: u32, + }, + SetYear { + year: u32, + }, + GetYear, #[cfg(feature = "today")] Today, } @@ -74,6 +88,18 @@ mod args { submit: args.opt_value_from_str("--submit")?, dhat: args.contains("--dhat"), }, + Some("try") => AppArguments::Try { + day: args.free_from_str()?, + release: args.contains("--submit"), + dhat: args.contains("--dhat"), + }, + Some("new-year") => AppArguments::NewYear { + year: args.free_from_str()?, + }, + Some("set-year") => AppArguments::SetYear { + year: args.free_from_str()?, + }, + Some("get-year") => AppArguments::GetYear, #[cfg(feature = "today")] Some("today") => AppArguments::Today, Some(x) => { @@ -122,6 +148,7 @@ fn main() { dhat, submit, } => solve::handle(day, release, dhat, submit), + AppArguments::Try { day, release, dhat } => attempt::handle(day, release, dhat), #[cfg(feature = "today")] AppArguments::Today => { match Day::today() { @@ -139,6 +166,12 @@ fn main() { } }; } + AppArguments::NewYear { year } => new_year::handle(year), + AppArguments::SetYear { year } => set_year::handle(year), + AppArguments::GetYear => { + let year = advent_of_code::template::get_year_exit_on_fail(); + println!("The repository is currently set to {}", year); + } }, }; } diff --git a/src/template.txt b/src/template.txt index 87eac99..409af68 100644 --- a/src/template.txt +++ b/src/template.txt @@ -1,4 +1,4 @@ -advent_of_code::solution!(%DAY_NUMBER%); +advent_of_code_YEAR_NUMBER::solution!(DAY_NUMBER); pub fn part_one(input: &str) -> Option { None @@ -14,13 +14,15 @@ mod tests { #[test] fn test_part_one() { - let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); + let result = part_one(&input); assert_eq!(result, None); } #[test] fn test_part_two() { - let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); + let result = part_two(&input); assert_eq!(result, None); } } diff --git a/src/template/aoc_cli.rs b/src/template/aoc_cli.rs index 2d3300d..7dc7265 100644 --- a/src/template/aoc_cli.rs +++ b/src/template/aoc_cli.rs @@ -81,24 +81,19 @@ pub fn submit(day: Day, part: u8, result: &str) -> Result String { - format!("data/inputs/{day}.txt") + let year = crate::template::get_year_exit_on_fail(); + format!("{year}/data/inputs/{day}.txt") } fn get_puzzle_path(day: Day) -> String { - format!("data/puzzles/{day}.md") -} - -fn get_year() -> Option { - match std::env::var("AOC_YEAR") { - Ok(x) => x.parse().ok().or(None), - Err(_) => None, - } + let year = crate::template::get_year_exit_on_fail(); + format!("{year}/data/puzzles/{day}.md") } fn build_args(command: &str, args: &[String], day: Day) -> Vec { let mut cmd_args = args.to_vec(); - if let Some(year) = get_year() { + if let Some(year) = super::get_year() { cmd_args.push("--year".into()); cmd_args.push(year.to_string()); } diff --git a/src/template/commands/attempt.rs b/src/template/commands/attempt.rs new file mode 100644 index 0000000..66ff8af --- /dev/null +++ b/src/template/commands/attempt.rs @@ -0,0 +1,37 @@ +use std::process::{Command, Stdio}; + +use crate::template::Day; + +pub fn handle(day: Day, release: bool, dhat: bool) { + let year = crate::template::get_year_exit_on_fail(); + let year = format!("advent_of_code_{}", year); + let mut cmd_args = vec![ + "test".to_string(), + "-p".to_string(), + year, + "--bin".to_string(), + day.to_string(), + ]; + + if dhat { + cmd_args.extend([ + "--profile".to_string(), + "dhat".to_string(), + "--features".to_string(), + "dhat-heap".to_string(), + ]); + } else if release { + cmd_args.push("--release".to_string()); + } + + cmd_args.push("--".to_string()); + + let mut cmd = Command::new("cargo") + .args(&cmd_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + cmd.wait().unwrap(); +} diff --git a/src/template/commands/mod.rs b/src/template/commands/mod.rs index 36be280..5c22018 100644 --- a/src/template/commands/mod.rs +++ b/src/template/commands/mod.rs @@ -1,6 +1,43 @@ +use std::{ + fs::{File, OpenOptions}, + io::Write, + path::PathBuf, +}; + pub mod all; +pub mod attempt; pub mod download; +pub mod new_year; pub mod read; pub mod scaffold; +pub mod set_year; pub mod solve; pub mod time; + +#[derive(Debug)] +enum WriteError { + Open, + Write, +} + +fn open_file(filepath: &PathBuf) -> Result { + OpenOptions::new().write(true).truncate(true).open(filepath) +} + +fn write_file(filepath: &PathBuf, to_write: &[u8]) -> Result<(), WriteError> { + let file = open_file(filepath); + if file.is_err() { + eprintln!("Failed to open file {}", filepath.to_str().unwrap()); + return Err(WriteError::Open); + } + let mut file = file.unwrap(); + + match file.write_all(to_write) { + Ok(()) => Ok(()), + Err(e) => { + let filepath = filepath.to_str().unwrap(); + eprintln!("Failed to write to {filepath}: {e}"); + Err(WriteError::Write) + } + } +} diff --git a/src/template/commands/new_year.rs b/src/template/commands/new_year.rs new file mode 100644 index 0000000..e31ec72 --- /dev/null +++ b/src/template/commands/new_year.rs @@ -0,0 +1,161 @@ +use std::{ + fs::{self}, + path::{Path, PathBuf}, + process::{self, Command, Stdio}, + str::FromStr, +}; + +use crate::template::commands::set_year; + +use super::{write_file, WriteError}; + +const YEAR_NUMBER_FILES: [&str; 7] = [ + "Cargo.toml", + "src/main.rs", + "src/template/aoc_cli.rs", + "src/template/run_multi.rs", + "src/template/template.txt", + "src/template/commands/scaffold.rs", + ".cargo/config.toml", +]; + +pub fn handle(year: u32) { + let project_root = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .join("year_template"); + let new_root = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .join(format!("{}", year)); + + copy_year_template(&project_root, &new_root); + set_year_numbers(year, &new_root); + set_year(year); + add_to_workspace(year); + println!("Created AOC year {} workspace module", year); +} + +fn copy_year_template(project_root: &Path, new_root: &Path) { + let cmd_args = vec![ + project_root.to_str().unwrap(), + &new_root.to_str().unwrap(), + "-r", + ]; + let mut cmd = Command::new("cp") + .args(&cmd_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + cmd.wait().unwrap(); +} + +fn set_year_numbers(year: u32, new_root: &Path) { + for filename in YEAR_NUMBER_FILES { + let filepath = new_root.join(filename); + + let original_contents = match fs::read_to_string(filepath.clone()) { + Ok(original) => original, + Err(_) => { + eprintln!("Could not read from file to set year numbers"); + cleanup(year); + process::exit(1); + } + }; + let mut new_contents = original_contents.clone(); + new_contents = new_contents.replace("%YEAR_NUMBER%", &year.to_string()); + if !filename.contains("scaffold") { + new_contents = new_contents.replace("YEAR_NUMBER", &year.to_string()); + } + let new_contents = new_contents.as_bytes(); + + if write_file(&filepath, new_contents).is_err() { + cleanup(year); + process::exit(1); + } + } +} + +fn set_year(year: u32) { + if !set_year::set_year(year) { + cleanup(year); + process::exit(1); + } +} + +fn add_to_workspace(year: u32) { + let filepath = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .join("Cargo.toml"); + let original_contents = read_toml_file(); + if original_contents.is_err() { + cleanup(year); + process::exit(1); + } + let original_contents = original_contents.unwrap(); + let new_contents = add_year_to_toml_str(year, &original_contents); + match write_file(&filepath, new_contents.to_string().as_bytes()) { + Ok(()) => (), + Err(WriteError::Open) => (), + Err(WriteError::Write) => { + cleanup(year); + write_file(&filepath, original_contents.to_string().as_bytes()).unwrap(); + process::exit(1); + } + } +} + +fn read_toml_file() -> Result { + let filepath = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"); + let f = fs::read_to_string(filepath); + if f.is_err() { + eprintln!("failed to read Cargo.toml"); + return Err(()); + } + Ok(f.unwrap()) +} + +fn add_year_to_toml_str(year: u32, original: &str) -> String { + let end_pos = get_end_pos_of_members(original); + if end_pos.is_err() { + cleanup(year); + process::exit(1); + } + let end_pos = end_pos.unwrap(); + + let start = &original[..end_pos]; + let end = &original[end_pos..]; + let new = format!(", \"{}\"", year); + format!("{}{}{}", start, new, end) +} + +fn get_end_pos_of_members(original: &str) -> Result { + let start_idx = original[..].find("members = ["); + if start_idx.is_none() { + eprintln!("failed to find a members section of Cargo.toml"); + return Err(()); + } + let start_idx = start_idx.unwrap(); + let end_idx = original[start_idx..].find("]"); + match end_idx { + Some(i) => Ok(i + start_idx), + None => { + eprintln!("failed to find the end of the members section of Cargo.toml"); + Err(()) + } + } +} + +fn cleanup(year: u32) { + let mut new_root = String::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); + new_root.push_str(&format!("/{}/", year)); + + let cmd_args = vec![&new_root, "-r"]; + let mut cmd = Command::new("rm") + .args(&cmd_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + cmd.wait().unwrap(); +} diff --git a/src/template/commands/scaffold.rs b/src/template/commands/scaffold.rs index fc3d950..2f49ab8 100644 --- a/src/template/commands/scaffold.rs +++ b/src/template/commands/scaffold.rs @@ -4,7 +4,7 @@ use std::{ process, }; -use crate::template::Day; +use crate::template::{get_year_exit_on_fail, Day}; const MODULE_TEMPLATE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/template.txt")); @@ -28,9 +28,10 @@ fn create_file(path: &str) -> Result { } pub fn handle(day: Day, overwrite: bool) { - let input_path = format!("data/inputs/{day}.txt"); - let example_path = format!("data/examples/{day}.txt"); - let module_path = format!("src/bin/{day}.rs"); + let year = get_year_exit_on_fail(); + let input_path = format!("{year}/data/inputs/{day}.txt"); + let example_path = format!("{year}/data/examples/{day}.txt"); + let module_path = format!("{year}/src/bin/{day}.rs"); let mut file = match safe_create_file(&module_path, overwrite) { Ok(file) => file, @@ -42,7 +43,8 @@ pub fn handle(day: Day, overwrite: bool) { match file.write_all( MODULE_TEMPLATE - .replace("%DAY_NUMBER%", &day.into_inner().to_string()) + .replace("YEAR_NUMBER", &year.to_string()) + .replace("DAY_NUMBER", &day.into_inner().to_string()) .as_bytes(), ) { Ok(()) => { diff --git a/src/template/commands/set_year.rs b/src/template/commands/set_year.rs new file mode 100644 index 0000000..e271ef1 --- /dev/null +++ b/src/template/commands/set_year.rs @@ -0,0 +1,58 @@ +use std::{ + fs::{self}, + path::PathBuf, + process, + str::FromStr, +}; + +use super::write_file; + +pub fn handle(year: u32) { + if !set_year(year) { + process::exit(1); + } + println!("Set repository to AOC year {}", year); +} + +pub fn set_year(year: u32) -> bool { + let config_path = get_config_path(); + let new_aoc_year_line = format!("AOC_YEAR = \"{year}\""); + let config_contents = read_config(&config_path); + if config_contents.is_err() { + return false; + } + let config_contents = config_contents.unwrap(); + let lines = config_contents.lines().map(|x| { + if !x.contains("AOC_YEAR") { + x + } else { + &new_aoc_year_line + } + }); + let new_contents: Vec<&str> = lines.collect(); + let new_contents = new_contents.join("\n"); + + match write_file(&config_path, new_contents.as_bytes()) { + Ok(_) => true, + Err(_) => { + eprintln!("failed to write new year to the config file"); + false + } + } +} + +fn get_config_path() -> PathBuf { + PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .join(".cargo") + .join("config.toml") +} + +fn read_config(filepath: &PathBuf) -> Result { + let f = fs::read_to_string(filepath); + if f.is_err() { + eprintln!("failed to read Cargo.toml"); + return Err(()); + } + Ok(f.unwrap()) +} diff --git a/src/template/commands/solve.rs b/src/template/commands/solve.rs index ec92a6f..72d7510 100644 --- a/src/template/commands/solve.rs +++ b/src/template/commands/solve.rs @@ -3,7 +3,15 @@ use std::process::{Command, Stdio}; use crate::template::Day; pub fn handle(day: Day, release: bool, dhat: bool, submit_part: Option) { - let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day.to_string()]; + let year = crate::template::get_year_exit_on_fail(); + let year = format!("advent_of_code_{}", year); + let mut cmd_args = vec![ + "run".to_string(), + "-p".to_string(), + year, + "--bin".to_string(), + day.to_string(), + ]; if dhat { cmd_args.extend([ diff --git a/src/template/mod.rs b/src/template/mod.rs index dd8e4c0..ee4d01d 100644 --- a/src/template/mod.rs +++ b/src/template/mod.rs @@ -1,15 +1,15 @@ -use std::{env, fs}; +use std::{env, fs, path::PathBuf, str::FromStr}; pub mod aoc_cli; pub mod commands; +pub mod readme_benchmarks; +pub mod run_multi; pub mod runner; +pub mod timings; pub use day::*; mod day; -mod readme_benchmarks; -mod run_multi; -mod timings; pub const ANSI_ITALIC: &str = "\x1b[3m"; pub const ANSI_BOLD: &str = "\x1b[1m"; @@ -18,7 +18,7 @@ pub const ANSI_RESET: &str = "\x1b[0m"; /// Helper function that reads a text file to a string. #[must_use] pub fn read_file(folder: &str, day: Day) -> String { - let cwd = env::current_dir().unwrap(); + let cwd = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); let filepath = cwd.join("data").join(folder).join(format!("{day}.txt")); let f = fs::read_to_string(filepath); f.expect("could not open input file") @@ -27,7 +27,7 @@ pub fn read_file(folder: &str, day: Day) -> String { /// Helper function that reads a text file to string, appending a part suffix. E.g. like `01-2.txt`. #[must_use] pub fn read_file_part(folder: &str, day: Day, part: u8) -> String { - let cwd = env::current_dir().unwrap(); + let cwd = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); let filepath = cwd .join("data") .join(folder) @@ -36,6 +36,21 @@ pub fn read_file_part(folder: &str, day: Day, part: u8) -> String { f.expect("could not open input file") } +pub fn get_year() -> Option { + std::env::var("AOC_YEAR") + .ok() + .and_then(|x| x.parse::().ok()) +} + +pub fn get_year_exit_on_fail() -> u32 { + let year = get_year(); + if year.is_none() { + eprintln!("Failed to get the currently set AOC year"); + std::process::exit(1); + } + year.unwrap() +} + /// Creates the constant `DAY` and sets up the input and runner for each part. /// /// The optional, second parameter (1 or 2) allows you to only run a single part of the solution. diff --git a/src/template/readme_benchmarks.rs b/src/template/readme_benchmarks.rs index 5c42ae4..888d557 100644 --- a/src/template/readme_benchmarks.rs +++ b/src/template/readme_benchmarks.rs @@ -27,7 +27,8 @@ pub struct TablePosition { #[must_use] pub fn get_path_for_bin(day: Day) -> String { - format!("./src/bin/{day}.rs") + let year = crate::template::get_year_exit_on_fail(); + format!("{year}/src/bin/{day}.rs") } fn locate_table(readme: &str) -> Result { diff --git a/src/template/run_multi.rs b/src/template/run_multi.rs index c951faa..790828b 100644 --- a/src/template/run_multi.rs +++ b/src/template/run_multi.rs @@ -61,14 +61,15 @@ impl From for Error { #[must_use] pub fn get_path_for_bin(day: Day) -> String { - format!("./src/bin/{day}.rs") + let year = crate::template::get_year_exit_on_fail(); + format!("{year}/src/bin/{day}.rs") } /// All solutions live in isolated binaries. /// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output. pub mod child_commands { use super::{get_path_for_bin, Error}; - use crate::template::Day; + use crate::template::{get_year_exit_on_fail, Day}; use std::{ io::{BufRead, BufReader}, path::Path, @@ -83,8 +84,11 @@ pub mod child_commands { return Ok(vec![]); } + let year = get_year_exit_on_fail(); + let year = format!("advent_of_code_{}", year); + let day_padded = day.to_string(); - let mut args = vec!["run", "--quiet", "--bin", &day_padded]; + let mut args = vec!["run", "-p", &year, "--quiet", "--bin", &day_padded]; if is_release { args.push("--release"); From fd6f23f83e1ba5323531ab72330318bd5b6c5763 Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Sat, 14 Dec 2024 17:32:03 -0800 Subject: [PATCH 2/8] adding the `year_template` folder for spawning new years --- Cargo.lock | 398 +++++++++++++++++- Cargo.toml | 3 + year_template/.cargo/config.toml | 13 + year_template/Cargo.toml | 25 ++ year_template/data/examples/.keep | 0 year_template/data/inputs/.keep | 0 year_template/data/puzzles/.keep | 0 year_template/src/bin/.keep | 0 year_template/src/lib.rs | 3 + year_template/src/main.rs | 178 ++++++++ year_template/src/template/aoc_cli.rs | 116 +++++ year_template/src/template/commands/all.rs | 5 + .../src/template/commands/attempt.rs | 29 ++ .../src/template/commands/download.rs | 14 + year_template/src/template/commands/mod.rs | 42 ++ year_template/src/template/commands/read.rs | 15 + .../src/template/commands/scaffold.rs | 82 ++++ .../src/template/commands/set_year.rs | 38 ++ year_template/src/template/commands/solve.rs | 34 ++ year_template/src/template/commands/time.rs | 40 ++ year_template/src/template/day.rs | 192 +++++++++ year_template/src/template/mod.rs | 111 +++++ .../src/template/readme_benchmarks.rs | 183 ++++++++ year_template/src/template/run_multi.rs | 257 +++++++++++ year_template/src/template/runner.rs | 169 ++++++++ year_template/src/template/template.txt | 28 ++ year_template/src/template/timings.rs | 384 +++++++++++++++++ year_template/src/utils/.keep | 0 28 files changed, 2348 insertions(+), 11 deletions(-) create mode 100644 year_template/.cargo/config.toml create mode 100644 year_template/Cargo.toml create mode 100644 year_template/data/examples/.keep create mode 100644 year_template/data/inputs/.keep create mode 100644 year_template/data/puzzles/.keep create mode 100644 year_template/src/bin/.keep create mode 100644 year_template/src/lib.rs create mode 100644 year_template/src/main.rs create mode 100644 year_template/src/template/aoc_cli.rs create mode 100644 year_template/src/template/commands/all.rs create mode 100644 year_template/src/template/commands/attempt.rs create mode 100644 year_template/src/template/commands/download.rs create mode 100644 year_template/src/template/commands/mod.rs create mode 100644 year_template/src/template/commands/read.rs create mode 100644 year_template/src/template/commands/scaffold.rs create mode 100644 year_template/src/template/commands/set_year.rs create mode 100644 year_template/src/template/commands/solve.rs create mode 100644 year_template/src/template/commands/time.rs create mode 100644 year_template/src/template/day.rs create mode 100644 year_template/src/template/mod.rs create mode 100644 year_template/src/template/readme_benchmarks.rs create mode 100644 year_template/src/template/run_multi.rs create mode 100644 year_template/src/template/runner.rs create mode 100644 year_template/src/template/template.txt create mode 100644 year_template/src/template/timings.rs create mode 100644 year_template/src/utils/.keep diff --git a/Cargo.lock b/Cargo.lock index 9504be6..231e09d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,28 @@ dependencies = [ "tinyjson", ] +[[package]] +name = "advent_of_code_YEAR_NUMBER" +version = "0.11.0" +dependencies = [ + "chrono", + "dhat", + "graph_builder", + "num", + "pico-args", + "regex", + "tinyjson", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -42,6 +64,27 @@ dependencies = [ "libc", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + [[package]] name = "autocfg" version = "1.1.0" @@ -75,6 +118,18 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.0.83" @@ -110,6 +165,55 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "delegate" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082a24a9967533dc5d743c602157637116fc1b52806d694a5a45e6f32567fcdd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "dhat" version = "0.3.3" @@ -126,12 +230,70 @@ dependencies = [ "thousands", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "graph_builder" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1cfa7a71e06cdd160ce79175bdffd0477bbc97ec943491dd882ba3bd0a2e63" +dependencies = [ + "atoi", + "atomic", + "byte-slice-cast", + "dashmap", + "delegate", + "fast-float", + "fxhash", + "linereader", + "log", + "memmap2", + "num", + "num-format", + "num_cpus", + "page_size", + "parking_lot", + "rayon", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -182,6 +344,15 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "linereader" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d921fea6860357575519aca014c6e22470585accdd543b370c404a8a72d0dd1d" +dependencies = [ + "memchr", +] + [[package]] name = "lock_api" version = "0.4.11" @@ -204,6 +375,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -223,15 +403,99 @@ dependencies = [ "sys-info", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.1" @@ -247,6 +511,16 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -278,22 +552,42 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -303,6 +597,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -344,7 +667,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -366,9 +689,20 @@ checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "syn" -version = "2.0.39" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -385,6 +719,26 @@ dependencies = [ "libc", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "thousands" version = "0.2.0" @@ -424,7 +778,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -446,7 +800,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -457,6 +811,28 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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-core" version = "0.51.1" diff --git a/Cargo.toml b/Cargo.toml index 038a1a3..9149320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" default-run = "advent_of_code" publish = false +[workspace] +members = ["year_template"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] doctest = false diff --git a/year_template/.cargo/config.toml b/year_template/.cargo/config.toml new file mode 100644 index 0000000..1ab0c3f --- /dev/null +++ b/year_template/.cargo/config.toml @@ -0,0 +1,13 @@ +[alias] +today = "run --quiet --release --bin advent_of_code_YEAR_NUMBER --features today -- today" +scaffold = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- scaffold" +download = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- download" +read = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- read" +set-year = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- set-year" +new-year = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- new-year" +get-year = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- get-year" +try = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- try" +solve = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- solve" +all = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- all" +time = "run --quiet --release --bin advent_of_code_YEAR_NUMBER -- time" + diff --git a/year_template/Cargo.toml b/year_template/Cargo.toml new file mode 100644 index 0000000..b143fec --- /dev/null +++ b/year_template/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "advent_of_code_YEAR_NUMBER" +version = "0.11.0" +authors = ["Felix Spöttel <1682504+fspoettel@users.noreply.github.com>"] +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +dhat-heap = ["dhat"] +today = ["chrono"] +test_lib = [] + +[dependencies] + +# Template dependencies +chrono = { version = "0.4.38", optional = true } +dhat = { version = "0.3.3", optional = true } +graph_builder = "0.4.0" +regex = "1.11.1" +pico-args = "0.5.0" +tinyjson = "2.5.1" +num = "0.4.3" + +# Solution dependencies diff --git a/year_template/data/examples/.keep b/year_template/data/examples/.keep new file mode 100644 index 0000000..e69de29 diff --git a/year_template/data/inputs/.keep b/year_template/data/inputs/.keep new file mode 100644 index 0000000..e69de29 diff --git a/year_template/data/puzzles/.keep b/year_template/data/puzzles/.keep new file mode 100644 index 0000000..e69de29 diff --git a/year_template/src/bin/.keep b/year_template/src/bin/.keep new file mode 100644 index 0000000..e69de29 diff --git a/year_template/src/lib.rs b/year_template/src/lib.rs new file mode 100644 index 0000000..27d7df8 --- /dev/null +++ b/year_template/src/lib.rs @@ -0,0 +1,3 @@ +pub mod template; + +// Use this file to add helper functions and additional modules. diff --git a/year_template/src/main.rs b/year_template/src/main.rs new file mode 100644 index 0000000..e00ba91 --- /dev/null +++ b/year_template/src/main.rs @@ -0,0 +1,178 @@ +use advent_of_code_YEAR_NUMBER::template::commands::{ + all, attempt, download, read, scaffold, set_year, solve, time, +}; +use args::{parse, AppArguments}; + +#[cfg(feature = "today")] +use advent_of_code::template::Day; +#[cfg(feature = "today")] +use std::process; + +mod args { + use advent_of_code_YEAR_NUMBER::template::Day; + use std::process; + + pub enum AppArguments { + Download { + day: Day, + }, + Read { + day: Day, + }, + Scaffold { + day: Day, + download: bool, + overwrite: bool, + }, + Solve { + day: Day, + release: bool, + dhat: bool, + submit: Option, + }, + Try { + day: Day, + release: bool, + dhat: bool, + }, + All { + release: bool, + }, + Time { + all: bool, + day: Option, + store: bool, + }, + NewYear, + SetYear { + year: u32, + }, + GetYear, + #[cfg(feature = "today")] + Today, + } + + pub fn parse() -> Result> { + let mut args = pico_args::Arguments::from_env(); + + let app_args = match args.subcommand()?.as_deref() { + Some("all") => AppArguments::All { + release: args.contains("--release"), + }, + Some("time") => { + let all = args.contains("--all"); + let store = args.contains("--store"); + + AppArguments::Time { + all, + day: args.opt_free_from_str()?, + store, + } + } + Some("download") => AppArguments::Download { + day: args.free_from_str()?, + }, + Some("read") => AppArguments::Read { + day: args.free_from_str()?, + }, + Some("scaffold") => AppArguments::Scaffold { + day: args.free_from_str()?, + download: args.contains("--download"), + overwrite: args.contains("--overwrite"), + }, + Some("solve") => AppArguments::Solve { + day: args.free_from_str()?, + release: args.contains("--release"), + submit: args.opt_value_from_str("--submit")?, + dhat: args.contains("--dhat"), + }, + Some("try") => AppArguments::Try { + day: args.free_from_str()?, + release: args.contains("--submit"), + dhat: args.contains("--dhat"), + }, + Some("new-year") => AppArguments::NewYear, + Some("set-year") => AppArguments::SetYear { + year: args.free_from_str()?, + }, + Some("get-year") => AppArguments::GetYear, + #[cfg(feature = "today")] + Some("today") => AppArguments::Today, + Some(x) => { + eprintln!("Unknown command: {x}"); + process::exit(1); + } + None => { + eprintln!("No command specified."); + process::exit(1); + } + }; + + let remaining = args.finish(); + if !remaining.is_empty() { + eprintln!("Warning: unknown argument(s): {remaining:?}."); + } + + Ok(app_args) + } +} + +fn main() { + match parse() { + Err(err) => { + eprintln!("Error: {err}"); + std::process::exit(1); + } + Ok(args) => match args { + AppArguments::All { release } => all::handle(release), + AppArguments::Time { day, all, store } => time::handle(day, all, store), + AppArguments::Download { day } => download::handle(day), + AppArguments::Read { day } => read::handle(day), + AppArguments::Scaffold { + day, + download, + overwrite, + } => { + scaffold::handle(day, overwrite); + if download { + download::handle(day); + } + } + AppArguments::Solve { + day, + release, + dhat, + submit, + } => solve::handle(day, release, dhat, submit), + AppArguments::Try { day, release, dhat } => attempt::handle(day, release, dhat), + #[cfg(feature = "today")] + AppArguments::Today => { + match Day::today() { + Some(day) => { + scaffold::handle(day, false); + download::handle(day); + read::handle(day) + } + None => { + eprintln!( + "`today` command can only be run between the 1st and \ + the 25th of december. Please use `scaffold` with a specific day." + ); + process::exit(1) + } + }; + } + AppArguments::NewYear => { + println!("You can only generate new year folders at the project root"); + } + AppArguments::SetYear { year } => { + set_year::handle(year); + println!("Set repository to AOC year {}", year); + } + AppArguments::GetYear => { + let year = advent_of_code_YEAR_NUMBER::template::get_year_exit_on_fail(); + println!("The repository is currently set to {}", year); + } + }, + }; +} diff --git a/year_template/src/template/aoc_cli.rs b/year_template/src/template/aoc_cli.rs new file mode 100644 index 0000000..4b7c573 --- /dev/null +++ b/year_template/src/template/aoc_cli.rs @@ -0,0 +1,116 @@ +/// Wrapper module around the "aoc-cli" command-line. +use std::{ + fmt::Display, + process::{Command, Output, Stdio}, +}; + +use crate::template::Day; + +#[derive(Debug)] +pub enum AocCommandError { + CommandNotFound, + CommandNotCallable, + BadExitStatus(Output), +} + +impl Display for AocCommandError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + 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.") + } + } + } +} + +pub fn check() -> Result<(), AocCommandError> { + Command::new("aoc") + .arg("-V") + .output() + .map_err(|_| AocCommandError::CommandNotFound)?; + Ok(()) +} + +pub fn read(day: Day) -> Result { + let puzzle_path = get_puzzle_path(day); + + let args = build_args( + "read", + &[ + "--description-only".into(), + "--puzzle-file".into(), + puzzle_path, + ], + day, + ); + + call_aoc_cli(&args) +} + +pub fn download(day: Day) -> Result { + let input_path = get_input_path(day); + let puzzle_path = get_puzzle_path(day); + + let args = build_args( + "download", + &[ + "--overwrite".into(), + "--input-file".into(), + input_path.to_string(), + "--puzzle-file".into(), + puzzle_path.to_string(), + ], + day, + ); + + let output = call_aoc_cli(&args)?; + println!("---"); + println!("🎄 Successfully wrote input to \"{}\".", &input_path); + println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path); + Ok(output) +} + +pub fn submit(day: Day, 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()); + args.push(result.to_string()); + call_aoc_cli(&args) +} + +fn get_input_path(day: Day) -> String { + format!("data/inputs/{day}.txt") +} + +fn get_puzzle_path(day: Day) -> String { + format!("data/puzzles/{day}.md") +} + +fn build_args(command: &str, args: &[String], day: Day) -> Vec { + let mut cmd_args = args.to_vec(); + + cmd_args.push("--year".into()); + cmd_args.push("%YEAR_NUMBER%".to_string()); + + cmd_args.append(&mut vec!["--day".into(), day.to_string(), command.into()]); + + cmd_args +} + +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(|_| AocCommandError::CommandNotCallable)?; + + if output.status.success() { + Ok(output) + } else { + Err(AocCommandError::BadExitStatus(output)) + } +} diff --git a/year_template/src/template/commands/all.rs b/year_template/src/template/commands/all.rs new file mode 100644 index 0000000..b844e1e --- /dev/null +++ b/year_template/src/template/commands/all.rs @@ -0,0 +1,5 @@ +use crate::template::{all_days, run_multi::run_multi}; + +pub fn handle(is_release: bool) { + run_multi(&all_days().collect(), is_release, false); +} diff --git a/year_template/src/template/commands/attempt.rs b/year_template/src/template/commands/attempt.rs new file mode 100644 index 0000000..4a0537d --- /dev/null +++ b/year_template/src/template/commands/attempt.rs @@ -0,0 +1,29 @@ +use std::process::{Command, Stdio}; + +use crate::template::Day; + +pub fn handle(day: Day, release: bool, dhat: bool) { + let mut cmd_args = vec!["test".to_string(), "--bin".to_string(), day.to_string()]; + + if dhat { + cmd_args.extend([ + "--profile".to_string(), + "dhat".to_string(), + "--features".to_string(), + "dhat-heap".to_string(), + ]); + } else if release { + cmd_args.push("--release".to_string()); + } + + cmd_args.push("--".to_string()); + + let mut cmd = Command::new("cargo") + .args(&cmd_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + cmd.wait().unwrap(); +} diff --git a/year_template/src/template/commands/download.rs b/year_template/src/template/commands/download.rs new file mode 100644 index 0000000..9274f05 --- /dev/null +++ b/year_template/src/template/commands/download.rs @@ -0,0 +1,14 @@ +use crate::template::{aoc_cli, Day}; +use std::process; + +pub fn handle(day: Day) { + 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}"); + process::exit(1); + }; +} diff --git a/year_template/src/template/commands/mod.rs b/year_template/src/template/commands/mod.rs new file mode 100644 index 0000000..f6caf7a --- /dev/null +++ b/year_template/src/template/commands/mod.rs @@ -0,0 +1,42 @@ +use std::{ + fs::{File, OpenOptions}, + io::Write, + path::PathBuf, +}; + +pub mod all; +pub mod attempt; +pub mod download; +pub mod read; +pub mod scaffold; +pub mod set_year; +pub mod solve; +pub mod time; + +#[derive(Debug)] +enum WriteError { + Open, + Write, +} + +fn open_file(filepath: &PathBuf) -> Result { + OpenOptions::new().write(true).truncate(true).open(filepath) +} + +fn write_file(filepath: &PathBuf, to_write: &[u8]) -> Result<(), WriteError> { + let file = open_file(filepath); + if file.is_err() { + eprintln!("Failed to open file {}", filepath.to_str().unwrap()); + return Err(WriteError::Open); + } + let mut file = file.unwrap(); + + match file.write_all(to_write) { + Ok(()) => Ok(()), + Err(e) => { + let filepath = filepath.to_str().unwrap(); + eprintln!("Failed to write to {filepath}: {e}"); + Err(WriteError::Write) + } + } +} diff --git a/year_template/src/template/commands/read.rs b/year_template/src/template/commands/read.rs new file mode 100644 index 0000000..3e1a307 --- /dev/null +++ b/year_template/src/template/commands/read.rs @@ -0,0 +1,15 @@ +use std::process; + +use crate::template::{aoc_cli, Day}; + +pub fn handle(day: Day) { + 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}"); + process::exit(1); + }; +} diff --git a/year_template/src/template/commands/scaffold.rs b/year_template/src/template/commands/scaffold.rs new file mode 100644 index 0000000..558f418 --- /dev/null +++ b/year_template/src/template/commands/scaffold.rs @@ -0,0 +1,82 @@ +use std::{ + fs::{File, OpenOptions}, + io::Write, + process, +}; + +use crate::template::Day; + +const MODULE_TEMPLATE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/template/template.txt" +)); + +fn safe_create_file(path: &str, overwrite: bool) -> Result { + let mut file = OpenOptions::new(); + if overwrite { + file.create(true); + } else { + file.create_new(true); + } + file.truncate(true).write(true).open(path) +} + +fn create_file(path: &str) -> Result { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) +} + +pub fn handle(day: Day, overwrite: bool) { + let input_path = format!("data/inputs/{day}.txt"); + let example_path = format!("data/examples/{day}.txt"); + let module_path = format!("src/bin/{day}.rs"); + + let mut file = match safe_create_file(&module_path, overwrite) { + Ok(file) => file, + Err(e) => { + eprintln!("Failed to create module file: {e}"); + process::exit(1); + } + }; + + match file.write_all( + MODULE_TEMPLATE + .replace("YEAR_NUMBER", "%YEAR_NUMBER%") + .replace("DAY_NUMBER", &day.into_inner().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 {day}` to run your solution."); +} diff --git a/year_template/src/template/commands/set_year.rs b/year_template/src/template/commands/set_year.rs new file mode 100644 index 0000000..1ca4edf --- /dev/null +++ b/year_template/src/template/commands/set_year.rs @@ -0,0 +1,38 @@ +use std::process; + +use crate::template::{get_config_path, read_config}; + +use super::write_file; + +pub fn handle(year: u32) { + if set_year(year) { + process::exit(1); + } +} + +pub fn set_year(year: u32) -> bool { + let config_path = get_config_path(); + let new_aoc_year_line = format!("AOC_YEAR = \"{year}\""); + let config_contents = read_config(&config_path); + if config_contents.is_err() { + return false; + } + let config_contents = config_contents.unwrap(); + let lines = config_contents.lines().map(|x| { + if !x.contains("AOC_YEAR") { + x + } else { + &new_aoc_year_line + } + }); + let new_contents: Vec<&str> = lines.collect(); + let new_contents = new_contents.join("\n"); + + match write_file(&config_path, new_contents.as_bytes()) { + Ok(_) => true, + Err(_) => { + eprintln!("failed to write new year to the config file"); + false + } + } +} diff --git a/year_template/src/template/commands/solve.rs b/year_template/src/template/commands/solve.rs new file mode 100644 index 0000000..ec92a6f --- /dev/null +++ b/year_template/src/template/commands/solve.rs @@ -0,0 +1,34 @@ +use std::process::{Command, Stdio}; + +use crate::template::Day; + +pub fn handle(day: Day, release: bool, dhat: bool, submit_part: Option) { + let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day.to_string()]; + + if dhat { + cmd_args.extend([ + "--profile".to_string(), + "dhat".to_string(), + "--features".to_string(), + "dhat-heap".to_string(), + ]); + } else if release { + cmd_args.push("--release".to_string()); + } + + cmd_args.push("--".to_string()); + + if let Some(submit_part) = submit_part { + cmd_args.push("--submit".to_string()); + cmd_args.push(submit_part.to_string()); + } + + let mut cmd = Command::new("cargo") + .args(&cmd_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + cmd.wait().unwrap(); +} diff --git a/year_template/src/template/commands/time.rs b/year_template/src/template/commands/time.rs new file mode 100644 index 0000000..49b91a8 --- /dev/null +++ b/year_template/src/template/commands/time.rs @@ -0,0 +1,40 @@ +use std::collections::HashSet; + +use crate::template::run_multi::run_multi; +use crate::template::timings::Timings; +use crate::template::{all_days, readme_benchmarks, Day}; + +pub fn handle(day: Option, run_all: bool, store: bool) { + let stored_timings = Timings::read_from_file(); + + let days_to_run = day.map_or_else( + || { + if run_all { + all_days().collect() + } else { + // when the `--all` flag is not set, filter out days that are fully benched. + all_days() + .filter(|day| !stored_timings.is_day_complete(*day)) + .collect() + } + }, + |day| HashSet::from([day]), + ); + + let timings = run_multi(&days_to_run, true, true).unwrap(); + + if store { + let merged_timings = stored_timings.merge(&timings); + merged_timings.store_file().unwrap(); + + println!(); + match readme_benchmarks::update(merged_timings) { + Ok(()) => { + println!("Stored updated benchmarks."); + } + Err(_) => { + eprintln!("Failed to store updated benchmarks."); + } + } + } +} diff --git a/year_template/src/template/day.rs b/year_template/src/template/day.rs new file mode 100644 index 0000000..99b8280 --- /dev/null +++ b/year_template/src/template/day.rs @@ -0,0 +1,192 @@ +use std::error::Error; +use std::fmt::Display; +use std::str::FromStr; + +#[cfg(feature = "today")] +use chrono::{Datelike, FixedOffset, Utc}; + +#[cfg(feature = "today")] +const SERVER_UTC_OFFSET: i32 = -5; + +/// A valid day number of advent (i.e. an integer in range 1 to 25). +/// +/// # Display +/// This value displays as a two digit number. +/// +/// ``` +/// # use advent_of_code::Day; +/// let day = Day::new(8).unwrap(); +/// assert_eq!(day.to_string(), "08") +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Day(u8); + +impl Day { + /// Creates a [`Day`] from the provided value if it's in the valid range, + /// returns [`None`] otherwise. + pub fn new(day: u8) -> Option { + if day == 0 || day > 25 { + return None; + } + Some(Self(day)) + } + + // Not part of the public API + #[doc(hidden)] + pub const fn __new_unchecked(day: u8) -> Self { + Self(day) + } + + /// Converts the [`Day`] into an [`u8`]. + pub fn into_inner(self) -> u8 { + self.0 + } +} + +#[cfg(feature = "today")] +impl Day { + /// Returns the current day if it's between the 1st and the 25th of december, `None` otherwise. + pub fn today() -> Option { + let offset = FixedOffset::east_opt(SERVER_UTC_OFFSET * 3600)?; + let today = Utc::now().with_timezone(&offset); + if today.month() == 12 && today.day() <= 25 { + Self::new(u8::try_from(today.day()).ok()?) + } else { + None + } + } +} + +impl Display for Day { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:02}", self.0) + } +} + +impl PartialEq for Day { + fn eq(&self, other: &u8) -> bool { + self.0.eq(other) + } +} + +impl PartialOrd for Day { + fn partial_cmp(&self, other: &u8) -> Option { + self.0.partial_cmp(other) + } +} + +/* -------------------------------------------------------------------------- */ + +impl FromStr for Day { + type Err = DayFromStrError; + + fn from_str(s: &str) -> Result { + let day = s.parse().map_err(|_| DayFromStrError)?; + Self::new(day).ok_or(DayFromStrError) + } +} + +/// An error which can be returned when parsing a [`Day`]. +#[derive(Debug)] +pub struct DayFromStrError; + +impl Error for DayFromStrError {} + +impl Display for DayFromStrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("expecting a day number between 1 and 25") + } +} + +/* -------------------------------------------------------------------------- */ + +/// An iterator that yields every day of advent from the 1st to the 25th. +pub fn all_days() -> AllDays { + AllDays::new() +} + +/// An iterator that yields every day of advent from the 1st to the 25th. +pub struct AllDays { + current: u8, +} + +impl AllDays { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { current: 1 } + } +} + +impl Iterator for AllDays { + type Item = Day; + + fn next(&mut self) -> Option { + if self.current > 25 { + return None; + } + // NOTE: the iterator starts at 1 and we have verified that the value is not above 25. + let day = Day(self.current); + self.current += 1; + + Some(day) + } +} + +/* -------------------------------------------------------------------------- */ + +/// Creates a [`Day`] value in a const context. +#[macro_export] +macro_rules! day { + ($day:expr) => {{ + const _ASSERT: () = assert!( + $day != 0 && $day <= 25, + concat!( + "invalid day number `", + $day, + "`, expecting a value between 1 and 25" + ), + ); + $crate::template::Day::__new_unchecked($day) + }}; +} + +/* -------------------------------------------------------------------------- */ + +#[cfg(feature = "test_lib")] +mod tests { + use super::{all_days, Day}; + + #[test] + fn all_days_iterator() { + let mut iter = all_days(); + + assert_eq!(iter.next(), Some(Day(1))); + assert_eq!(iter.next(), Some(Day(2))); + assert_eq!(iter.next(), Some(Day(3))); + assert_eq!(iter.next(), Some(Day(4))); + assert_eq!(iter.next(), Some(Day(5))); + assert_eq!(iter.next(), Some(Day(6))); + assert_eq!(iter.next(), Some(Day(7))); + assert_eq!(iter.next(), Some(Day(8))); + assert_eq!(iter.next(), Some(Day(9))); + assert_eq!(iter.next(), Some(Day(10))); + assert_eq!(iter.next(), Some(Day(11))); + assert_eq!(iter.next(), Some(Day(12))); + assert_eq!(iter.next(), Some(Day(13))); + assert_eq!(iter.next(), Some(Day(14))); + assert_eq!(iter.next(), Some(Day(15))); + assert_eq!(iter.next(), Some(Day(16))); + assert_eq!(iter.next(), Some(Day(17))); + assert_eq!(iter.next(), Some(Day(18))); + assert_eq!(iter.next(), Some(Day(19))); + assert_eq!(iter.next(), Some(Day(20))); + assert_eq!(iter.next(), Some(Day(21))); + assert_eq!(iter.next(), Some(Day(22))); + assert_eq!(iter.next(), Some(Day(23))); + assert_eq!(iter.next(), Some(Day(24))); + assert_eq!(iter.next(), Some(Day(25))); + assert_eq!(iter.next(), None); + } +} + +/* -------------------------------------------------------------------------- */ diff --git a/year_template/src/template/mod.rs b/year_template/src/template/mod.rs new file mode 100644 index 0000000..3f0147c --- /dev/null +++ b/year_template/src/template/mod.rs @@ -0,0 +1,111 @@ +use std::{env, fs, path::PathBuf, process, str::FromStr}; + +pub mod aoc_cli; +pub mod commands; +pub mod readme_benchmarks; +pub mod run_multi; +pub mod runner; +pub mod timings; + +pub use day::*; + +mod day; + +pub const ANSI_ITALIC: &str = "\x1b[3m"; +pub const ANSI_BOLD: &str = "\x1b[1m"; +pub const ANSI_RESET: &str = "\x1b[0m"; + +/// Helper function that reads a text file to a string. +#[must_use] +pub fn read_file(folder: &str, day: Day) -> String { + let cwd = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); + let filepath = cwd.join("data").join(folder).join(format!("{day}.txt")); + let f = fs::read_to_string(filepath); + f.expect("could not open input file") +} + +/// Helper function that reads a text file to string, appending a part suffix. E.g. like `01-2.txt`. +#[must_use] +pub fn read_file_part(folder: &str, day: Day, part: u8) -> String { + let cwd = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); + let filepath = cwd + .join("data") + .join(folder) + .join(format!("{day}-{part}.txt")); + let f = fs::read_to_string(filepath); + f.expect("could not open input file") +} + +pub fn get_year() -> Option { + let config_path = get_config_path(); + let config_contents = read_config(&config_path); + if let Err(()) = config_contents { + process::exit(1); + } + let config_contents = config_contents.unwrap(); + let year: String = config_contents + .lines() + .filter(|s| s.contains("AOC_YEAR")) + .collect(); + let year: Vec<&str> = year.split("\"").collect(); + let year = year.get(year.len() - 2).unwrap(); + year.parse::().ok() +} + +pub fn get_year_exit_on_fail() -> u32 { + let year = get_year(); + if year.is_none() { + eprintln!("Failed to get the currently set AOC year"); + std::process::exit(1); + } + year.unwrap() +} + +fn get_config_path() -> PathBuf { + let config_path = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .join("..") + .join(".cargo") + .join("config.toml"); + config_path.canonicalize().unwrap() +} + +fn read_config(filepath: &PathBuf) -> Result { + let f = fs::read_to_string(filepath); + if f.is_err() { + eprintln!("failed to read Cargo.toml"); + return Err(()); + } + Ok(f.unwrap()) +} + +/// Creates the constant `DAY` and sets up the input and runner for each part. +/// +/// The optional, second parameter (1 or 2) allows you to only run a single part of the solution. +#[macro_export] +macro_rules! solution { + ($day:expr) => { + $crate::solution!(@impl $day, [part_one, 1] [part_two, 2]); + }; + ($day:expr, 1) => { + $crate::solution!(@impl $day, [part_one, 1]); + }; + ($day:expr, 2) => { + $crate::solution!(@impl $day, [part_two, 2]); + }; + + (@impl $day:expr, $( [$func:expr, $part:expr] )*) => { + /// The current day. + const DAY: $crate::template::Day = $crate::day!($day); + + #[cfg(feature = "dhat-heap")] + #[global_allocator] + static ALLOC: dhat::Alloc = dhat::Alloc; + + fn main() { + use $crate::template::runner::*; + let input = $crate::template::read_file("inputs", DAY); + $( run_part($func, &input, DAY, $part); )* + } + }; +} diff --git a/year_template/src/template/readme_benchmarks.rs b/year_template/src/template/readme_benchmarks.rs new file mode 100644 index 0000000..6a9522d --- /dev/null +++ b/year_template/src/template/readme_benchmarks.rs @@ -0,0 +1,183 @@ +/// Module that updates the readme me with timing information. +/// The approach taken is similar to how `aoc-readme-stars` handles this. +use std::{fs, io}; + +use crate::template::timings::Timings; +use crate::template::Day; + +static MARKER: &str = ""; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error { + Parser(String), + IO(io::Error), +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IO(e) + } +} + +pub struct TablePosition { + pos_start: usize, + pos_end: usize, +} + +#[must_use] +pub fn get_path_for_bin(day: Day) -> String { + format!("src/bin/{day}.rs") +} + +fn locate_table(readme: &str) -> Result { + let matches: Vec<_> = readme.match_indices(MARKER).collect(); + + if matches.len() > 2 { + return Err(Error::Parser( + "{}: too many occurences of marker in README.".into(), + )); + } + + let pos_start = matches + .first() + .map(|m| m.0) + .ok_or_else(|| Error::Parser("Could not find table start position.".into()))?; + + let pos_end = matches + .last() + .map(|m| m.0 + m.1.len()) + .ok_or_else(|| Error::Parser("Could not find table end position.".into()))?; + + Ok(TablePosition { pos_start, pos_end }) +} + +fn construct_table(prefix: &str, timings: Timings, total_millis: f64) -> String { + let header = format!("{prefix} Benchmarks"); + + let mut lines: Vec = vec![ + MARKER.into(), + header, + String::new(), + "| Day | Part 1 | Part 2 |".into(), + "| :---: | :---: | :---: |".into(), + ]; + + for timing in timings.data { + let path = get_path_for_bin(timing.day); + lines.push(format!( + "| [Day {}]({}) | `{}` | `{}` |", + timing.day.into_inner(), + path, + timing.part_1.unwrap_or_else(|| "-".into()), + timing.part_2.unwrap_or_else(|| "-".into()) + )); + } + + lines.push(String::new()); + lines.push(format!("**Total: {total_millis:.2}ms**")); + lines.push(MARKER.into()); + + lines.join("\n") +} + +fn update_content(s: &mut String, timings: Timings, total_millis: f64) -> Result<(), Error> { + let positions = locate_table(s)?; + let table = construct_table("##", timings, total_millis); + s.replace_range(positions.pos_start..positions.pos_end, &table); + Ok(()) +} + +pub fn update(timings: Timings) -> Result<(), Error> { + let path = "README.md"; + let mut readme = String::from_utf8_lossy(&fs::read(path)?).to_string(); + let total_millis = timings.total_millis(); + update_content(&mut readme, timings, total_millis)?; + fs::write(path, &readme)?; + Ok(()) +} + +#[cfg(feature = "test_lib")] +mod tests { + use super::{update_content, MARKER}; + use crate::{day, template::timings::Timing, template::timings::Timings}; + + fn get_mock_timings() -> Timings { + Timings { + data: vec![ + Timing { + day: day!(1), + part_1: Some("10ms".into()), + part_2: Some("20ms".into()), + total_nanos: 3e+10, + }, + Timing { + day: day!(2), + part_1: Some("30ms".into()), + part_2: Some("40ms".into()), + total_nanos: 7e+10, + }, + Timing { + day: day!(4), + part_1: Some("40ms".into()), + part_2: Some("50ms".into()), + total_nanos: 9e+10, + }, + ], + } + } + + #[test] + #[should_panic] + fn errors_if_marker_not_present() { + let mut s = "# readme".to_string(); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + } + + #[test] + #[should_panic] + fn errors_if_too_many_markers_present() { + let mut s = format!("{} {} {}", MARKER, MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + } + + #[test] + fn updates_empty_benchmarks() { + let mut s = format!("foo\nbar\n{}{}\nbaz", MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + assert_eq!(s.contains("## Benchmarks"), true); + } + + #[test] + fn updates_existing_benchmarks() { + let mut s = format!("foo\nbar\n{}{}\nbaz", MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + assert_eq!(s.matches(MARKER).collect::>().len(), 2); + assert_eq!(s.matches("## Benchmarks").collect::>().len(), 1); + } + + #[test] + fn format_benchmarks() { + let mut s = format!("foo\nbar\n{}\n{}\nbaz", MARKER, MARKER); + update_content(&mut s, get_mock_timings(), 190.0).unwrap(); + let expected = [ + "foo", + "bar", + "", + "## Benchmarks", + "", + "| Day | Part 1 | Part 2 |", + "| :---: | :---: | :---: |", + "| [Day 1](./src/bin/01.rs) | `10ms` | `20ms` |", + "| [Day 2](./src/bin/02.rs) | `30ms` | `40ms` |", + "| [Day 4](./src/bin/04.rs) | `40ms` | `50ms` |", + "", + "**Total: 190.00ms**", + "", + "baz", + ] + .join("\n"); + assert_eq!(s, expected); + } +} diff --git a/year_template/src/template/run_multi.rs b/year_template/src/template/run_multi.rs new file mode 100644 index 0000000..e501115 --- /dev/null +++ b/year_template/src/template/run_multi.rs @@ -0,0 +1,257 @@ +use std::{collections::HashSet, io}; + +use crate::template::{Day, ANSI_BOLD, ANSI_ITALIC, ANSI_RESET}; + +use super::{ + all_days, + timings::{Timing, Timings}, +}; + +pub fn run_multi(days_to_run: &HashSet, is_release: bool, is_timed: bool) -> Option { + let mut timings: Vec = Vec::with_capacity(days_to_run.len()); + + let mut need_space = false; + + // NOTE: use non-duplicate, sorted day values. + all_days() + .filter(|day| days_to_run.contains(day)) + .for_each(|day| { + if need_space { + println!(); + } + need_space = true; + + println!("{ANSI_BOLD}Day {day}{ANSI_RESET}"); + println!("------"); + + let output = child_commands::run_solution(day, is_timed, is_release).unwrap(); + + if output.is_empty() { + println!("Not solved."); + } else { + let val = child_commands::parse_exec_time(&output, day); + timings.push(val); + } + }); + + if is_timed { + let timings = Timings { data: timings }; + let total_millis = timings.total_millis(); + println!( + "\n{ANSI_BOLD}Total (Run):{ANSI_RESET} {ANSI_ITALIC}{total_millis:.2}ms{ANSI_RESET}" + ); + Some(timings) + } else { + None + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error { + BrokenPipe, + IO(io::Error), +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IO(e) + } +} + +#[must_use] +pub fn get_path_for_bin(day: Day) -> String { + format!("src/bin/{day}.rs") +} + +/// All solutions live in isolated binaries. +/// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output. +pub mod child_commands { + use super::{get_path_for_bin, Error}; + use crate::template::Day; + use std::{ + io::{BufRead, BufReader}, + path::Path, + process::{Command, Stdio}, + thread, + }; + + /// Run the solution bin for a given day + pub fn run_solution(day: Day, is_timed: bool, is_release: bool) -> Result, Error> { + // skip command invocation for days that have not been scaffolded yet. + if !Path::new(&get_path_for_bin(day)).exists() { + return Ok(vec![]); + } + + let day_padded = day.to_string(); + let mut args = vec!["run", "--quiet", "--bin", &day_padded]; + + if is_release { + args.push("--release"); + } + + if is_timed { + // mirror `--time` flag to child invocations. + args.push("--"); + args.push("--time"); + } + + // spawn child command with piped stdout/stderr. + // forward output to stdout/stderr while grabbing stdout lines. + + let mut cmd = Command::new("cargo") + .args(&args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let stdout = BufReader::new(cmd.stdout.take().ok_or(super::Error::BrokenPipe)?); + let stderr = BufReader::new(cmd.stderr.take().ok_or(super::Error::BrokenPipe)?); + + let mut output = vec![]; + + let thread = thread::spawn(move || { + stderr.lines().for_each(|line| { + eprintln!("{}", line.unwrap()); + }); + }); + + for line in stdout.lines() { + let line = line.unwrap(); + println!("{line}"); + output.push(line); + } + + thread.join().unwrap(); + cmd.wait()?; + + Ok(output) + } + + pub fn parse_exec_time(output: &[String], day: Day) -> super::Timing { + let mut timings = super::Timing { + day, + part_1: None, + part_2: None, + total_nanos: 0_f64, + }; + + output + .iter() + .filter_map(|l| { + if !l.contains(" samples)") { + 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()?; + Some((part, timing_str, nanos)) + }) + .for_each(|(part, timing_str, nanos)| { + if part.contains("Part 1") { + timings.part_1 = Some(timing_str.into()); + } else if part.contains("Part 2") { + timings.part_2 = Some(timing_str.into()); + } + + timings.total_nanos += nanos; + }); + + timings + } + + fn parse_to_float(s: &str, postfix: &str) -> Option { + s.split(postfix).next()?.parse().ok() + } + + fn parse_time(line: &str) -> Option<(&str, f64)> { + // for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200 + let str_timing = line + .split(" samples)") + .next()? + .split('(') + .last()? + .split('@') + .next()? + .trim(); + + 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 * 1_000_000_f64), + s => parse_to_float(s, "s").map(|x| x * 1_000_000_000_f64), + }?; + + Some((str_timing, parsed_timing)) + } + + /// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333 + #[cfg(feature = "test_lib")] + 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(feature = "test_lib")] + mod tests { + use super::parse_exec_time; + + use crate::day; + + #[test] + fn parses_execution_times() { + let res = parse_exec_time( + &[ + "Part 1: 0 (74.13ns @ 100000 samples)".into(), + "Part 2: 10 (74.13ms @ 99999 samples)".into(), + "".into(), + ], + day!(1), + ); + assert_approx_eq!(res.total_nanos, 74130074.13_f64); + assert_eq!(res.part_1.unwrap(), "74.13ns"); + assert_eq!(res.part_2.unwrap(), "74.13ms"); + } + + #[test] + fn parses_with_patterns_in_input() { + let res = parse_exec_time( + &[ + "Part 1: @ @ @ ( ) ms (2s @ 5 samples)".into(), + "Part 2: 10s (100ms @ 1 samples)".into(), + "".into(), + ], + day!(1), + ); + assert_approx_eq!(res.total_nanos, 2100000000_f64); + assert_eq!(res.part_1.unwrap(), "2s"); + assert_eq!(res.part_2.unwrap(), "100ms"); + } + + #[test] + fn parses_missing_parts() { + let res = parse_exec_time( + &[ + "Part 1: ✖ ".into(), + "Part 2: ✖ ".into(), + "".into(), + ], + day!(1), + ); + assert_approx_eq!(res.total_nanos, 0_f64); + assert_eq!(res.part_1.is_none(), true); + assert_eq!(res.part_2.is_none(), true); + } + } +} diff --git a/year_template/src/template/runner.rs b/year_template/src/template/runner.rs new file mode 100644 index 0000000..0a48767 --- /dev/null +++ b/year_template/src/template/runner.rs @@ -0,0 +1,169 @@ +/// Encapsulates code that interacts with solution functions. +use std::fmt::Display; +use std::hint::black_box; +use std::io::{stdout, Write}; +use std::process::Output; +use std::time::{Duration, Instant}; +use std::{cmp, env, process}; + +use crate::template::ANSI_BOLD; +use crate::template::{aoc_cli, Day, ANSI_ITALIC, ANSI_RESET}; + +pub fn run_part(func: impl Fn(I) -> Option, input: I, day: Day, part: u8) { + let part_str = format!("Part {part}"); + + let (result, duration, samples) = + run_timed(func, input, |result| print_result(result, &part_str, "")); + + print_result(&result, &part_str, &format_duration(&duration, samples)); + + if let Some(result) = result { + submit_result(result, day, part); + } +} + +/// Run a solution part. The behavior differs depending on whether we are running a release or debug build: +/// 1. in debug, the function is executed once. +/// 2. in release, the function is benched (approx. 1 second of execution time or 10 samples, whatever take longer.) +fn run_timed( + func: impl Fn(I) -> T, + input: I, + hook: impl Fn(&T), +) -> (T, Duration, u128) { + let timer = Instant::now(); + let result = { + let input = input.clone(); + + #[cfg(feature = "dhat-heap")] + let _profiler = dhat::Profiler::new_heap(); + + func(input) + }; + let base_time = timer.elapsed(); + + hook(&result); + + let run = if std::env::args().any(|x| x == "--time") { + bench(func, input, &base_time) + } else { + (base_time, 1) + }; + + (result, run.0, run.1) +} + +fn bench(func: impl Fn(I) -> T, input: I, base_time: &Duration) -> (Duration, u128) { + let mut stdout = stdout(); + + print!(" > {ANSI_ITALIC}benching{ANSI_RESET}"); + let _ = stdout.flush(); + + let bench_iterations = + (Duration::from_secs(1).as_nanos() / cmp::max(base_time.as_nanos(), 10)).clamp(10, 10000); + + let mut timers: Vec = vec![]; + + for _ in 0..bench_iterations { + // need a clone here to make the borrow checker happy. + let cloned = input.clone(); + let timer = Instant::now(); + black_box(func(black_box(cloned))); + timers.push(timer.elapsed()); + } + + ( + #[allow(clippy::cast_possible_truncation)] + Duration::from_nanos(average_duration(&timers) as u64), + bench_iterations, + ) +} + +fn average_duration(numbers: &[Duration]) -> 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!(" ({duration:.1?})") + } else { + format!(" ({duration:.1?} @ {samples} samples)") + } +} + +fn print_result(result: &Option, part: &str, duration_str: &str) { + let is_intermediate_result = duration_str.is_empty(); + + match result { + Some(result) => { + if result.to_string().contains('\n') { + let str = format!("{part}: ▼ {duration_str}"); + if is_intermediate_result { + print!("{str}"); + } else { + print!("\r"); + println!("{str}"); + println!("{result}"); + } + } else { + let str = format!("{part}: {ANSI_BOLD}{result}{ANSI_RESET}{duration_str}"); + if is_intermediate_result { + print!("{str}"); + } else { + print!("\r"); + println!("{str}"); + } + } + } + None => { + if is_intermediate_result { + print!("{part}: ✖"); + } else { + print!("\r"); + println!("{part}: ✖ "); + } + } + } +} + +/// Parse the arguments passed to `solve` and try to submit one part of the solution if: +/// 1. we are in `--release` mode. +/// 2. aoc-cli is installed. +fn submit_result( + result: T, + day: Day, + part: u8, +) -> Option> { + let args: Vec = env::args().collect(); + + if !args.contains(&"--submit".into()) { + return None; + } + + if args.len() < 3 { + eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1"); + process::exit(1); + } + + let part_index = args.iter().position(|x| x == "--submit").unwrap() + 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 { + return None; + } + + 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); + } + + println!("Submitting result via aoc-cli..."); + Some(aoc_cli::submit(day, part, &result.to_string())) +} diff --git a/year_template/src/template/template.txt b/year_template/src/template/template.txt new file mode 100644 index 0000000..13e8209 --- /dev/null +++ b/year_template/src/template/template.txt @@ -0,0 +1,28 @@ +advent_of_code_YEAR_NUMBER::solution!(DAY_NUMBER); + +pub fn part_one(input: &str) -> Option { + None +} + +pub fn part_two(input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); + let result = part_one(&input); + assert_eq!(result, None); + } + + #[test] + fn test_part_two() { + let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); + let result = part_two(&input); + assert_eq!(result, None); + } +} \ No newline at end of file diff --git a/year_template/src/template/timings.rs b/year_template/src/template/timings.rs new file mode 100644 index 0000000..fb79835 --- /dev/null +++ b/year_template/src/template/timings.rs @@ -0,0 +1,384 @@ +use std::{collections::HashMap, fs, io::Error, str::FromStr}; +use tinyjson::JsonValue; + +use crate::template::Day; + +static TIMINGS_FILE_PATH: &str = "./data/timings.json"; + +/// Represents benchmark times for a single day. +#[derive(Clone, Debug)] +pub struct Timing { + pub day: Day, + pub part_1: Option, + pub part_2: Option, + pub total_nanos: f64, +} + +/// Represents benchmark times for a set of days. +/// Can be serialized from / to JSON. +#[derive(Clone, Debug, Default)] +pub struct Timings { + pub data: Vec, +} + +impl Timings { + /// Dehydrate timings to a JSON file. + pub fn store_file(&self) -> Result<(), Error> { + let json = JsonValue::from(self.clone()); + let mut file = fs::File::create(TIMINGS_FILE_PATH)?; + json.format_to(&mut file) + } + + /// Rehydrate timings from a JSON file. If not present, returns empty timings. + pub fn read_from_file() -> Self { + fs::read_to_string(TIMINGS_FILE_PATH) + .map_err(|x| x.to_string()) + .and_then(Timings::try_from) + .unwrap_or_default() + } + + /// Merge two sets of timings, overwriting `self` with `other` if present. + pub fn merge(&self, new: &Self) -> Self { + let mut data: Vec = vec![]; + + for timing in &new.data { + data.push(timing.clone()); + } + + for timing in &self.data { + if !data.iter().any(|t| t.day == timing.day) { + data.push(timing.clone()); + } + } + + data.sort_unstable_by(|a, b| a.day.cmp(&b.day)); + Timings { data } + } + + /// Sum up total duration of timings as millis. + pub fn total_millis(&self) -> f64 { + self.data.iter().map(|x| x.total_nanos).sum::() / 1_000_000_f64 + } + + pub fn is_day_complete(&self, day: Day) -> bool { + self.data + .iter() + .any(|t| t.day == day && t.part_1.is_some() && t.part_2.is_some()) + } +} + +/* -------------------------------------------------------------------------- */ + +impl From for JsonValue { + fn from(value: Timings) -> Self { + let mut map: HashMap = HashMap::new(); + + map.insert( + "data".into(), + JsonValue::Array(value.data.iter().map(JsonValue::from).collect()), + ); + + JsonValue::Object(map) + } +} + +impl TryFrom for Timings { + type Error = String; + + fn try_from(value: String) -> Result { + let json = JsonValue::from_str(&value).or(Err("not valid JSON file."))?; + + let json_data = json + .get::>() + .ok_or("expected JSON document to be an object.")? + .get("data") + .ok_or("expected JSON document to have key `data`.")? + .get::>() + .ok_or("expected `json.data` to be an array.")?; + + Ok(Timings { + data: json_data + .iter() + .map(Timing::try_from) + .collect::>()?, + }) + } +} + +/* -------------------------------------------------------------------------- */ + +impl From<&Timing> for JsonValue { + fn from(value: &Timing) -> Self { + let mut map: HashMap = HashMap::new(); + + map.insert("day".into(), JsonValue::String(value.day.to_string())); + map.insert("total_nanos".into(), JsonValue::Number(value.total_nanos)); + + let part_1 = value.part_1.clone().map(JsonValue::String); + let part_2 = value.part_2.clone().map(JsonValue::String); + + map.insert( + "part_1".into(), + match part_1 { + Some(x) => x, + None => JsonValue::Null, + }, + ); + + map.insert( + "part_2".into(), + match part_2 { + Some(x) => x, + None => JsonValue::Null, + }, + ); + + JsonValue::Object(map) + } +} + +impl TryFrom<&JsonValue> for Timing { + type Error = String; + + fn try_from(value: &JsonValue) -> Result { + let json = value + .get::>() + .ok_or("Expected timing to be a JSON object.")?; + + let day = json + .get("day") + .and_then(|v| v.get::()) + .and_then(|day| Day::from_str(day).ok()) + .ok_or("Expected timing.day to be a Day struct.")?; + + let part_1 = json + .get("part_1") + .map(|v| if v.is_null() { None } else { v.get::() }) + .ok_or("Expected timing.part_1 to be null or string.")?; + + let part_2 = json + .get("part_2") + .map(|v| if v.is_null() { None } else { v.get::() }) + .ok_or("Expected timing.part_2 to be null or string.")?; + + let total_nanos = json + .get("total_nanos") + .and_then(|v| v.get::().copied()) + .ok_or("Expected timing.total_nanos to be a number.")?; + + Ok(Timing { + day, + part_1: part_1.cloned(), + part_2: part_2.cloned(), + total_nanos, + }) + } +} + +/* -------------------------------------------------------------------------- */ + +#[cfg(feature = "test_lib")] +mod tests { + use crate::day; + + use super::{Timing, Timings}; + + fn get_mock_timings() -> Timings { + Timings { + data: vec![ + Timing { + day: day!(1), + part_1: Some("10ms".into()), + part_2: Some("20ms".into()), + total_nanos: 3e+10, + }, + Timing { + day: day!(2), + part_1: Some("30ms".into()), + part_2: Some("40ms".into()), + total_nanos: 7e+10, + }, + Timing { + day: day!(4), + part_1: Some("40ms".into()), + part_2: None, + total_nanos: 4e+10, + }, + ], + } + } + + mod deserialization { + use crate::{day, template::timings::Timings}; + + #[test] + fn handles_json_timings() { + let json = r#"{ "data": [{ "day": "01", "part_1": "1ms", "part_2": null, "total_nanos": 1000000000 }] }"#.to_string(); + let timings = Timings::try_from(json).unwrap(); + assert_eq!(timings.data.len(), 1); + let timing = timings.data.first().unwrap(); + assert_eq!(timing.day, day!(1)); + assert_eq!(timing.part_1, Some("1ms".to_string())); + assert_eq!(timing.part_2, None); + assert_eq!(timing.total_nanos, 1_000_000_000_f64); + } + + #[test] + fn handles_empty_timings() { + let json = r#"{ "data": [] }"#.to_string(); + let timings = Timings::try_from(json).unwrap(); + assert_eq!(timings.data.len(), 0); + } + + #[test] + #[should_panic] + fn panics_for_invalid_json() { + let json = r#"{}"#.to_string(); + Timings::try_from(json).unwrap(); + } + + #[test] + #[should_panic] + fn panics_for_malformed_timings() { + let json = r#"{ "data": [{ "day": "01" }, { "day": "26" }, { "day": "02", "part_2": null, "total_nanos": 0 }] }"#.to_string(); + Timings::try_from(json).unwrap(); + } + } + + mod serialization { + use super::get_mock_timings; + use std::collections::HashMap; + use tinyjson::JsonValue; + + #[test] + fn serializes_timings() { + let timings = get_mock_timings(); + let value = JsonValue::try_from(timings).unwrap(); + assert_eq!( + value + .get::>() + .unwrap() + .get("data") + .unwrap() + .get::>() + .unwrap() + .len(), + 3 + ); + } + } + + mod is_day_complete { + use crate::{ + day, + template::timings::{Timing, Timings}, + }; + + #[test] + fn handles_completed_days() { + let timings = Timings { + data: vec![Timing { + day: day!(1), + part_1: Some("1ms".into()), + part_2: Some("2ms".into()), + total_nanos: 3_000_000_000_f64, + }], + }; + + assert_eq!(timings.is_day_complete(&day!(1)), true); + } + + #[test] + fn handles_partial_days() { + let timings = Timings { + data: vec![Timing { + day: day!(1), + part_1: Some("1ms".into()), + part_2: None, + total_nanos: 1_000_000_000_f64, + }], + }; + + assert_eq!(timings.is_day_complete(&day!(1)), false); + } + + #[test] + fn handles_uncompleted_days() { + let timings = Timings { + data: vec![Timing { + day: day!(1), + part_1: None, + part_2: None, + total_nanos: 0.0, + }], + }; + + assert_eq!(timings.is_day_complete(&day!(1)), false); + } + } + + mod merge { + use crate::{ + day, + template::timings::{Timing, Timings}, + }; + + use super::get_mock_timings; + + #[test] + fn handles_disjunct_timings() { + let timings = get_mock_timings(); + let other = Timings { + data: vec![Timing { + day: day!(3), + part_1: None, + part_2: None, + total_nanos: 0_f64, + }], + }; + let merged = timings.merge(&other); + assert_eq!(merged.data.len(), 4); + assert_eq!(merged.data[0].day, day!(1)); + assert_eq!(merged.data[1].day, day!(2)); + assert_eq!(merged.data[2].day, day!(3)); + assert_eq!(merged.data[3].day, day!(4)); + } + + #[test] + fn handles_overlapping_timings() { + let timings = get_mock_timings(); + + let other = Timings { + data: vec![Timing { + day: day!(2), + part_1: None, + part_2: None, + total_nanos: 0_f64, + }], + }; + let merged = timings.merge(&other); + + assert_eq!(merged.data.len(), 3); + assert_eq!(merged.data[0].day, day!(1)); + assert_eq!(merged.data[1].day, day!(2)); + assert_eq!(merged.data[1].total_nanos, 0_f64); + assert_eq!(merged.data[2].day, day!(4)); + } + + #[test] + fn handles_empty_timings() { + let timings = Timings::default(); + let other = get_mock_timings(); + let merged = timings.merge(&other); + assert_eq!(merged.data.len(), 3); + } + + #[test] + fn handles_empty_other_timings() { + let timings = get_mock_timings(); + let other = Timings::default(); + let merged = timings.merge(&other); + assert_eq!(merged.data.len(), 3); + } + } +} diff --git a/year_template/src/utils/.keep b/year_template/src/utils/.keep new file mode 100644 index 0000000..e69de29 From adeb6d2eee42c632268aea68ec45e0d71cf873de Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Sat, 14 Dec 2024 19:56:05 -0800 Subject: [PATCH 3/8] setting the year no longer drops the final newline --- data/examples/.keep | 0 data/inputs/.keep | 0 data/puzzles/.keep | 0 src/template/commands/set_year.rs | 4 ++-- year_template/src/template/commands/set_year.rs | 4 ++-- 5 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 data/examples/.keep delete mode 100644 data/inputs/.keep delete mode 100644 data/puzzles/.keep diff --git a/data/examples/.keep b/data/examples/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/data/inputs/.keep b/data/inputs/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/data/puzzles/.keep b/data/puzzles/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/template/commands/set_year.rs b/src/template/commands/set_year.rs index e271ef1..5c66b50 100644 --- a/src/template/commands/set_year.rs +++ b/src/template/commands/set_year.rs @@ -29,8 +29,8 @@ pub fn set_year(year: u32) -> bool { &new_aoc_year_line } }); - let new_contents: Vec<&str> = lines.collect(); - let new_contents = new_contents.join("\n"); + let mut new_contents: String = lines.collect::>().join("\n"); + new_contents.push('\n'); match write_file(&config_path, new_contents.as_bytes()) { Ok(_) => true, diff --git a/year_template/src/template/commands/set_year.rs b/year_template/src/template/commands/set_year.rs index 1ca4edf..d498540 100644 --- a/year_template/src/template/commands/set_year.rs +++ b/year_template/src/template/commands/set_year.rs @@ -25,8 +25,8 @@ pub fn set_year(year: u32) -> bool { &new_aoc_year_line } }); - let new_contents: Vec<&str> = lines.collect(); - let new_contents = new_contents.join("\n"); + let mut new_contents: String = lines.collect::>().join("\n"); + new_contents.push('\n'); match write_file(&config_path, new_contents.as_bytes()) { Ok(_) => true, From 16515984acdda61b013f3859b3f4d10e60629665 Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Sat, 14 Dec 2024 20:13:43 -0800 Subject: [PATCH 4/8] fix `template.txt` location --- src/template/commands/scaffold.rs | 2 +- src/{ => template}/template.txt | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) rename src/{ => template}/template.txt (59%) diff --git a/src/template/commands/scaffold.rs b/src/template/commands/scaffold.rs index 2f49ab8..900aa39 100644 --- a/src/template/commands/scaffold.rs +++ b/src/template/commands/scaffold.rs @@ -7,7 +7,7 @@ use std::{ use crate::template::{get_year_exit_on_fail, Day}; const MODULE_TEMPLATE: &str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/template.txt")); + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/template/template.txt")); fn safe_create_file(path: &str, overwrite: bool) -> Result { let mut file = OpenOptions::new(); diff --git a/src/template.txt b/src/template/template.txt similarity index 59% rename from src/template.txt rename to src/template/template.txt index 409af68..5c50182 100644 --- a/src/template.txt +++ b/src/template/template.txt @@ -14,15 +14,13 @@ mod tests { #[test] fn test_part_one() { - let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); - let result = part_one(&input); + let result = part_one(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY);); assert_eq!(result, None); } #[test] fn test_part_two() { - let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); - let result = part_two(&input); + let result = part_two(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY);); assert_eq!(result, None); } } From 9e65a65f0707c854002e0287134fdf5b7e793b5a Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Sun, 15 Dec 2024 22:51:47 -0800 Subject: [PATCH 5/8] cleaning up error messages etc --- Cargo.lock | 372 +----------------- src/main.rs | 2 +- src/template/commands/new_year.rs | 15 +- src/template/commands/scaffold.rs | 6 +- src/template/commands/set_year.rs | 6 +- src/template/mod.rs | 2 +- src/template/template.txt | 4 +- year_template/Cargo.toml | 3 - year_template/src/main.rs | 7 +- .../src/template/commands/set_year.rs | 5 +- year_template/src/template/mod.rs | 8 +- year_template/src/template/template.txt | 8 +- 12 files changed, 36 insertions(+), 402 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 231e09d..0ecc99f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,22 +33,10 @@ version = "0.11.0" dependencies = [ "chrono", "dhat", - "graph_builder", - "num", "pico-args", - "regex", "tinyjson", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -64,27 +52,6 @@ dependencies = [ "libc", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - [[package]] name = "autocfg" version = "1.1.0" @@ -118,18 +85,6 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "cc" version = "1.0.83" @@ -165,55 +120,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "delegate" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082a24a9967533dc5d743c602157637116fc1b52806d694a5a45e6f32567fcdd" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "dhat" version = "0.3.3" @@ -230,70 +136,12 @@ dependencies = [ "thousands", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "fast-float" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "graph_builder" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1cfa7a71e06cdd160ce79175bdffd0477bbc97ec943491dd882ba3bd0a2e63" -dependencies = [ - "atoi", - "atomic", - "byte-slice-cast", - "dashmap", - "delegate", - "fast-float", - "fxhash", - "linereader", - "log", - "memmap2", - "num", - "num-format", - "num_cpus", - "page_size", - "parking_lot", - "rayon", - "thiserror", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "iana-time-zone" version = "0.1.58" @@ -344,15 +192,6 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" -[[package]] -name = "linereader" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d921fea6860357575519aca014c6e22470585accdd543b370c404a8a72d0dd1d" -dependencies = [ - "memchr", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -375,15 +214,6 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -403,80 +233,6 @@ dependencies = [ "sys-info", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec", - "itoa", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -486,16 +242,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.1" @@ -511,16 +257,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "page_size" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -568,26 +304,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -597,35 +313,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -667,7 +354,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -687,17 +374,6 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.90" @@ -719,26 +395,6 @@ dependencies = [ "libc", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "thousands" version = "0.2.0" @@ -778,7 +434,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn", "wasm-bindgen-shared", ] @@ -800,7 +456,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -811,28 +467,6 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -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-core" version = "0.51.1" diff --git a/src/main.rs b/src/main.rs index e4e1300..96f4103 100644 --- a/src/main.rs +++ b/src/main.rs @@ -170,7 +170,7 @@ fn main() { AppArguments::SetYear { year } => set_year::handle(year), AppArguments::GetYear => { let year = advent_of_code::template::get_year_exit_on_fail(); - println!("The repository is currently set to {}", year); + println!("The repository is currently set to {}.", year); } }, }; diff --git a/src/template/commands/new_year.rs b/src/template/commands/new_year.rs index e31ec72..9f6c544 100644 --- a/src/template/commands/new_year.rs +++ b/src/template/commands/new_year.rs @@ -31,7 +31,11 @@ pub fn handle(year: u32) { set_year_numbers(year, &new_root); set_year(year); add_to_workspace(year); - println!("Created AOC year {} workspace module", year); + println!("Created {} workspace project.", year); + println!("Set the repository's current working year to {}.", year); + println!("---"); + println!("🎄 Type `cargo scaffold ` to get started on the year."); + println!("🎄 Or type `cargo set-year ` to switch to working on a different year."); } fn copy_year_template(project_root: &Path, new_root: &Path) { @@ -57,7 +61,8 @@ fn set_year_numbers(year: u32, new_root: &Path) { let original_contents = match fs::read_to_string(filepath.clone()) { Ok(original) => original, Err(_) => { - eprintln!("Could not read from file to set year numbers"); + eprintln!("Could not read from file {} to set year numbers.", + filepath.to_str().unwrap()); cleanup(year); process::exit(1); } @@ -109,7 +114,7 @@ fn read_toml_file() -> Result { let filepath = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"); let f = fs::read_to_string(filepath); if f.is_err() { - eprintln!("failed to read Cargo.toml"); + eprintln!("Failed to read Cargo.toml."); return Err(()); } Ok(f.unwrap()) @@ -132,7 +137,7 @@ fn add_year_to_toml_str(year: u32, original: &str) -> String { fn get_end_pos_of_members(original: &str) -> Result { let start_idx = original[..].find("members = ["); if start_idx.is_none() { - eprintln!("failed to find a members section of Cargo.toml"); + eprintln!("Failed to find a members section of Cargo.toml."); return Err(()); } let start_idx = start_idx.unwrap(); @@ -140,7 +145,7 @@ fn get_end_pos_of_members(original: &str) -> Result { match end_idx { Some(i) => Ok(i + start_idx), None => { - eprintln!("failed to find the end of the members section of Cargo.toml"); + eprintln!("Failed to find the end of the members section of Cargo.toml."); Err(()) } } diff --git a/src/template/commands/scaffold.rs b/src/template/commands/scaffold.rs index 900aa39..76b6421 100644 --- a/src/template/commands/scaffold.rs +++ b/src/template/commands/scaffold.rs @@ -6,8 +6,10 @@ use std::{ use crate::template::{get_year_exit_on_fail, Day}; -const MODULE_TEMPLATE: &str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/template/template.txt")); +const MODULE_TEMPLATE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/template/template.txt" +)); fn safe_create_file(path: &str, overwrite: bool) -> Result { let mut file = OpenOptions::new(); diff --git a/src/template/commands/set_year.rs b/src/template/commands/set_year.rs index 5c66b50..46e1ae8 100644 --- a/src/template/commands/set_year.rs +++ b/src/template/commands/set_year.rs @@ -11,7 +11,7 @@ pub fn handle(year: u32) { if !set_year(year) { process::exit(1); } - println!("Set repository to AOC year {}", year); + println!("Set repository to year {}.", year); } pub fn set_year(year: u32) -> bool { @@ -35,7 +35,7 @@ pub fn set_year(year: u32) -> bool { match write_file(&config_path, new_contents.as_bytes()) { Ok(_) => true, Err(_) => { - eprintln!("failed to write new year to the config file"); + eprintln!("Failed to write the new year to config.toml."); false } } @@ -51,7 +51,7 @@ fn get_config_path() -> PathBuf { fn read_config(filepath: &PathBuf) -> Result { let f = fs::read_to_string(filepath); if f.is_err() { - eprintln!("failed to read Cargo.toml"); + eprintln!("Failed to read config.toml."); return Err(()); } Ok(f.unwrap()) diff --git a/src/template/mod.rs b/src/template/mod.rs index ee4d01d..122e535 100644 --- a/src/template/mod.rs +++ b/src/template/mod.rs @@ -45,7 +45,7 @@ pub fn get_year() -> Option { pub fn get_year_exit_on_fail() -> u32 { let year = get_year(); if year.is_none() { - eprintln!("Failed to get the currently set AOC year"); + eprintln!("Failed to get the currently set year."); std::process::exit(1); } year.unwrap() diff --git a/src/template/template.txt b/src/template/template.txt index 5c50182..4da9d32 100644 --- a/src/template/template.txt +++ b/src/template/template.txt @@ -14,13 +14,13 @@ mod tests { #[test] fn test_part_one() { - let result = part_one(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY);); + let result = part_one(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY)); assert_eq!(result, None); } #[test] fn test_part_two() { - let result = part_two(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY);); + let result = part_two(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY)); assert_eq!(result, None); } } diff --git a/year_template/Cargo.toml b/year_template/Cargo.toml index b143fec..27462e8 100644 --- a/year_template/Cargo.toml +++ b/year_template/Cargo.toml @@ -16,10 +16,7 @@ test_lib = [] # Template dependencies chrono = { version = "0.4.38", optional = true } dhat = { version = "0.3.3", optional = true } -graph_builder = "0.4.0" -regex = "1.11.1" pico-args = "0.5.0" tinyjson = "2.5.1" -num = "0.4.3" # Solution dependencies diff --git a/year_template/src/main.rs b/year_template/src/main.rs index e00ba91..b2a97a0 100644 --- a/year_template/src/main.rs +++ b/year_template/src/main.rs @@ -165,13 +165,10 @@ fn main() { AppArguments::NewYear => { println!("You can only generate new year folders at the project root"); } - AppArguments::SetYear { year } => { - set_year::handle(year); - println!("Set repository to AOC year {}", year); - } + AppArguments::SetYear { year } => set_year::handle(year), AppArguments::GetYear => { let year = advent_of_code_YEAR_NUMBER::template::get_year_exit_on_fail(); - println!("The repository is currently set to {}", year); + println!("The repository is currently set to {}.", year); } }, }; diff --git a/year_template/src/template/commands/set_year.rs b/year_template/src/template/commands/set_year.rs index d498540..b583830 100644 --- a/year_template/src/template/commands/set_year.rs +++ b/year_template/src/template/commands/set_year.rs @@ -5,9 +5,10 @@ use crate::template::{get_config_path, read_config}; use super::write_file; pub fn handle(year: u32) { - if set_year(year) { + if !set_year(year) { process::exit(1); } + println!("Set repository to year {}.", year); } pub fn set_year(year: u32) -> bool { @@ -31,7 +32,7 @@ pub fn set_year(year: u32) -> bool { match write_file(&config_path, new_contents.as_bytes()) { Ok(_) => true, Err(_) => { - eprintln!("failed to write new year to the config file"); + eprintln!("Failed to write the new year to config.toml."); false } } diff --git a/year_template/src/template/mod.rs b/year_template/src/template/mod.rs index 3f0147c..9b8dee3 100644 --- a/year_template/src/template/mod.rs +++ b/year_template/src/template/mod.rs @@ -1,4 +1,4 @@ -use std::{env, fs, path::PathBuf, process, str::FromStr}; +use std::{env, fs, path::PathBuf, str::FromStr}; pub mod aoc_cli; pub mod commands; @@ -40,7 +40,7 @@ pub fn get_year() -> Option { let config_path = get_config_path(); let config_contents = read_config(&config_path); if let Err(()) = config_contents { - process::exit(1); + std::process::exit(1); } let config_contents = config_contents.unwrap(); let year: String = config_contents @@ -55,7 +55,7 @@ pub fn get_year() -> Option { pub fn get_year_exit_on_fail() -> u32 { let year = get_year(); if year.is_none() { - eprintln!("Failed to get the currently set AOC year"); + eprintln!("Failed to get the currently set year."); std::process::exit(1); } year.unwrap() @@ -73,7 +73,7 @@ fn get_config_path() -> PathBuf { fn read_config(filepath: &PathBuf) -> Result { let f = fs::read_to_string(filepath); if f.is_err() { - eprintln!("failed to read Cargo.toml"); + eprintln!("Failed to read config.toml."); return Err(()); } Ok(f.unwrap()) diff --git a/year_template/src/template/template.txt b/year_template/src/template/template.txt index 13e8209..4da9d32 100644 --- a/year_template/src/template/template.txt +++ b/year_template/src/template/template.txt @@ -14,15 +14,13 @@ mod tests { #[test] fn test_part_one() { - let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); - let result = part_one(&input); + let result = part_one(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY)); assert_eq!(result, None); } #[test] fn test_part_two() { - let input = advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY); - let result = part_two(&input); + let result = part_two(&advent_of_code_YEAR_NUMBER::template::read_file("examples", DAY)); assert_eq!(result, None); } -} \ No newline at end of file +} From 344c6789ecdc0454ae331c7ee753c4a00b430230 Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Mon, 16 Dec 2024 09:37:46 -0800 Subject: [PATCH 6/8] allow `cargo try` to do test filtering --- src/main.rs | 6 +++--- src/template/commands/attempt.rs | 6 +++--- year_template/src/main.rs | 6 +++--- year_template/src/template/commands/attempt.rs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 96f4103..5870ad3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ mod args { }, Try { day: Day, - release: bool, + test: Option, dhat: bool, }, All { @@ -90,7 +90,7 @@ mod args { }, Some("try") => AppArguments::Try { day: args.free_from_str()?, - release: args.contains("--submit"), + test: args.free_from_str().ok(), dhat: args.contains("--dhat"), }, Some("new-year") => AppArguments::NewYear { @@ -148,7 +148,7 @@ fn main() { dhat, submit, } => solve::handle(day, release, dhat, submit), - AppArguments::Try { day, release, dhat } => attempt::handle(day, release, dhat), + AppArguments::Try { day, test, dhat } => attempt::handle(day, test, dhat), #[cfg(feature = "today")] AppArguments::Today => { match Day::today() { diff --git a/src/template/commands/attempt.rs b/src/template/commands/attempt.rs index 66ff8af..44f5142 100644 --- a/src/template/commands/attempt.rs +++ b/src/template/commands/attempt.rs @@ -2,7 +2,7 @@ use std::process::{Command, Stdio}; use crate::template::Day; -pub fn handle(day: Day, release: bool, dhat: bool) { +pub fn handle(day: Day, test: Option, dhat: bool) { let year = crate::template::get_year_exit_on_fail(); let year = format!("advent_of_code_{}", year); let mut cmd_args = vec![ @@ -20,8 +20,8 @@ pub fn handle(day: Day, release: bool, dhat: bool) { "--features".to_string(), "dhat-heap".to_string(), ]); - } else if release { - cmd_args.push("--release".to_string()); + } else if let Some(test_id) = test { + cmd_args.push(test_id); } cmd_args.push("--".to_string()); diff --git a/year_template/src/main.rs b/year_template/src/main.rs index b2a97a0..fe9f177 100644 --- a/year_template/src/main.rs +++ b/year_template/src/main.rs @@ -32,7 +32,7 @@ mod args { }, Try { day: Day, - release: bool, + test: Option, dhat: bool, }, All { @@ -88,7 +88,7 @@ mod args { }, Some("try") => AppArguments::Try { day: args.free_from_str()?, - release: args.contains("--submit"), + test: args.free_from_str().ok(), dhat: args.contains("--dhat"), }, Some("new-year") => AppArguments::NewYear, @@ -144,7 +144,7 @@ fn main() { dhat, submit, } => solve::handle(day, release, dhat, submit), - AppArguments::Try { day, release, dhat } => attempt::handle(day, release, dhat), + AppArguments::Try { day, test, dhat } => attempt::handle(day, test, dhat), #[cfg(feature = "today")] AppArguments::Today => { match Day::today() { diff --git a/year_template/src/template/commands/attempt.rs b/year_template/src/template/commands/attempt.rs index 4a0537d..861dca4 100644 --- a/year_template/src/template/commands/attempt.rs +++ b/year_template/src/template/commands/attempt.rs @@ -2,7 +2,7 @@ use std::process::{Command, Stdio}; use crate::template::Day; -pub fn handle(day: Day, release: bool, dhat: bool) { +pub fn handle(day: Day, test: Option, dhat: bool) { let mut cmd_args = vec!["test".to_string(), "--bin".to_string(), day.to_string()]; if dhat { @@ -12,8 +12,8 @@ pub fn handle(day: Day, release: bool, dhat: bool) { "--features".to_string(), "dhat-heap".to_string(), ]); - } else if release { - cmd_args.push("--release".to_string()); + } else if let Some(test_id) = test { + cmd_args.push(test_id); } cmd_args.push("--".to_string()); From 53967abb302f98ef53630b5a2cf3c385b27983ff Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Mon, 16 Dec 2024 09:42:27 -0800 Subject: [PATCH 7/8] Update README,md --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e3db903..b29f412 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ This template supports all major OS (macOS, Linux, Windows). 1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github. 2. Click [Use this template](https://github.com/fspoettel/advent-of-code-rust/generate) and create your repository. 3. Clone your repository to your computer. -4. If you are solving a previous year's advent of code, change the `AOC_YEAR` variable in `.cargo/config.toml` to reflect the year you are solving. ### 💻 Setup rust @@ -33,6 +32,22 @@ This template supports all major OS (macOS, Linux, Windows). ## Usage +### ➡️ Start a new year + +```sh +# example: `cargo new-year 2024` +cargo new-year + +# output: +# Created 2024 workspace project. +# Set the repository's current working year to 2024. +# --- +# 🎄 Type `cargo scaffold ` to get started on the year. +# 🎄 Or type `cargo set-year ` to switch to working on a different year. +``` + +A year has its own directory `.//` within the repository. This subdirectory behaves as its own crate with its own dependencies separate from the repository root. Its contents are copied from the directory `./year_template/` so if you add dependencies or utility files there they will be copied into any new year project you create. You can run all of the following commands from within the `.//` directory. You can also run the Advent of Code custom commands from the project root directory so long as the repository year is set to `` (see the `set-year` command). + ### ➡️ Scaffold a day ```sh @@ -74,6 +89,24 @@ cargo download # 🎄 Successfully wrote puzzle to "data/puzzles/01.md". ``` +### ➡️ Run tests for a day + +```sh +# example: `cargo try 1` +cargo try + +# Finished `test` profile [unoptimized + debuginfo] target(s) in 0.84s +# Running unittests src/bin/01.rs (target/debug/deps/01-faca1023160cfe39) + +# running 2 tests +# test tests::test_part_two ... ok +# test tests::test_part_one ... ok +# +# test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +The `try` command runs the tests for your solution against the example puzzle inputs. You can narrow it down to a specific test or set of tests, e.g. `cargo try 1 part_one` to run just the part one test. + ### ➡️ Run solutions for a day ```sh @@ -112,7 +145,7 @@ cargo all # Total: 0.20ms ``` -This runs all solutions sequentially and prints output to the command-line. Same as for the `solve` command, the `--release` flag runs an optimized build. +This runs all solutions for a year sequentially and prints output to the command-line. Same as for the `solve` command, the `--release` flag runs an optimized build. ### ➡️ Benchmark your solutions @@ -149,7 +182,7 @@ By default, `cargo time` does not write to the readme. In order to do so, append cargo test ``` -To run tests for a specific day, append `--bin `, e.g. `cargo test --bin 01`. You can further scope it down to a specific part, e.g. `cargo test --bin 01 part_one`. +To run tests for a specific day, append `--bin `, e.g. `cargo test --bin 01`. You can further scope it down to a specific part, e.g. `cargo test --bin 01 part_one`. This command only works within a given year's subdirectory (such as running all the 2024 tests by running `cargo test` in `./2024/`). ### ➡️ Read puzzle description @@ -201,18 +234,42 @@ cargo today # ...the input... ``` +### ➡️ Change what year the repository is set to +```sh +# example: `cargo set-year 2024` +cargo set-year 2024 + +# output: +# Set repository to year 2024. +``` + +This sets the repository's "configured year" which is tracked in `./.cargo/config.toml`. When running Advent of Code custom commands from the project's root directory, it will execute them for this year. Creating a new year subproject automatically sets the repository's year to that year. + +### ➡️ Check what year the repository is set to +```sh +# example: `cargo get-year` when you've been working on 2024 +cargo get-year + +# output: +# The repository is currently set to 2024. +``` + ### ➡️ Format code ```sh cargo fmt ``` +Run this inside the `./` directory. + ### ➡️ Lint code ```sh cargo clippy ``` +Run this inside the `./` directory. + ## Optional template features ### Configure aoc-cli integration From 6800a1433054a183552af429e2686c474ef5f2e3 Mon Sep 17 00:00:00 2001 From: PaulTreitel Date: Mon, 16 Dec 2024 10:23:00 -0800 Subject: [PATCH 8/8] new year safeguard to prevent overwriting --- src/template/commands/new_year.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/template/commands/new_year.rs b/src/template/commands/new_year.rs index 9f6c544..bc48961 100644 --- a/src/template/commands/new_year.rs +++ b/src/template/commands/new_year.rs @@ -26,6 +26,10 @@ pub fn handle(year: u32) { let new_root = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) .unwrap() .join(format!("{}", year)); + if new_root.exists() { + eprintln!("{} directory already exists", year); + process::exit(1); + } copy_year_template(&project_root, &new_root); set_year_numbers(year, &new_root);