Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6d9bf34

Browse files
feat: use checked Day type instead of integers for days (#35)
1 parent 6653e85 commit 6d9bf34

File tree

12 files changed

+248
-64
lines changed

12 files changed

+248
-64
lines changed

‎src/day.rs‎

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use std::error::Error;
2+
use std::fmt::Display;
3+
use std::str::FromStr;
4+
5+
/// A valid day number of advent (i.e. an integer in range 1 to 25).
6+
///
7+
/// # Display
8+
/// This value displays as a two digit number.
9+
///
10+
/// ```
11+
/// # use advent_of_code::Day;
12+
/// let day = Day::new(8).unwrap();
13+
/// assert_eq!(day.to_string(), "08")
14+
/// ```
15+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16+
pub struct Day(u8);
17+
18+
impl Day {
19+
/// Creates a [`Day`] from the provided value if it's in the valid range,
20+
/// returns [`None`] otherwise.
21+
pub fn new(day: u8) -> Option<Self> {
22+
if day == 0 || day > 25 {
23+
return None;
24+
}
25+
Some(Self(day))
26+
}
27+
28+
// Not part of the public API
29+
#[doc(hidden)]
30+
pub const fn __new_unchecked(day: u8) -> Self {
31+
Self(day)
32+
}
33+
34+
/// Converts the [`Day`] into an [`u8`].
35+
pub fn into_inner(self) -> u8 {
36+
self.0
37+
}
38+
}
39+
40+
impl Display for Day {
41+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42+
write!(f, "{:02}", self.0)
43+
}
44+
}
45+
46+
impl PartialEq<u8> for Day {
47+
fn eq(&self, other: &u8) -> bool {
48+
self.0.eq(other)
49+
}
50+
}
51+
52+
impl PartialOrd<u8> for Day {
53+
fn partial_cmp(&self, other: &u8) -> Option<std::cmp::Ordering> {
54+
self.0.partial_cmp(other)
55+
}
56+
}
57+
58+
/* -------------------------------------------------------------------------- */
59+
60+
impl FromStr for Day {
61+
type Err = DayFromStrError;
62+
63+
fn from_str(s: &str) -> Result<Self, Self::Err> {
64+
let day = s.parse().map_err(|_| DayFromStrError)?;
65+
Self::new(day).ok_or(DayFromStrError)
66+
}
67+
}
68+
69+
/// An error which can be returned when parsing a [`Day`].
70+
#[derive(Debug)]
71+
pub struct DayFromStrError;
72+
73+
impl Error for DayFromStrError {}
74+
75+
impl Display for DayFromStrError {
76+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77+
f.write_str("expecting a day number between 1 and 25")
78+
}
79+
}
80+
81+
/* -------------------------------------------------------------------------- */
82+
83+
/// An iterator that yields every day of advent from the 1st to the 25th.
84+
pub fn all_days() -> AllDays {
85+
AllDays::new()
86+
}
87+
88+
/// An iterator that yields every day of advent from the 1st to the 25th.
89+
pub struct AllDays {
90+
current: u8,
91+
}
92+
93+
impl AllDays {
94+
#[allow(clippy::new_without_default)]
95+
pub fn new() -> Self {
96+
Self { current: 1 }
97+
}
98+
}
99+
100+
impl Iterator for AllDays {
101+
type Item = Day;
102+
103+
fn next(&mut self) -> Option<Self::Item> {
104+
if self.current > 25 {
105+
return None;
106+
}
107+
// NOTE: the iterator starts at 1 and we have verified that the value is not above 25.
108+
let day = Day(self.current);
109+
self.current += 1;
110+
111+
Some(day)
112+
}
113+
}
114+
115+
/* -------------------------------------------------------------------------- */
116+
117+
/// Creates a [`Day`] value in a const context.
118+
#[macro_export]
119+
macro_rules! day {
120+
($day:expr) => {{
121+
const _ASSERT: () = assert!(
122+
$day != 0 && $day <= 25,
123+
concat!(
124+
"invalid day number `",
125+
$day,
126+
"`, expecting a value between 1 and 25"
127+
),
128+
);
129+
$crate::Day::__new_unchecked($day)
130+
}};
131+
}
132+
133+
/* -------------------------------------------------------------------------- */
134+
135+
#[cfg(feature = "test_lib")]
136+
mod tests {
137+
use super::{all_days, Day};
138+
139+
#[test]
140+
fn all_days_iterator() {
141+
let mut iter = all_days();
142+
143+
assert_eq!(iter.next(), Some(Day(1)));
144+
assert_eq!(iter.next(), Some(Day(2)));
145+
assert_eq!(iter.next(), Some(Day(3)));
146+
assert_eq!(iter.next(), Some(Day(4)));
147+
assert_eq!(iter.next(), Some(Day(5)));
148+
assert_eq!(iter.next(), Some(Day(6)));
149+
assert_eq!(iter.next(), Some(Day(7)));
150+
assert_eq!(iter.next(), Some(Day(8)));
151+
assert_eq!(iter.next(), Some(Day(9)));
152+
assert_eq!(iter.next(), Some(Day(10)));
153+
assert_eq!(iter.next(), Some(Day(11)));
154+
assert_eq!(iter.next(), Some(Day(12)));
155+
assert_eq!(iter.next(), Some(Day(13)));
156+
assert_eq!(iter.next(), Some(Day(14)));
157+
assert_eq!(iter.next(), Some(Day(15)));
158+
assert_eq!(iter.next(), Some(Day(16)));
159+
assert_eq!(iter.next(), Some(Day(17)));
160+
assert_eq!(iter.next(), Some(Day(18)));
161+
assert_eq!(iter.next(), Some(Day(19)));
162+
assert_eq!(iter.next(), Some(Day(20)));
163+
assert_eq!(iter.next(), Some(Day(21)));
164+
assert_eq!(iter.next(), Some(Day(22)));
165+
assert_eq!(iter.next(), Some(Day(23)));
166+
assert_eq!(iter.next(), Some(Day(24)));
167+
assert_eq!(iter.next(), Some(Day(25)));
168+
assert_eq!(iter.next(), None);
169+
}
170+
}
171+
172+
/* -------------------------------------------------------------------------- */

