1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum OsFamily {
10 Unix,
12 Windows,
14 Wasm,
16 Unknown,
18 Custom(String),
20}
21
22impl fmt::Display for OsFamily {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 match self {
25 Self::Unix => formatter.write_str("unix"),
26 Self::Windows => formatter.write_str("windows"),
27 Self::Wasm => formatter.write_str("wasm"),
28 Self::Unknown => formatter.write_str("unknown"),
29 Self::Custom(value) => formatter.write_str(value),
30 }
31 }
32}
33
34impl FromStr for OsFamily {
35 type Err = OsFamilyParseError;
36
37 fn from_str(value: &str) -> Result<Self, Self::Err> {
38 let trimmed = value.trim();
39
40 if trimmed.is_empty() {
41 return Err(OsFamilyParseError::Empty);
42 }
43
44 let key = trimmed.to_ascii_lowercase().replace(['_', ' '], "-");
45
46 match key.as_str() {
47 "unix" | "linux" | "macos" | "mac-os" | "darwin" | "bsd" | "freebsd" | "openbsd"
48 | "netbsd" => Ok(Self::Unix),
49 "windows" | "win" | "win32" | "win64" | "mswindows" => Ok(Self::Windows),
50 "wasm" | "webassembly" | "wasi" | "wasip1" | "wasip2" => Ok(Self::Wasm),
51 "unknown" => Ok(Self::Unknown),
52 _ => Ok(Self::Custom(trimmed.to_string())),
53 }
54 }
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum OsFamilyParseError {
60 Empty,
62}
63
64impl fmt::Display for OsFamilyParseError {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::Empty => formatter.write_str("OS family cannot be empty"),
68 }
69 }
70}
71
72impl Error for OsFamilyParseError {}
73
74#[cfg(test)]
75mod tests {
76 use super::{OsFamily, OsFamilyParseError};
77
78 #[test]
79 fn parses_known_families() -> Result<(), OsFamilyParseError> {
80 assert_eq!("unix".parse::<OsFamily>()?, OsFamily::Unix);
81 assert_eq!("windows".parse::<OsFamily>()?, OsFamily::Windows);
82 assert_eq!("wasm".parse::<OsFamily>()?, OsFamily::Wasm);
83 assert_eq!("unknown".parse::<OsFamily>()?, OsFamily::Unknown);
84 Ok(())
85 }
86
87 #[test]
88 fn parses_obvious_aliases() -> Result<(), OsFamilyParseError> {
89 assert_eq!("linux".parse::<OsFamily>()?, OsFamily::Unix);
90 assert_eq!("darwin".parse::<OsFamily>()?, OsFamily::Unix);
91 assert_eq!("win32".parse::<OsFamily>()?, OsFamily::Windows);
92 assert_eq!("wasi".parse::<OsFamily>()?, OsFamily::Wasm);
93 Ok(())
94 }
95
96 #[test]
97 fn stores_custom_families() -> Result<(), OsFamilyParseError> {
98 assert_eq!(
99 "plan9".parse::<OsFamily>()?,
100 OsFamily::Custom("plan9".to_string())
101 );
102 Ok(())
103 }
104
105 #[test]
106 fn rejects_empty_family_names() {
107 assert_eq!(" ".parse::<OsFamily>(), Err(OsFamilyParseError::Empty));
108 }
109
110 #[test]
111 fn displays_canonical_names() {
112 assert_eq!(OsFamily::Unix.to_string(), "unix");
113 assert_eq!(OsFamily::Windows.to_string(), "windows");
114 assert_eq!(OsFamily::Wasm.to_string(), "wasm");
115 assert_eq!(
116 OsFamily::Custom("custom-os".to_string()).to_string(),
117 "custom-os"
118 );
119 }
120}