1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//  OS.rs
//    by Lut99
//
//  Created:
//    01 May 2024, 10:10:50
//  Last edited:
//    01 May 2024, 10:27:41
//  Auto updated?
//    Yes
//
//  Description:
//!   Implements a OS-string enum that allows us to communicate operating
//!   system (types) to/from users.
//

use std::error::Error;
use std::fmt::{Display, Formatter, Result as FResult};
use std::hash::Hash;
use std::str::FromStr;

use serde::{Deserialize, Serialize};


/***** ERRORS *****/
/// Defines the error that may occur when parsing operating systems
#[derive(Debug)]
pub enum ParseError {
    /// Running on an OS we do not known.
    #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
    UnknownLocalOs,
    /// Could not deserialize the given string
    UnknownOs { raw: String },
}
impl Display for ParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        use ParseError::*;
        match self {
            #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
            UnknownLocalOs => write!(f, "Running on an unknown OS, cannot automatically resolve '$LOCAL' OS string"),
            UnknownOs { raw } => write!(f, "Unknown operating system '{raw}'"),
        }
    }
}
impl Error for ParseError {}





/***** AUXILLARY *****/
/// A formatter for operating systems that writes it in a way that is used to download cfssl binaries.
#[derive(Debug)]
pub struct OsCfsslFormatter<'o> {
    /// The operating system to format.
    os: &'o Os,
}
impl<'o> Display for OsCfsslFormatter<'o> {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        match self.os {
            Os::Windows => write!(f, "windows"),
            Os::MacOS => write!(f, "darwin"),
            Os::Linux => write!(f, "linux"),
        }
    }
}





/***** LIBRARY *****/
/// The Os enum defines possible operating systems that we know of and love
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Os {
    /// Windows 95/98/2000/XP/Vista/7/8/10/11/w/e
    #[serde(alias = "win")]
    Windows,
    /// Apple's operating system
    #[serde(alias = "darwin")]
    MacOS,
    /// Linux of any shape.
    Linux,
}

impl Os {
    /// Constant referring to the compiled (=host) operating system
    #[cfg(windows)]
    pub const HOST: Self = Self::Windows;
    #[cfg(target_os = "macos")]
    pub const HOST: Self = Self::MacOS;
    #[cfg(target_os = "linux")]
    pub const HOST: Self = Self::Linux;

    /// Returns if this operating system points to Windows.
    ///
    /// # Returns
    /// True if we're [`Os::Windows`], or false otherwise.
    #[inline]
    pub fn is_windows(&self) -> bool { matches!(self, Self::Windows) }

    /// Returns if this operating system is Unix-compatible.
    ///
    /// # Returns
    /// True if we're [`Os::MacOS`] or [`Os::Linux`], false if we're [`Os::Windows`].
    #[inline]
    pub fn is_unix(&self) -> bool { matches!(self, Self::MacOS) || matches!(self, Self::Linux) }

    /// Returns if this operating system points to macOS.
    ///
    /// # Returns
    /// True if we're [`Os::MacOS`], or false otherwise.
    #[inline]
    pub fn is_macos(&self) -> bool { matches!(self, Self::MacOS) }

    /// Returns if this operating system points to Linux.
    ///
    /// # Returns
    /// True if we're [`Os::Linux`], or false otherwise.
    #[inline]
    pub fn is_linux(&self) -> bool { matches!(self, Self::Linux) }

    /// Allows one to serialize the operating system for use to download cfssl binaries.
    ///
    /// # Returns
    /// An `OsCfsslFormatter` that implements [`Display`]` in a cfssl-compatible way.
    #[inline]
    pub fn cfssl(&self) -> OsCfsslFormatter { OsCfsslFormatter { os: self } }
}

impl Display for Os {
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        match self {
            Self::Windows => write!(f, "Windows"),
            Self::MacOS => write!(f, "macOS"),
            Self::Linux => write!(f, "Linux"),
        }
    }
}
impl FromStr for Os {
    type Err = ParseError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "win" | "windows" => Ok(Self::Windows),
            "darwin" | "macos" => Ok(Self::MacOS),
            "linux" => Ok(Self::Linux),

            // Meta-argument for resolving the local architecture
            #[cfg(windows)]
            "$LOCAL" => Ok(Self::Windows),
            #[cfg(target_os = "macos")]
            "$LOCAL" => Ok(Self::MacOS),
            #[cfg(target_os = "linux")]
            "$LOCAL" => Ok(Self::Linux),
            #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
            "$LOCAL" => Err(ParseError::UnknownLocalOs),

            raw => Err(ParseError::UnknownOs { raw: raw.to_string() }),
        }
    }
}