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
use std::env::consts::{ARCH, OS};
#[cfg(target_os = "linux")]
use std::fs;
#[cfg(target_os = "linux")]
use std::path::Path;
use raw_cpuid::CpuId;
use wgpu::{Backends, Instance};
#[cfg(target_os = "windows")]
use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey};

pub struct OSProfile {
    pub os: &'static str,
    pub arch: &'static str,
    pub win_edition: Option<String>,
    pub is_wsl: Option<bool>,
    pub linux_distro: Option<String>,
}

impl OSProfile {
    pub fn new() -> Self {
        Self {
            os: OS,
            arch: ARCH,
            win_edition: None,
            is_wsl: None,
            linux_distro: None,
        }
    }

    /// Returns the Windows edition if a Windows system is available
    #[cfg(target_os = "windows")]
    pub fn win_edition(mut self) -> Self {
        let sub_key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
        let reg = RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey(sub_key).expect("Failed to open registry key");
        let edition: String = reg.get_value("EditionID").expect("Failed to get Windows edition from registry");
        
        self.win_edition = Some(edition);
        self
    }

    /// Returns the Linux distro if a Linux system is available
    #[cfg(target_os = "linux")]
    pub fn linux_distro(mut self) -> Self {
        let text = fs::read_to_string("/etc/os-release").expect("Failed to read /etc/os-release");
        let tokens = text.split("\n").collect::<Vec<&str>>();
        let pretty_name = tokens
            .iter()
            .filter(|line| line.contains("PRETTY_NAME"))
            .collect::<Vec<&&str>>();
        
        let distro = pretty_name[0].split("=").collect::<Vec<&str>>()[1].replace("\"", "");
        self.linux_distro = Some(distro);
        self
    }

    /// Returns true if the Linux host is running on WSL
    #[cfg(target_os = "linux")]
    pub fn is_wsl(mut self) -> Self {
        let path = Path::new("/proc/sys/fs/binfmt_misc/WSLInterop").exists();
        self.is_wsl = Some(path);
        self
    }

    pub fn build(self) -> Self {
        Self {
            os: self.os,
            arch: self.arch,
            win_edition: self.win_edition,
            is_wsl: self.is_wsl,
            linux_distro: self.linux_distro,
        }
    }
}

/// Returns the CPU model
pub fn cpu() -> String {
    let cpuid = CpuId::new();
    let brand = cpuid.get_processor_brand_string().expect("Unsupported CPU");
    
    brand
    .as_str()
    .to_string()
}

/// Returns the number of CPU cores available for processing
pub fn cores() -> u32 {
    let cpuid = CpuId::new();
    let proc_cap_features = cpuid
        .get_processor_capacity_feature_info()
        .expect("Unsupported CPU");
    
    let cores = proc_cap_features.maximum_logical_processors();
    cores as u32
}

/// Returns the GPU model
pub fn gpu() -> Option<String> {
    let instance = Instance::default();
    
    for adapter in instance.enumerate_adapters(Backends::all()) {
        let name = adapter.get_info().name;
        return Some(name);
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_profile() {
        let profile = OSProfile::new().build();
        assert_eq!(profile.os, OS);
        assert_eq!(profile.arch, ARCH);
    }

    #[test]
    fn test_cpu() {
        let cpu = cpu();
        assert!(!cpu.is_empty());
    }

    #[test]
    fn test_cores() {
        let cores = cores();
        assert!(cores > 0);
    }

    #[test]
    fn test_gpu() {
        let gpu = gpu();
        assert!(gpu.is_some());
    }
}