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
use regex::Regex;
use std::fs::File;
use std::io::prelude::*;
use std::io::Error;
use utils;

#[derive(Debug, PartialEq)]
pub struct OSRelease {
    pub distro: Option<String>,
    pub version: Option<String>,
}

fn read_file(filename: &str) -> Result<String, Error> {
    let mut file = File::open(filename)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

pub fn retrieve() -> Option<OSRelease> {
    if utils::file_exists("/etc/os-release") {
        if let Ok(release) = read_file("/etc/os-release") {
            Some(parse(release))
        } else {
            None
        }
    } else {
        if let Ok(release) = read_file("/usr/lib/os-release") {
            Some(parse(release))
        } else {
            None
        }
    }
}

pub fn parse(file: String) -> OSRelease {
    let distrib_regex = Regex::new(r#"NAME="(\w+)"#).unwrap();
    let version_regex = Regex::new(r#"VERSION_ID="?([\w\.]+)"#).unwrap();

    let distro = match distrib_regex.captures_iter(&file).next() {
        Some(m) => match m.get(1) {
            Some(distro) => Some(distro.as_str().to_owned()),
            None => None,
        },
        None => None,
    };

    let version = match version_regex.captures_iter(&file).next() {
        Some(m) => match m.get(1) {
            Some(version) => Some(version.as_str().to_owned()),
            None => None,
        },
        None => None,
    };

    OSRelease { distro, version }
}

mod tests {
    use super::*;

    #[test]
    fn parse_ubuntu_18_04_os_release() {
        let sample = "\
        NAME=\"Ubuntu\"\
        VERSION=\"18.04 LTS (Bionic Beaver)\"
        ID=ubuntu
        ID_LIKE=debian
        PRETTY_NAME=\"Ubuntu 18.04 LTS\"\
        VERSION_ID=\"18.04\"\
        HOME_URL=\"https://www.ubuntu.com/\"\
        SUPPORT_URL=\"https://help.ubuntu.com/\"\
        BUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu\"\
        PRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\
        VERSION_CODENAME=bionic
        UBUNTU_CODENAME=bionic
        ".to_string();

        assert_eq!(
            parse(sample),
            OSRelease {
                distro: Some("Ubuntu".to_string()),
                version: Some("18.04".to_string()),
            }
        );
    }

    #[test]
    fn parse_alpine_3_9_5_os_release() {
        let sample = "\
        NAME=\"Alpine Linux\"
        ID=alpine
        VERSION_ID=3.9.5
        PRETTY_NAME=\"Alpine Linux v3.9\"
        HOME_URL=\"https://alpinelinux.org/\"
        BUG_REPORT_URL=\"https://bugs.alpinelinux.org/\"
        ".to_string();

        assert_eq!(
            parse(sample),
            OSRelease {
                distro: Some("Alpine".to_string()),
                version: Some("3.9.5".to_string()),
            }
        );
    }
    
    #[test]
    fn parse_deepin_20_3_os_release() {
        let sample = "\
        PRETTY_NAME=\"Deepin 20.3\"
        NAME=\"Deepin\"
        VERSION_ID=\"20.3\"
        VERSION=\"20.3\"
        ID=Deepin
        HOME_URL=\"https://www.deepin.org/\"
        ".to_string();

        assert_eq!(
            parse(sample),
            OSRelease {
                distro: Some("Deepin".to_string()),
                version: Some("20.3".to_string()),
            }
        );
    }
}