hyprshell-core-lib 0.7.0

hyprshell is a Rust-based GUI designed to enhance window management in hyprland
use crate::config::{Config, Plugin};
use anyhow::{bail, Context};
use ron::extensions::Extensions;
use ron::Options;
use std::ffi::OsStr;
use std::mem;
use std::path::Path;
use tracing::{span, Level};

pub fn load_config(config_path: &Path) -> anyhow::Result<Config> {
    let _span = span!(Level::TRACE, "load_config").entered();
    if !config_path.exists() {
        bail!("Config file does not exist, create it using `hyprshell config generate`");
    }
    let config = match config_path.extension().and_then(OsStr::to_str) {
        None | Some("ron") => {
            let options = Options::default()
                .with_default_extension(Extensions::IMPLICIT_SOME)
                .with_default_extension(Extensions::UNWRAP_NEWTYPES)
                .with_default_extension(Extensions::UNWRAP_VARIANT_NEWTYPES);
            let file = std::fs::File::open(config_path)
                .with_context(|| format!("Failed to open config at ({config_path:?})"))?;
            options
                .from_reader(file)
                .context("Failed to read ron config")?
        }
        Some("json") => {
            let file = std::fs::File::open(config_path)
                .with_context(|| format!("Failed to open config at ({config_path:?})"))?;
            serde_json::from_reader(file).context("Failed to read json config")?
        }
        #[cfg(feature = "toml_config")]
        Some("toml") => {
            use std::io::Read;
            let mut file = std::fs::File::open(config_path)
                .with_context(|| format!("Failed to open config at ({config_path:?})"))?;
            let mut content = String::new();
            file.read_to_string(&mut content)
                .context("Failed to read toml config")?;
            toml::from_str(&content).context("Failed to parse toml config")?
        }
        Some(ext) => bail!("Invalid config file extension: {} (check `FEATURES: ` debug log to see enabled extensions)", ext),
    };

    check(&config)?;

    Ok(config)
}

fn check(config: &Config) -> anyhow::Result<()> {
    if config
        .windows
        .as_ref()
        .map(|w| w.scale >= 15f64 || w.scale <= 0f64)
        .unwrap_or(false)
    {
        bail!("Scale factor must be less than 15 and greater than 0");
    }

    if let Some(l) = &config.launcher {
        let mut active_plugins: Vec<&Plugin> = vec![];
        for plugin in &l.plugins {
            if active_plugins
                .iter()
                .any(|p| mem::discriminant(*p) == mem::discriminant(plugin))
            {
                bail!("Duplicate plugin: {:?}", plugin);
            } else {
                active_plugins.push(plugin);
            }

            if let Plugin::WebSearch(config) = plugin {
                let mut used: Vec<&str> = vec![];
                for engine in config {
                    if used.contains(&engine.key.as_str()) {
                        bail!("Duplicate search engine key: {}", engine.key);
                    } else {
                        used.push(&engine.key);
                    }
                }
            }
            if let Plugin::Calc() = plugin {
                #[cfg(not(feature = "calc"))]
                {
                    bail!(
                        "Calc Plugin enabled but not compiled in, please enable the calc feature"
                    );
                }
            }
        }
    };

    Ok(())
}