telar 0.1.5

A modular Rust UI framework with its own template language, reactive signals and a self-contained renderer.
Documentation
use serde::{Deserialize, Serialize};
use services_core::AppPathsProvider;
use std::path::PathBuf;

use crate::config::RendererBackend;

#[derive(Serialize, Deserialize, Clone, Default)]
pub struct UserPrefs {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub backend: Option<RendererBackend>,
}

impl UserPrefs {
    pub fn load(app_name: &str, paths: &dyn AppPathsProvider) -> Self {
        let Some(path) = prefs_path(app_name, paths) else {
            return Self::default();
        };
        match std::fs::read_to_string(&path) {
            Ok(content) => toml::from_str(&content).unwrap_or_else(|e| {
                tracing::warn!("Failed to parse {}: {e}", path.display());
                Self::default()
            }),
            Err(_) => Self::default(),
        }
    }

    pub fn save(&self, app_name: &str, paths: &dyn AppPathsProvider) -> Result<(), String> {
        let path =
            prefs_path(app_name, paths).ok_or_else(|| "Cannot resolve config dir".to_string())?;

        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
        }

        let content = toml::to_string_pretty(self).map_err(|e| e.to_string())?;
        std::fs::write(&path, content).map_err(|e| e.to_string())
    }
}

fn prefs_path(app_name: &str, paths: &dyn AppPathsProvider) -> Option<PathBuf> {
    paths
        .config_dir()
        .map(|base| base.join(app_name).join("prefs.toml"))
}