implement Day newtype

This commit is contained in:
Tristan Guichaoua 2023-11-22 10:24:18 +01:00
parent 6653e856a6
commit da04cb77a8
11 changed files with 150 additions and 47 deletions

100
src/day.rs Normal file
View file

@ -0,0 +1,100 @@
use std::error::Error;
use std::fmt::Display;
use std::str::FromStr;
/* -------------------------------------------------------------------------- */
/// A valid day number of advent (i.e. an integer in range 1 to 25).
///
/// # Display
/// This value is display as a two digit number.
///
/// ```
/// 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 {
pub fn new(day: u8) -> Option<Self> {
if day == 0 || day > 25 {
return None;
}
Some(Self(day))
}
fn new_unchecked(day: u8) -> Self {
debug_assert!(day != 0 && day <= 25);
Self(day)
}
}
impl Display for Day {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}", self.0)
}
}
impl PartialEq<u8> for Day {
fn eq(&self, other: &u8) -> bool {
self.0.eq(other)
}
}
impl PartialOrd<u8> for Day {
fn partial_cmp(&self, other: &u8) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
/* -------------------------------------------------------------------------- */
impl FromStr for Day {
type Err = DayFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let day = s.parse().map_err(|_| DayFromStrError)?;
Self::new(day).ok_or(DayFromStrError)
}
}
#[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 number between 1 and 25")
}
}
/* -------------------------------------------------------------------------- */
/// An iterator that yield every day of advent from the 1st to the 25th.
pub fn every_day() -> EveryDay {
EveryDay { current: 1 }
}
/// An iterator that yield every day of advent from the 1st to the 25th.
pub struct EveryDay {
current: u8,
}
impl Iterator for EveryDay {
type Item = Day;
fn next(&mut self) -> Option<Self::Item> {
if self.current > 25 {
return None;
}
// NOTE: the iterator start at 1 and we have verified that the value is not above 25.
let day = Day::new_unchecked(self.current);
self.current += 1;
Some(day)
}
}
/* -------------------------------------------------------------------------- */

View file

@ -1 +1,4 @@
mod day;
pub mod template; pub mod template;
pub use day::*;

View file

