apple_clis/shared/identifiers/generation/
num_generation.rs

1use std::hash::Hash;
2
3use crate::prelude::*;
4
5/// Only considers the generation number for [Hash] and [PartialEq].
6#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
7pub struct NumGeneration {
8	num: NonZeroU8,
9	short: bool,
10}
11
12impl PartialEq for NumGeneration {
13	fn eq(&self, other: &Self) -> bool {
14		self.num == other.num
15	}
16}
17
18impl Hash for NumGeneration {
19	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
20		self.num.hash(state);
21	}
22}
23
24impl NumGeneration {
25	const fn ordinal(&self) -> &str {
26		match self.num.get() {
27			1 => "st",
28			2 => "nd",
29			3 => "rd",
30			_ => "th",
31		}
32	}
33
34	#[cfg_attr(not(test), allow(dead_code))]
35	pub(super) fn testing_new(num: NonZeroU8) -> Self {
36		Self {
37			num,
38			short: false,
39		}
40	}
41
42	fn long(num: NonZeroU8) -> Self {
43		Self {
44			num,
45			short: false,
46		}
47	}
48
49	fn short(num: NonZeroU8) -> Self {
50		Self {
51			num,
52			short: true,
53		}
54	}
55
56	pub fn get(&self) -> u8 {
57		self.num.get()
58	}
59}
60
61#[tracing::instrument(level = "trace", skip(input))]
62fn ordinal(input: &str) -> IResult<&str, &str> {
63	alt((tag("st"), tag("nd"), tag("rd"), tag("th")))(input)
64}
65
66fn generation_brackets(input: &str) -> IResult<&str, NumGeneration> {
67	delimited(
68		ws(tag("(")),
69		map(NonZeroU8::nom_from_str, NumGeneration::long),
70		preceded(ws(ordinal), tag("generation)")),
71	)(input)
72}
73
74fn generation_model(input: &str) -> IResult<&str, NumGeneration> {
75	terminated(
76		map(NonZeroU8::nom_from_str, NumGeneration::short),
77		tag("G"),
78	)(input)
79}
80
81impl NomFromStr for NumGeneration {
82	#[tracing::instrument(level = "trace", skip(input))]
83	fn nom_from_str(input: &str) -> IResult<&str, Self> {
84		alt((generation_brackets, generation_model))(input)
85	}
86}
87
88impl Display for NumGeneration {
89	#[tracing::instrument(level = "trace", skip(self, f))]
90	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91		match self.short {
92			false => write!(f, "({}{} generation)", self.get(), self.ordinal()),
93			true => write!(f, "{}G", self.get()),
94		}
95	}
96}
97
98#[cfg(test)]
99mod tests {
100	use tracing::debug;
101
102	use super::*;
103
104	#[test]
105	fn generation_ordering() {
106		let old = NumGeneration::long(NonZeroU8::new(1).unwrap());
107		let newer = NumGeneration::short(NonZeroU8::new(2).unwrap());
108		assert!(newer > old);
109
110		let old = NumGeneration::short(NonZeroU8::new(1).unwrap());
111		let newer = NumGeneration::long(NonZeroU8::new(2).unwrap());
112		assert!(newer > old);
113	}
114
115	#[test]
116	fn test_parse_ordinal() {
117		let examples = ["st", "nd", "th"];
118		for example in examples.iter() {
119			let output = ordinal(example);
120			match output {
121				Ok((remaining, _)) => {
122					debug!("Parsed ordinal from {}: {:?}", example, remaining)
123				}
124				Err(e) => panic!("Failed to parse {:?}: {}", example, e),
125			}
126		}
127	}
128
129	#[test]
130	fn hardcoded_num_generation() {
131		let examples = [
132			"(1st generation)",
133			"(2nd generation)",
134			"(3rd generation)",
135			"(4th generation)",
136			"3G",
137			"69G",
138		];
139		for example in examples.iter() {
140			let output = NumGeneration::nom_from_str(example);
141			match output {
142				Ok((remaining, generation)) => {
143					debug!(
144						"Parsed generation: {:?} from {} [remaining: {}]",
145						generation, example, remaining
146					);
147					assert!(
148						remaining.is_empty(),
149						"Remaining was not empty: {}",
150						remaining
151					);
152					assert_eq!(&format!("{}", generation), example);
153				}
154				Err(e) => panic!("Failed to parse {:?}: {}", example, e),
155			}
156		}
157	}
158}