lla_plugin_utils 0.5.5

Blazing Fast and highly customizable ls Replacement with Superpowers
Documentation
use serde::{de::DeserializeOwned, Serialize};
use std::path::PathBuf;

pub trait PluginConfig: Default + Serialize + DeserializeOwned {
    fn validate(&self) -> Result<(), String> {
        Ok(())
    }
}

pub struct ConfigManager<T: PluginConfig> {
    config: T,
    config_path: PathBuf,
}

impl<T: PluginConfig> ConfigManager<T> {
    pub fn new(plugin_name: &str) -> Self {
        let config_path = dirs::home_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join(".config")
            .join("lla")
            .join("plugins")
            .join(plugin_name)
            .join("config.toml");

        if let Some(parent) = config_path.parent() {
            if let Err(e) = std::fs::create_dir_all(parent) {
                eprintln!("[ConfigManager] Failed to create config directory: {}", e);
            }
        }

        let config = if config_path.exists() {
            Self::load_config(&config_path).unwrap_or_else(|e| {
                eprintln!(
                    "[ConfigManager] Failed to load config: {}, using default",
                    e
                );
                T::default()
            })
        } else {
            let config = T::default();
            match toml::to_string_pretty(&config) {
                Ok(content) => {
                    if let Err(e) = std::fs::write(&config_path, content) {
                        eprintln!("[ConfigManager] Failed to write initial config: {}", e);
                    }
                }
                Err(e) => eprintln!("[ConfigManager] Failed to serialize default config: {}", e),
            }
            config
        };

        Self {
            config,
            config_path,
        }
    }

    pub fn get(&self) -> &T {
        &self.config
    }

    pub fn get_mut(&mut self) -> &mut T {
        &mut self.config
    }

    pub fn save(&self) -> Result<(), String> {
        self.config.validate()?;
        if let Some(parent) = self.config_path.parent() {
            std::fs::create_dir_all(parent)
                .map_err(|e| format!("Failed to create config directory: {}", e))?;
        }
        let content = toml::to_string_pretty(&self.config)
            .map_err(|e| format!("Failed to serialize config: {}", e))?;
        std::fs::write(&self.config_path, content)
            .map_err(|e| format!("Failed to write config file: {}", e))?;
        Ok(())
    }

    pub fn reload(&mut self) -> Result<(), String> {
        if let Ok(new_config) = Self::load_config(&self.config_path) {
            self.config = new_config;
            Ok(())
        } else {
            Err("Failed to reload configuration".to_string())
        }
    }

    fn load_config(path: &PathBuf) -> Result<T, String> {
        let content = std::fs::read_to_string(path)
            .map_err(|e| format!("Failed to read config file: {}", e))?;

        let config: T =
            toml::from_str(&content).map_err(|e| format!("Failed to parse config file: {}", e))?;

        config.validate()?;

        Ok(config)
    }
}

pub struct ConfigBuilder<T: Default> {
    config: T,
}

impl<T: Default> ConfigBuilder<T> {
    pub fn new() -> Self {
        Self {
            config: T::default(),
        }
    }

    pub fn build(self) -> T {
        self.config
    }
}

impl<T: Default> Default for ConfigBuilder<T> {
    fn default() -> Self {
        Self::new()
    }
}

#[macro_export]
macro_rules! plugin_config {
    (
        $(#[$meta:meta])*
        pub struct $name:ident {
            $(
                $(#[$field_meta:meta])*
                pub $field:ident: $type:ty $(= $default:expr)?
            ),* $(,)?
        }
    ) => {
        $(#[$meta])*
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
        pub struct $name {
            $(
                $(#[$field_meta])*
                pub $field: $type,
            )*
        }

        impl Default for $name {
            fn default() -> Self {
                Self {
                    $(
                        $field: plugin_config!(@default $($default)?),
                    )*
                }
            }
        }

        impl $crate::config::PluginConfig for $name {}
    };

    (@default) => {
        Default::default()
    };

    (@default $expr:expr) => {
        $expr
    };
}