rebecca-core 0.3.0

Core planning, safety, scanning, and history models for Rebecca.
Documentation
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::PathBuf;

use crate::{Platform, macos_paths};

pub trait Environment {
    fn get(&self, key: &str) -> Option<OsString>;
}

impl<E> Environment for &E
where
    E: Environment + ?Sized,
{
    fn get(&self, key: &str) -> Option<OsString> {
        (*self).get(key)
    }
}

#[derive(Debug, Clone, Default)]
pub struct SystemEnvironment;

impl Environment for SystemEnvironment {
    fn get(&self, key: &str) -> Option<OsString> {
        std::env::var_os(key)
    }
}

#[derive(Debug, Clone)]
pub struct PlatformEnvironment<E> {
    platform: Platform,
    inner: E,
}

impl<E> PlatformEnvironment<E> {
    pub fn new(platform: Platform, inner: E) -> Self {
        Self { platform, inner }
    }

    pub fn current(inner: E) -> Self {
        Self::new(Platform::current(), inner)
    }
}

impl<E> Environment for PlatformEnvironment<E>
where
    E: Environment,
{
    fn get(&self, key: &str) -> Option<OsString> {
        let value = self.inner.get(key);
        if self.platform == Platform::Linux && linux_xdg_default_suffix(key).is_some() {
            return value
                .filter(|value| !value.is_empty())
                .or_else(|| linux_xdg_default(key, &self.inner));
        }
        if self.platform == Platform::Macos && macos_paths::default_home_suffix(key).is_some() {
            return value
                .filter(|value| !value.is_empty())
                .or_else(|| macos_default(key, &self.inner));
        }
        value
    }
}

#[derive(Debug, Clone, Default)]
pub struct MapEnvironment {
    values: HashMap<String, OsString>,
}

impl MapEnvironment {
    pub fn new() -> Self {
        Self {
            values: HashMap::new(),
        }
    }

    pub fn with_var(mut self, key: impl Into<String>, value: impl Into<OsString>) -> Self {
        self.values.insert(key.into(), value.into());
        self
    }
}

impl Environment for MapEnvironment {
    fn get(&self, key: &str) -> Option<OsString> {
        self.values.get(key).cloned()
    }
}

impl<K, V> FromIterator<(K, V)> for MapEnvironment
where
    K: Into<String>,
    V: Into<OsString>,
{
    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
        let mut values = HashMap::new();
        for (key, value) in iter {
            values.insert(key.into(), value.into());
        }
        Self { values }
    }
}

fn linux_xdg_default(key: &str, env: &impl Environment) -> Option<OsString> {
    let suffix = linux_xdg_default_suffix(key)?;
    home_relative_default(env, suffix)
}

fn macos_default(key: &str, env: &impl Environment) -> Option<OsString> {
    let suffix = macos_paths::default_home_suffix(key)?;
    home_relative_default(env, suffix)
}

fn home_relative_default(env: &impl Environment, suffix: &[&str]) -> Option<OsString> {
    let home = env.get("HOME")?;
    if home.is_empty() {
        return None;
    }

    let mut path = PathBuf::from(home);
    for segment in suffix {
        path.push(segment);
    }
    Some(path.into_os_string())
}

fn linux_xdg_default_suffix(key: &str) -> Option<&'static [&'static str]> {
    match key {
        "XDG_CACHE_HOME" => Some(&[".cache"]),
        "XDG_CONFIG_HOME" => Some(&[".config"]),
        "XDG_DATA_HOME" => Some(&[".local", "share"]),
        "XDG_STATE_HOME" => Some(&[".local", "state"]),
        _ => None,
    }
}