use std::{env::consts::OS, fs};
use camino::{Utf8Path, Utf8PathBuf};
use serde::Serialize;
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub(crate) enum Error {
#[error("unable to find cache_dir")]
Cache,
#[error("unable to find config_dir")]
Config,
#[error("unable to find home_dir")]
Home,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Serialize)]
pub(crate) struct Facts {
pub cache_dir: Utf8PathBuf,
pub config_dir: Utf8PathBuf,
pub home_dir: Utf8PathBuf,
pub is_distro_archlinux: bool,
pub is_distro_armbian: bool,
pub is_distro_raspbian: bool,
pub is_os_linux: bool,
pub is_os_macos: bool,
pub is_os_windows: bool,
pub main_file: Utf8PathBuf,
}
impl Facts {
pub fn gather() -> Result {
let is_os_linux = OS == "linux";
let os_release =
fs::read_to_string(Utf8Path::new(OS_RELEASE_PATH)).unwrap_or_else(|_| String::new());
Ok(Self {
cache_dir: Utf8PathBuf::try_from(dirs::cache_dir().ok_or(Error::Cache)?)
.expect("cache directory path has invalid characters"),
config_dir: Utf8PathBuf::try_from(dirs::config_dir().ok_or(Error::Config)?)
.expect("config directory path has invalid characters"),
home_dir: Utf8PathBuf::try_from(dirs::home_dir().ok_or(Error::Home)?)
.expect("home directory path has invalid characters"),
is_distro_archlinux: is_os_linux && is_distro_archlinux(&os_release),
is_distro_armbian: is_os_linux && is_distro_armbian(&os_release),
is_distro_raspbian: is_os_linux && is_distro_raspbian(&os_release),
is_os_linux,
is_os_macos: OS == "macos",
is_os_windows: OS == "windows",
..Default::default()
})
}
}
impl Default for Facts {
fn default() -> Self {
Self {
cache_dir: Utf8PathBuf::new(),
config_dir: Utf8PathBuf::new(),
home_dir: Utf8PathBuf::new(),
is_distro_archlinux: false,
is_distro_armbian: false,
is_distro_raspbian: false,
is_os_linux: false,
is_os_macos: false,
is_os_windows: false,
#[cfg(test)]
main_file: Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("main.toml"),
#[cfg(not(test))]
main_file: Utf8PathBuf::new(),
}
}
}
pub(crate) type Result = std::result::Result<Facts, Error>;
const OS_RELEASE_ARCHLINUX: &str = "ID=arch";
const OS_RELEASE_ARMBIAN: &str = r#"NAME="Armbian"#;
const OS_RELEASE_DEBIAN: &str = "ID=debian";
const OS_RELEASE_DEBIAN_LIKE: &str = "ID_LIKE=debian";
const OS_RELEASE_PATH: &str = "/etc/os-release";
const OS_RELEASE_RASPBIAN: &str = "ID=raspbian";
fn is_distro_archlinux(os_release: &str) -> bool {
os_release.contains(OS_RELEASE_ARCHLINUX)
}
fn is_distro_armbian(os_release: &str) -> bool {
os_release.contains(OS_RELEASE_DEBIAN) && os_release.contains(OS_RELEASE_ARMBIAN)
}
fn is_distro_raspbian(os_release: &str) -> bool {
os_release.contains(OS_RELEASE_DEBIAN_LIKE) && os_release.contains(OS_RELEASE_RASPBIAN)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gather_result_ok() {
let got = Facts::gather();
assert!(got.is_ok());
}
}