nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use std::path::PathBuf;

use serde::Serialize;
use serde::de::DeserializeOwned;

pub struct Settings<T> {
    pub data: T,
    app_name: String,
}

impl<T: Default> Default for Settings<T> {
    fn default() -> Self {
        Self {
            data: T::default(),
            app_name: String::new(),
        }
    }
}

impl<T: Serialize + DeserializeOwned + Default> Settings<T> {
    pub fn load(app_name: impl Into<String>) -> Self {
        let app_name = app_name.into();
        let data = Self::load_from_disk(&app_name).unwrap_or_default();
        Self { data, app_name }
    }

    pub fn save(&self) -> Result<(), String> {
        let path = self
            .settings_path()
            .ok_or_else(|| "Could not determine config directory".to_string())?;

        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)
                .map_err(|error| format!("Failed to create config directory: {}", error))?;
        }

        let json = serde_json::to_string_pretty(&self.data)
            .map_err(|error| format!("Failed to serialize settings: {}", error))?;

        std::fs::write(&path, json)
            .map_err(|error| format!("Failed to write settings file: {}", error))
    }

    pub fn settings_path(&self) -> Option<PathBuf> {
        dirs::config_dir().map(|config_dir| config_dir.join(&self.app_name).join("settings.json"))
    }

    fn load_from_disk(app_name: &str) -> Option<T> {
        let config_dir = dirs::config_dir()?;
        let path = config_dir.join(app_name).join("settings.json");
        let contents = std::fs::read_to_string(path).ok()?;
        serde_json::from_str(&contents).ok()
    }
}