use std::{
convert::Infallible,
fs::File,
io::{self, BufRead, BufReader},
path::{Path, PathBuf},
str::FromStr,
};
use crate::{entry::OsReleaseLine, OsRelease, OsReleaseEntry};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("no os-release file found")]
NoOsRelease,
#[error("failed to open os-release file: {err:?}")]
Open {
path: PathBuf,
#[source]
err: std::io::Error,
},
#[error("failed to read os-release file: {err:?}")]
Read {
#[source]
err: std::io::Error,
},
}
impl OsRelease {
pub fn open() -> Result<Self, Error> {
let path = os_release_path().ok_or(Error::NoOsRelease)?;
let file = File::open(path).map_err(|err| Error::Open {
path: path.to_owned(),
err,
})?;
Self::from_reader(file)
}
pub fn from_reader(reader: impl io::Read) -> Result<Self, Error> {
let reader = BufReader::new(reader);
reader
.lines()
.collect::<Result<_, _>>()
.map_err(|err| Error::Read { err })
}
}
impl<'a> FromIterator<OsReleaseEntry<'a>> for OsRelease {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = OsReleaseEntry<'a>>,
{
Self {
fields: iter
.into_iter()
.map(|entry| (entry.key().to_owned(), entry.value().to_owned()))
.collect(),
}
}
}
impl<'a> FromIterator<&'a str> for OsRelease {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = &'a str>,
{
iter.into_iter()
.filter_map(|line| {
OsReleaseLine::from_str(line)
.ok()
.and_then(|line| line.into_entry())
})
.collect()
}
}
impl FromIterator<String> for OsRelease {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = String>,
{
iter.into_iter()
.filter_map(|line| {
OsReleaseLine::from_str(&line)
.ok()
.and_then(|line| line.into_entry())
})
.collect()
}
}
impl FromStr for OsRelease {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.lines().collect())
}
}
fn os_release_path() -> Option<&'static Path> {
[
Path::new("/etc/os-release"),
Path::new("/usr/lib/os-release"),
]
.into_iter()
.find(|path| path.exists())
}