os_version/
macos.rs

1use anyhow::Result;
2use std::fmt;
3
4#[cfg(target_os = "macos")]
5const PATH: &str = "/System/Library/CoreServices/SystemVersion.plist";
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct MacOS {
9    pub version: String,
10}
11
12impl MacOS {
13    #[cfg(target_os = "macos")]
14    pub fn detect() -> Result<MacOS> {
15        let file = std::fs::read_to_string(PATH)?;
16        parse(&file)
17    }
18
19    #[cfg(not(target_os = "macos"))]
20    pub fn detect() -> Result<MacOS> {
21        unreachable!()
22    }
23
24    #[cfg(all(feature = "tokio", target_os = "macos"))]
25    pub async fn detect_async() -> Result<MacOS> {
26        let file = tokio::fs::read_to_string(PATH).await?;
27        parse(&file)
28    }
29
30    #[cfg(all(feature = "tokio", not(target_os = "macos")))]
31    pub async fn detect_async() -> Result<MacOS> {
32        unreachable!()
33    }
34}
35
36impl fmt::Display for MacOS {
37    fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(w, "macOS {}", self.version)
39    }
40}
41
42#[cfg(target_os = "macos")]
43fn parse(file: &str) -> Result<MacOS> {
44    use anyhow::Error;
45
46    let cur = std::io::Cursor::new(file.as_bytes());
47    let v = plist::Value::from_reader(cur)?;
48
49    let version = v
50        .as_dictionary()
51        .ok_or_else(|| Error::msg("SystemVersion.plist is not a dictionary"))?
52        .get("ProductVersion")
53        .ok_or_else(|| Error::msg("ProductVersion is missing"))?;
54
55    let version = version
56        .as_string()
57        .ok_or_else(|| Error::msg("Version is not a string"))?
58        .to_string();
59
60    Ok(MacOS { version })
61}
62
63#[cfg(test)]
64mod tests {
65    #[cfg(target_os = "macos")]
66    use super::*;
67
68    #[test]
69    #[cfg(target_os = "macos")]
70    fn detect_macos() {
71        let version = parse(r#"<?xml version="1.0" encoding="UTF-8"?>
72<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
73<plist version="1.0">
74<dict>
75    <key>ProductBuildVersion</key>
76    <string>17G11023</string>
77    <key>ProductCopyright</key>
78    <string>1983-2020 Apple Inc.</string>
79    <key>ProductName</key>
80    <string>Mac OS X</string>
81    <key>ProductUserVisibleVersion</key>
82    <string>10.13.6</string>
83    <key>ProductVersion</key>
84    <string>10.13.6</string>
85</dict>
86</plist>
87"#).unwrap();
88        assert_eq!(
89            MacOS {
90                version: "10.13.6".to_string(),
91            },
92            version
93        );
94    }
95}