headless_lms_utils/
version.rs1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub struct Version(pub u64, pub u64, pub u64);
8
9impl Version {
10 pub fn parse(raw: &str) -> Option<Self> {
13 let parts: Vec<u64> = raw
14 .trim()
15 .trim_start_matches('v')
16 .split('.')
17 .map(|part| {
18 (!part.is_empty() && part.bytes().all(|b| b.is_ascii_digit()))
19 .then(|| part.parse().ok())
20 .flatten()
21 })
22 .collect::<Option<_>>()?;
23 match parts.as_slice() {
24 [major] => Some(Version(*major, 0, 0)),
25 [major, minor] => Some(Version(*major, *minor, 0)),
26 [major, minor, patch] => Some(Version(*major, *minor, *patch)),
27 _ => None,
28 }
29 }
30}
31
32impl fmt::Display for Version {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "{}.{}.{}", self.0, self.1, self.2)
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn missing_parts_default_to_zero() {
44 assert_eq!(Version::parse("1"), Some(Version(1, 0, 0)));
45 assert_eq!(Version::parse("1.2"), Some(Version(1, 2, 0)));
46 assert_eq!(Version::parse("1.2.3"), Some(Version(1, 2, 3)));
47 assert_eq!(Version::parse("x"), None);
48 }
49}