use eyre::Result;
use nom::{
bytes::complete::{tag, take_until},
character::complete::not_line_ending,
sequence::separated_pair,
IResult,
};
use std::collections::HashMap;
const OS_RELEASE_PATH: &str = "/etc/os-release";
const OS_RELEASE_ID: &str = "ID";
const OS_RELEASE_VERSION_ID: &str = "VERSION_ID";
pub struct EtcOsRelease {
id: Option<String>,
version_id: Option<String>,
}
impl EtcOsRelease {
pub fn load() -> Result<Self> {
let etc_os_release_str = std::fs::read_to_string(OS_RELEASE_PATH)?;
Self::parse(&etc_os_release_str)
}
pub fn id(&self) -> Option<String> {
self.id.clone()
}
pub fn version_id(&self) -> Option<String> {
self.version_id.clone()
}
fn parse(etc_os_release_str: &str) -> Result<Self> {
let parsed_map = etc_os_release_str
.trim()
.lines()
.filter_map(|line| {
let parse_result: IResult<&str, (&str, &str)> =
separated_pair(take_until("="), tag("="), not_line_ending)(line);
parse_result.map(|(_, (key, value))| (key, value)).ok()
})
.collect::<HashMap<&str, &str>>();
let id = parsed_map
.get(OS_RELEASE_ID)
.map(|s| s.trim_matches('\"').to_string());
let version_id = parsed_map
.get(OS_RELEASE_VERSION_ID)
.map(|s| s.trim_matches('\"').to_string());
Ok(Self { id, version_id })
}
}
#[cfg(test)]
mod test {
use super::*;
use rstest::rstest;
#[rstest]
#[case::version_with_quotes("VERSION_ID=\"12.1\"\nNAME=\"Ubuntu\"", Some("12.1".to_string()))]
#[case::version_without_quotes("VERSION_ID=12.1\nNAME=\"Ubuntu\"", Some("12.1".to_string()))]
#[case::no_version("BAD_ID=\"12.1\"\nNAME=\"Ubuntu\"", None)]
fn test_version_id_parse(#[case] etc_os_release: &str, #[case] expected: Option<String>) {
let os_release = EtcOsRelease::parse(etc_os_release).unwrap();
assert_eq!(os_release.version_id(), expected);
}
#[rstest]
#[case::id_with_quotes("ID=\"ubuntu\"\nVERSION_ID=\"12.1\"", Some("ubuntu".to_string()))]
#[case::id_without_quotes("ID=ubuntu\nVERSION_ID=\"12.1\"", Some("ubuntu".to_string()))]
#[case::no_id("BAD_ID=ubuntu\nVERSION_ID=\"12.1\"", None)]
fn test_id_parse(#[case] etc_os_release: &str, #[case] expected: Option<String>) {
let os_release = EtcOsRelease::parse(etc_os_release).unwrap();
assert_eq!(os_release.id(), expected);
}
}