use std::fmt::{self, Display, Formatter}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Operating system version. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Version { /// Unknown version. Unknown, /// Semantic version (major.minor.patch). Semantic(u64, u64, u64), /// Rolling version. Optionally contains the release date in the string format. Rolling(Option), /// Custom version format. Custom(String), } impl Version { /// Constructs `VersionType` from the given string. /// /// Returns `VersionType::Unknown` if the string is empty. If it can be parsed as a semantic /// version, then `VersionType::Semantic`, otherwise `VersionType::Custom`. /// /// # Examples /// /// ``` /// use os_info::Version; /// /// let v = Version::from_string("custom"); /// assert_eq!(Version::Custom("custom".to_owned()), v); /// /// let v = Version::from_string("1.2.3"); /// assert_eq!(Version::Semantic(1, 2, 3), v); /// ``` pub fn from_string + AsRef>(s: S) -> Self { if s.as_ref().is_empty() { Self::Unknown } else if let Some((major, minor, patch)) = parse_version(s.as_ref()) { Self::Semantic(major, minor, patch) } else { Self::Custom(s.into()) } } } impl Default for Version { fn default() -> Self { Version::Unknown } } impl Display for Version { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { Self::Unknown => f.write_str("Unknown"), Self::Semantic(major, minor, patch) => write!(f, "{major}.{minor}.{patch}"), Self::Rolling(ref date) => { let date = match date { Some(date) => format!(" ({date})"), None => "".to_owned(), }; write!(f, "Rolling Release{date}") } Self::Custom(ref version) => write!(f, "{version}"), } } } fn parse_version(s: &str) -> Option<(u64, u64, u64)> { let mut iter = s.trim().split_terminator('.').fuse(); let major = iter.next().and_then(|s| s.parse().ok())?; let minor = iter.next().unwrap_or("0").parse().ok()?; let patch = iter.next().unwrap_or("0").parse().ok()?; if iter.next().is_some() { return None; } Some((major, minor, patch)) } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn parse_semantic_version() { let data = [ ("", None), ("version", None), ("1", Some((1, 0, 0))), ("1.", Some((1, 0, 0))), ("1.2", Some((1, 2, 0))), ("1.2.", Some((1, 2, 0))), ("1.2.3", Some((1, 2, 3))), ("1.2.3.", Some((1, 2, 3))), ("1.2.3. ", Some((1, 2, 3))), (" 1.2.3.", Some((1, 2, 3))), (" 1.2.3. ", Some((1, 2, 3))), ("1.2.3.4", None), ("1.2.3.4.5.6.7.8.9", None), ]; for (s, expected) in &data { let result = parse_version(s); assert_eq!(expected, &result); } } #[test] fn from_string() { let custom_version = "some version"; let data = [ ("", Version::Unknown), ("1.2.3", Version::Semantic(1, 2, 3)), (custom_version, Version::Custom(custom_version.to_owned())), ]; for (s, expected) in &data { let version = Version::from_string(*s); assert_eq!(expected, &version); } } #[test] fn default() { assert_eq!(Version::Unknown, Version::default()); } #[test] fn display() { let data = [ (Version::Unknown, "Unknown"), (Version::Semantic(1, 5, 0), "1.5.0"), (Version::Rolling(None), "Rolling Release"), ( Version::Rolling(Some("date".to_owned())), "Rolling Release (date)", ), ]; for (version, expected) in &data { assert_eq!(expected, &version.to_string()); } } }