tuning 0.4.0

ansible-like tool with a smaller scope, focused primarily on complementing dotfiles for cross-machine bliss
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());
    }
}