‎src/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
mod day;
12
pub mod template;
3+
4+
pub use day::*;

‎src/main.rs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ use args::{parse, AppArguments};
44
mod args {
55
use std::process;
66

7+
use advent_of_code::Day;
8+
79
pub enum AppArguments {
810
Download {
9-
day: u8,
11+
day: Day,
1012
},
1113
Read {
12-
day: u8,
14+
day: Day,
1315
},
1416
Scaffold {
15-
day: u8,
17+
day: Day,
1618
},
1719
Solve {
18-
day: u8,
20+
day: Day,
1921
release: bool,
2022
time: bool,
2123
submit: Option<u8>,

‎src/template/aoc_cli.rs‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::{
44
process::{Command, Output, Stdio},
55
};
66

7+
use crate::Day;
8+
79
#[derive(Debug)]
810
pub enum AocCommandError {
911
CommandNotFound,
@@ -33,7 +35,7 @@ pub fn check() -> Result<(), AocCommandError> {
3335
Ok(())
3436
}
3537

36-
pub fn read(day: u8) -> Result<Output, AocCommandError> {
38+
pub fn read(day: Day) -> Result<Output, AocCommandError> {
3739
let puzzle_path = get_puzzle_path(day);
3840

3941
let args = build_args(
@@ -49,7 +51,7 @@ pub fn read(day: u8) -> Result<Output, AocCommandError> {
4951
call_aoc_cli(&args)
5052
}
5153

52-
pub fn download(day: u8) -> Result<Output, AocCommandError> {
54+
pub fn download(day: Day) -> Result<Output, AocCommandError> {
5355
let input_path = get_input_path(day);
5456
let puzzle_path = get_puzzle_path(day);
5557

@@ -72,22 +74,20 @@ pub fn download(day: u8) -> Result<Output, AocCommandError> {
7274
Ok(output)
7375
}
7476

75-
pub fn submit(day: u8, part: u8, result: &str) -> Result<Output, AocCommandError> {
77+
pub fn submit(day: Day, part: u8, result: &str) -> Result<Output, AocCommandError> {
7678
// workaround: the argument order is inverted for submit.
7779
let mut args = build_args("submit", &[], day);
7880
args.push(part.to_string());
7981
args.push(result.to_string());
8082
call_aoc_cli(&args)
8183
}
8284

83-
fn get_input_path(day: u8) -> String {
84-
let day_padded = format!("{day:02}");
85-
format!("data/inputs/{day_padded}.txt")
85+
fn get_input_path(day: Day) -> String {
86+
format!("data/inputs/{day}.txt")
8687
}
8788

88-
fn get_puzzle_path(day: u8) -> String {
89-
let day_padded = format!("{day:02}");
90-
format!("data/puzzles/{day_padded}.md")
89+
fn get_puzzle_path(day: Day) -> String {
90+
format!("data/puzzles/{day}.md")
9191
}
9292

9393
fn get_year() -> Option<u16> {
@@ -97,7 +97,7 @@ fn get_year() -> Option<u16> {
9797
}
9898
}
9999

100-
fn build_args(command: &str, args: &[String], day: u8) -> Vec<String> {
100+
fn build_args(command: &str, args: &[String], day: Day) -> Vec<String> {
101101
let mut cmd_args = args.to_vec();
102102

103103
if let Some(year) = get_year() {

‎src/template/commands/all.rs‎

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use crate::template::{
44
readme_benchmarks::{self, Timings},
55
ANSI_BOLD, ANSI_ITALIC, ANSI_RESET,
66
};
7+
use crate::{all_days, Day};
78

89
pub fn handle(is_release: bool, is_timed: bool) {
910
let mut timings: Vec<Timings> = vec![];
1011

11-
(1..=25).for_each(|day| {
12+
all_days().for_each(|day| {
1213
if day > 1 {
1314
println!();
1415
}
@@ -56,15 +57,15 @@ impl From<std::io::Error> for Error {
5657
}
5758

5859
#[must_use]
59-
pub fn get_path_for_bin(day: usize) -> String {
60-
let day_padded = format!("{day:02}");
61-
format!("./src/bin/{day_padded}.rs")
60+
pub fn get_path_for_bin(day: Day) -> String {
61+
format!("./src/bin/{day}.rs")
6262
}
6363

6464
/// All solutions live in isolated binaries.
6565
/// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output.
6666
mod child_commands {
6767
use super::{get_path_for_bin, Error};
68+
use crate::Day;
6869
use std::{
6970
io::{BufRead, BufReader},
7071
path::Path,
@@ -73,18 +74,13 @@ mod child_commands {
7374
};
7475

7576
/// Run the solution bin for a given day
76-
pub fn run_solution(
77-
day: usize,
78-
is_timed: bool,
79-
is_release: bool,
80-
) -> Result<Vec<String>, Error> {
81-
let day_padded = format!("{day:02}");
82-
77+
pub fn run_solution(day: Day, is_timed: bool, is_release: bool) -> Result<Vec<String>, Error> {
8378
// skip command invocation for days that have not been scaffolded yet.
8479
if !Path::new(&get_path_for_bin(day)).exists() {
8580
return Ok(vec![]);
8681
}
8782

83+
let day_padded = day.to_string();
8884
let mut args = vec!["run", "--quiet", "--bin", &day_padded];
8985

9086
if is_release {
@@ -129,7 +125,7 @@ mod child_commands {
129125
Ok(output)
130126
}
131127

132-
pub fn parse_exec_time(output: &[String], day: usize) -> super::Timings {
128+
pub fn parse_exec_time(output: &[String], day: Day) -> super::Timings {
133129
let mut timings = super::Timings {
134130
day,
135131
part_1: None,
@@ -208,6 +204,8 @@ mod child_commands {
208204
mod tests {
209205
use super::parse_exec_time;
210206

207+
use crate::day;
208+
211209
#[test]
212210
fn test_well_formed() {
213211
let res = parse_exec_time(
@@ -216,7 +214,7 @@ mod child_commands {
216214
"Part 2: 10 (74.13ms @ 99999 samples)".into(),
217215
"".into(),
218216
],
219-
1,
217+
day!(1),
220218
);
221219
assert_approx_eq!(res.total_nanos, 74130074.13_f64);
222220
assert_eq!(res.part_1.unwrap(), "74.13ns");
@@ -231,7 +229,7 @@ mod child_commands {
231229
"Part 2: 10s (100ms @ 1 samples)".into(),
232230
"".into(),
233231
],
234-
1,
232+
day!(1),
235233
);
236234
assert_approx_eq!(res.total_nanos, 2100000000_f64);
237235
assert_eq!(res.part_1.unwrap(), "2s");
@@ -246,7 +244,7 @@ mod child_commands {
246244
"Part 2: ✖ ".into(),
247245
"".into(),
248246
],
249-
1,
247+
day!(1),
250248
);
251249
assert_approx_eq!(res.total_nanos, 0_f64);
252250
assert_eq!(res.part_1.is_none(), true);

‎src/template/commands/download.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::template::aoc_cli;
2+
use crate::Day;
23
use std::process;
34

4-
pub fn handle(day: u8) {
5+
pub fn handle(day: Day) {
56
if aoc_cli::check().is_err() {
67
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
78
process::exit(1);

‎src/template/commands/read.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::process;
22

33
use crate::template::aoc_cli;
4+
use crate::Day;
45

5-
pub fn handle(day: u8) {
6+
pub fn handle(day: Day) {
67
if aoc_cli::check().is_err() {
78
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
89
process::exit(1);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /