Skip to main content

kindly_tools/
platform.rs

1use std::fmt;
2
3/// Represents the detected platform
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Platform {
6    Linux,
7    MacOS,
8    Windows,
9    Unknown,
10}
11
12impl Platform {
13    /// Detect the current platform
14    pub fn detect() -> Self {
15        #[cfg(target_os = "linux")]
16        return Platform::Linux;
17
18        #[cfg(target_os = "macos")]
19        return Platform::MacOS;
20
21        #[cfg(target_os = "windows")]
22        return Platform::Windows;
23
24        #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
25        return Platform::Unknown;
26    }
27
28    /// Get the platform's name
29    pub fn name(&self) -> &'static str {
30        match self {
31            Platform::Linux => "Linux",
32            Platform::MacOS => "macOS",
33            Platform::Windows => "Windows",
34            Platform::Unknown => "Unknown",
35        }
36    }
37
38    /// Get the platform's executable extension
39    pub fn exe_extension(&self) -> &'static str {
40        match self {
41            Platform::Windows => ".exe",
42            _ => "",
43        }
44    }
45
46    /// Get the platform's dynamic library extension
47    pub fn dylib_extension(&self) -> &'static str {
48        match self {
49            Platform::Linux => ".so",
50            Platform::MacOS => ".dylib",
51            Platform::Windows => ".dll",
52            Platform::Unknown => "",
53        }
54    }
55
56    /// Get the platform's static library extension
57    pub fn staticlib_extension(&self) -> &'static str {
58        match self {
59            Platform::Windows => ".lib",
60            _ => ".a",
61        }
62    }
63
64    /// Check if the platform is Unix-like
65    pub fn is_unix(&self) -> bool {
66        matches!(self, Platform::Linux | Platform::MacOS)
67    }
68
69    /// Get shell command for the platform
70    pub fn shell_command(&self) -> (&'static str, &'static [&'static str]) {
71        match self {
72            Platform::Windows => ("cmd", &["/C"]),
73            _ => ("sh", &["-c"]),
74        }
75    }
76
77    /// Get the platform's package manager
78    pub fn package_manager(&self) -> Option<&'static str> {
79        match self {
80            Platform::Linux => {
81                // Try to detect the specific Linux distribution
82                if std::path::Path::new("/etc/debian_version").exists() {
83                    Some("apt")
84                } else if std::path::Path::new("/etc/redhat-release").exists() {
85                    Some("yum")
86                } else if std::path::Path::new("/etc/arch-release").exists() {
87                    Some("pacman")
88                } else if std::path::Path::new("/etc/alpine-release").exists() {
89                    Some("apk")
90                } else {
91                    None
92                }
93            },
94            Platform::MacOS => Some("brew"),
95            Platform::Windows => Some("winget"),
96            Platform::Unknown => None,
97        }
98    }
99
100    /// Get common paths for the platform
101    pub fn common_paths(&self) -> CommonPaths {
102        match self {
103            Platform::Linux => CommonPaths {
104                config: "~/.config",
105                cache: "~/.cache",
106                data: "~/.local/share",
107                temp: "/tmp",
108            },
109            Platform::MacOS => CommonPaths {
110                config: "~/Library/Application Support",
111                cache: "~/Library/Caches",
112                data: "~/Library/Application Support",
113                temp: "/tmp",
114            },
115            Platform::Windows => CommonPaths {
116                config: "%APPDATA%",
117                cache: "%LOCALAPPDATA%",
118                data: "%APPDATA%",
119                temp: "%TEMP%",
120            },
121            Platform::Unknown => CommonPaths {
122                config: "~/.config",
123                cache: "~/.cache",
124                data: "~/.local/share",
125                temp: "/tmp",
126            },
127        }
128    }
129}
130
131impl fmt::Display for Platform {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{}", self.name())
134    }
135}
136
137/// Common directory paths for different platforms
138#[derive(Debug, Clone)]
139pub struct CommonPaths {
140    pub config: &'static str,
141    pub cache: &'static str,
142    pub data: &'static str,
143    pub temp: &'static str,
144}
145
146/// Architecture detection
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum Architecture {
149    X86,
150    X64,
151    Arm,
152    Arm64,
153    Unknown,
154}
155
156impl Architecture {
157    /// Detect the current architecture
158    pub fn detect() -> Self {
159        #[cfg(target_arch = "x86")]
160        return Architecture::X86;
161
162        #[cfg(target_arch = "x86_64")]
163        return Architecture::X64;
164
165        #[cfg(target_arch = "arm")]
166        return Architecture::Arm;
167
168        #[cfg(target_arch = "aarch64")]
169        return Architecture::Arm64;
170
171        #[cfg(not(any(
172            target_arch = "x86",
173            target_arch = "x86_64",
174            target_arch = "arm",
175            target_arch = "aarch64"
176        )))]
177        return Architecture::Unknown;
178    }
179
180    /// Get the architecture's name
181    pub fn name(&self) -> &'static str {
182        match self {
183            Architecture::X86 => "x86",
184            Architecture::X64 => "x64",
185            Architecture::Arm => "arm",
186            Architecture::Arm64 => "arm64",
187            Architecture::Unknown => "unknown",
188        }
189    }
190
191    /// Check if the architecture is 64-bit
192    pub fn is_64bit(&self) -> bool {
193        matches!(self, Architecture::X64 | Architecture::Arm64)
194    }
195}
196
197impl fmt::Display for Architecture {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(f, "{}", self.name())
200    }
201}
202
203/// Get full platform information
204pub struct PlatformInfo {
205    pub platform: Platform,
206    pub architecture: Architecture,
207    pub hostname: Option<String>,
208    pub username: Option<String>,
209}
210
211impl PlatformInfo {
212    /// Gather all platform information
213    pub fn gather() -> Self {
214        Self {
215            platform: Platform::detect(),
216            architecture: Architecture::detect(),
217            hostname: hostname::get().ok().and_then(|h| h.into_string().ok()),
218            username: std::env::var("USER")
219                .or_else(|_| std::env::var("USERNAME"))
220                .ok(),
221        }
222    }
223}
224
225impl fmt::Display for PlatformInfo {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write!(
228            f,
229            "{} {} (host: {}, user: {})",
230            self.platform,
231            self.architecture,
232            self.hostname.as_deref().unwrap_or("unknown"),
233            self.username.as_deref().unwrap_or("unknown")
234        )
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_platform_detection() {
244        let platform = Platform::detect();
245        assert_ne!(platform, Platform::Unknown);
246    }
247
248    #[test]
249    fn test_architecture_detection() {
250        let arch = Architecture::detect();
251        assert_ne!(arch, Architecture::Unknown);
252    }
253
254    #[test]
255    fn test_platform_info() {
256        let info = PlatformInfo::gather();
257        println!("Platform info: {}", info);
258    }
259}