bawa 0.1.0

A TUI game save organizer
Documentation
pub mod keys;
pub mod options;
pub mod theme;

use self::{
    keys::{KeyBindings, UserKeyBindings},
    options::{Options, UserOptions},
};
use crate::{cli::CLAP_ARGS, utils};
use anyhow::Result;
use serde::Deserialize;
use std::{
    fs,
    path::PathBuf,
    sync::{LazyLock, Once},
};
use theme::{Theme, UserTheme};

static CONFIG: LazyLock<Config> = LazyLock::new(|| match Config::new() {
    Ok(config) => config,
    Err(e) => {
        eprintln!("{e:?}");
        std::process::exit(1);
    }
});
pub static OPTIONS: LazyLock<&Options> = LazyLock::new(|| &CONFIG.options);
pub static KEY_BINDINGS: LazyLock<&KeyBindings> = LazyLock::new(|| &CONFIG.key_bindings);
pub static THEME: LazyLock<&Theme> = LazyLock::new(|| &CONFIG.theme);
pub static SKIP_CONFIG: Once = Once::new();
const CONFIG_FILE: &str = "config.toml";

#[derive(Deserialize)]
struct UserConfig {
    #[serde(flatten)]
    options: UserOptions,
    theme: Option<UserTheme>,
    key_bindings: Option<UserKeyBindings>,
}

#[derive(Default)]
pub struct Config {
    pub options: Options,
    pub theme: Theme,
    pub key_bindings: KeyBindings,
}

impl Config {
    pub fn new() -> Result<Self> {
        let mut config = Self::default();

        if SKIP_CONFIG.is_completed() {
            return Ok(config);
        }

        let config_path = (!CLAP_ARGS.get_flag("no_config")).then_some(
            match CLAP_ARGS.get_one::<PathBuf>("config") {
                Some(path) => path.to_owned(),
                None => utils::get_config_dir()?.join(CONFIG_FILE),
            },
        );

        let config_str = config_path.as_ref().map(fs::read_to_string);

        if let Some(Ok(user_config)) = &config_str {
            let user_config = toml::from_str::<UserConfig>(user_config)?;
            config.merge(user_config)?;
        }

        Ok(config)
    }
}

impl MergeConfig for Config {
    type Other = UserConfig;

    fn merge(&mut self, user_config: Self::Other) -> Result<()> {
        self.options.merge(user_config.options)?;

        if let Some(theme) = user_config.theme {
            self.theme.merge(theme)?;
        }

        if let Some(key_bindings) = user_config.key_bindings {
            self.key_bindings.merge(key_bindings)?;
        }

        Ok(())
    }
}

trait MergeConfig {
    type Other;

    fn merge(&mut self, user_config: Self::Other) -> Result<()>;
}

#[cfg(test)]
mod tests {
    use super::UserConfig;
    use std::{fs, path::PathBuf};

    pub fn read_example_config() -> UserConfig {
        let config_path = PathBuf::from("example/config.toml");
        let config_str = fs::read_to_string(config_path).unwrap();
        toml::from_str::<UserConfig>(&config_str).unwrap()
    }
}