@ -4,18 +4,20 @@ use args::{parse, AppArguments};
mod args { mod args {
use std::process; use std::process;
use advent_of_code::Day;
pub enum AppArguments { pub enum AppArguments {
Download { Download {
day: u8, day: Day,
}, },
Read { Read {
day: u8, day: Day,
}, },
Scaffold { Scaffold {
day: u8, day: Day,
}, },
Solve { Solve {
day: u8, day: Day,
release: bool, release: bool,
time: bool, time: bool,
submit: Option<u8>, submit: Option<u8>,

View file

@ -4,6 +4,8 @@ use std::{
process::{Command, Output, Stdio}, process::{Command, Output, Stdio},
}; };
use crate::Day;
#[derive(Debug)] #[derive(Debug)]
pub enum AocCommandError { pub enum AocCommandError {
CommandNotFound, CommandNotFound,
@ -33,7 +35,7 @@ pub fn check() -> Result<(), AocCommandError> {
Ok(()) Ok(())
} }
pub fn read(day: u8) -> Result<Output, AocCommandError> { pub fn read(day: Day) -> Result<Output, AocCommandError> {
let puzzle_path = get_puzzle_path(day); let puzzle_path = get_puzzle_path(day);
let args = build_args( let args = build_args(
@ -49,7 +51,7 @@ pub fn read(day: u8) -> Result<Output, AocCommandError> {
call_aoc_cli(&args) call_aoc_cli(&args)
} }
pub fn download(day: u8) -> Result<Output, AocCommandError> { pub fn download(day: Day) -> Result<Output, AocCommandError> {
let input_path = get_input_path(day); let input_path = get_input_path(day);
let puzzle_path = get_puzzle_path(day); let puzzle_path = get_puzzle_path(day);
@ -72,7 +74,7 @@ pub fn download(day: u8) -> Result<Output, AocCommandError> {
Ok(output) Ok(output)
} }
pub fn submit(day: u8, part: u8, result: &str) -> Result<Output, AocCommandError> { pub fn submit(day: Day, part: u8, result: &str) -> Result<Output, AocCommandError> {
// workaround: the argument order is inverted for submit. // workaround: the argument order is inverted for submit.
let mut args = build_args("submit", &[], day); let mut args = build_args("submit", &[], day);
args.push(part.to_string()); args.push(part.to_string());
@ -80,14 +82,12 @@ pub fn submit(day: u8, part: u8, result: &str) -> Result<Output, AocCommandError
call_aoc_cli(&args) call_aoc_cli(&args)
} }
fn get_input_path(day: u8) -> String { fn get_input_path(day: Day) -> String {
let day_padded = format!("{day:02}"); format!("data/inputs/{day}.txt")
format!("data/inputs/{day_padded}.txt")
} }
fn get_puzzle_path(day: u8) -> String { fn get_puzzle_path(day: Day) -> String {
let day_padded = format!("{day:02}"); format!("data/puzzles/{day}.md")
format!("data/puzzles/{day_padded}.md")
} }
fn get_year() -> Option<u16> { fn get_year() -> Option<u16> {
@ -97,7 +97,7 @@ fn get_year() -> Option<u16> {
} }
} }
fn build_args(command: &str, args: &[String], day: u8) -> Vec<String> { fn build_args(command: &str, args: &[String], day: Day) -> Vec<String> {
let mut cmd_args = args.to_vec(); let mut cmd_args = args.to_vec();
if let Some(year) = get_year() { if let Some(year) = get_year() {

View file

@ -4,11 +4,12 @@ use crate::template::{
readme_benchmarks::{self, Timings}, readme_benchmarks::{self, Timings},
ANSI_BOLD, ANSI_ITALIC, ANSI_RESET, ANSI_BOLD, ANSI_ITALIC, ANSI_RESET,
}; };
use crate::{every_day, Day};
pub fn handle(is_release: bool, is_timed: bool) { pub fn handle(is_release: bool, is_timed: bool) {
let mut timings: Vec<Timings> = vec![]; let mut timings: Vec<Timings> = vec![];
(1..=25).for_each(|day| { every_day().for_each(|day| {
if day > 1 { if day > 1 {
println!(); println!();
} }
@ -56,15 +57,15 @@ impl From<std::io::Error> for Error {
} }
#[must_use] #[must_use]
pub fn get_path_for_bin(day: usize) -> String { pub fn get_path_for_bin(day: Day) -> String {
let day_padded = format!("{day:02}"); format!("./src/bin/{day}.rs")
format!("./src/bin/{day_padded}.rs")
} }
/// All solutions live in isolated binaries. /// All solutions live in isolated binaries.
/// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output. /// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output.
mod child_commands { mod child_commands {
use super::{get_path_for_bin, Error}; use super::{get_path_for_bin, Error};
use crate::Day;
use std::{ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
path::Path, path::Path,
@ -73,18 +74,13 @@ mod child_commands {
}; };
/// Run the solution bin for a given day /// Run the solution bin for a given day
pub fn run_solution( pub fn run_solution(day: Day, is_timed: bool, is_release: bool) -> Result<Vec<String>, Error> {
day: usize,
is_timed: bool,
is_release: bool,
) -> Result<Vec<String>, Error> {
let day_padded = format!("{day:02}");
// skip command invocation for days that have not been scaffolded yet. // skip command invocation for days that have not been scaffolded yet.
if !Path::new(&get_path_for_bin(day)).exists() { if !Path::new(&get_path_for_bin(day)).exists() {
return Ok(vec![]); return Ok(vec![]);
} }
let day_padded = day.to_string();
let mut args = vec!["run", "--quiet", "--bin", &day_padded]; let mut args = vec!["run", "--quiet", "--bin", &day_padded];
if is_release { if is_release {
@ -129,7 +125,7 @@ mod child_commands {
Ok(output) Ok(output)
} }
pub fn parse_exec_time(output: &[String], day: usize) -> super::Timings { pub fn parse_exec_time(output: &[String], day: Day) -> super::Timings {
let mut timings = super::Timings { let mut timings = super::Timings {
day, day,
part_1: None, part_1: None,

View file

@ -1,7 +1,8 @@
use crate::template::aoc_cli; use crate::template::aoc_cli;
use crate::Day;
use std::process; use std::process;
pub fn handle(day: u8) { pub fn handle(day: Day) {
if aoc_cli::check().is_err() { if aoc_cli::check().is_err() {
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
process::exit(1); process::exit(1);

View file

@ -1,8 +1,9 @@
use std::process; use std::process;
use crate::template::aoc_cli; use crate::template::aoc_cli;
use crate::Day;
pub fn handle(day: u8) { pub fn handle(day: Day) {
if aoc_cli::check().is_err() { if aoc_cli::check().is_err() {
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
process::exit(1); process::exit(1);

View file

@ -4,6 +4,8 @@ use std::{
process, process,
}; };
use crate::Day;
const MODULE_TEMPLATE: &str = r#"pub fn part_one(input: &str) -> Option<u32> { const MODULE_TEMPLATE: &str = r#"pub fn part_one(input: &str) -> Option<u32> {
None None
} }
@ -40,12 +42,10 @@ fn create_file(path: &str) -> Result<File, std::io::Error> {
OpenOptions::new().write(true).create(true).open(path) OpenOptions::new().write(true).create(true).open(path)
} }
pub fn handle(day: u8) { pub fn handle(day: Day) {
let day_padded = format!("{day:02}"); let input_path = format!("data/inputs/{day}.txt");
let example_path = format!("data/examples/{day}.txt");
let input_path = format!("data/inputs/{day_padded}.txt"); let module_path = format!("src/bin/{day}.rs");
let example_path = format!("data/examples/{day_padded}.txt");
let module_path = format!("src/bin/{day_padded}.rs");
let mut file = match safe_create_file(&module_path) { let mut file = match safe_create_file(&module_path) {
Ok(file) => file, Ok(file) => file,
@ -86,8 +86,5 @@ pub fn handle(day: u8) {
} }
println!("---"); println!("---");
println!( println!("🎄 Type `cargo solve {}` to run your solution.", day);
"🎄 Type `cargo solve {}` to run your solution.",
&day_padded
);
} }

View file

@ -1,9 +1,9 @@
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
pub fn handle(day: u8, release: bool, time: bool, submit_part: Option<u8>) { use crate::Day;
let day_padded = format!("{day:02}");
let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day_padded]; pub fn handle(day: Day, release: bool, time: bool, submit_part: Option<u8>) {
let mut cmd_args = vec!["run".to_string(), "--bin".to_string(), day.to_string()];
if release { if release {
cmd_args.push("--release".to_string()); cmd_args.push("--release".to_string());

View file

@ -2,6 +2,8 @@
/// The approach taken is similar to how `aoc-readme-stars` handles this. /// The approach taken is similar to how `aoc-readme-stars` handles this.
use std::{fs, io}; use std::{fs, io};
use crate::Day;
static MARKER: &str = "<!--- benchmarking table --->"; static MARKER: &str = "<!--- benchmarking table --->";
#[derive(Debug)] #[derive(Debug)]
@ -18,7 +20,7 @@ impl From<std::io::Error> for Error {
#[derive(Clone)] #[derive(Clone)]
pub struct Timings { pub struct Timings {
pub day: usize, pub day: Day,
pub part_1: Option<String>, pub part_1: Option<String>,
pub part_2: Option<String>, pub part_2: Option<String>,
pub total_nanos: f64, pub total_nanos: f64,
@ -29,9 +31,9 @@ pub struct TablePosition {
pos_end: usize, pos_end: usize,
} }
#[must_use] pub fn get_path_for_bin(day: usize) -> String { #[must_use]
let day_padded = format!("{day:02}"); pub fn get_path_for_bin(day: Day) -> String {
format!("./src/bin/{day_padded}.rs") format!("./src/bin/{day}.rs")
} }
fn locate_table(readme: &str) -> Result<TablePosition, Error> { fn locate_table(readme: &str) -> Result<TablePosition, Error> {

View file

@ -1,5 +1,6 @@
/// Encapsulates code that interacts with solution functions. /// Encapsulates code that interacts with solution functions.
use crate::template::{aoc_cli, ANSI_ITALIC, ANSI_RESET}; use crate::template::{aoc_cli, ANSI_ITALIC, ANSI_RESET};
use crate::Day;
use std::fmt::Display; use std::fmt::Display;
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use std::process::Output; use std::process::Output;
@ -8,7 +9,7 @@ use std::{cmp, env, process};
use super::ANSI_BOLD; use super::ANSI_BOLD;
pub fn run_part<I: Clone, T: Display>(func: impl Fn(I) -> Option<T>, input: I, day: u8, part: u8) { pub fn run_part<I: Clone, T: Display>(func: impl Fn(I) -> Option<T>, input: I, day: Day, part: u8) {
let part_str = format!("Part {part}"); let part_str = format!("Part {part}");
let (result, duration, samples) = let (result, duration, samples) =
@ -131,7 +132,7 @@ fn print_result<T: Display>(result: &Option<T>, part: &str, duration_str: &str)
/// 2. aoc-cli is installed. /// 2. aoc-cli is installed.
fn submit_result<T: Display>( fn submit_result<T: Display>(
result: T, result: T,
day: u8, day: Day,
part: u8, part: u8,
) -> Option<Result<Output, aoc_cli::AocCommandError>> { ) -> Option<Result<Output, aoc_cli::AocCommandError>> {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();