lade 0.2.0

Load automatically and hierarchically env variables from secret stores (Infisical/Dopper/1Password).
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

use anyhow::Result;
use indexmap::IndexMap;
use lade_sdk::hydrate;
use regex::Regex;
use serde::Deserialize;
use std::fs::File;

#[derive(Deserialize, Debug)]
pub struct LadeFile {
    #[serde(flatten)]
    pub commands: IndexMap<String, HashMap<String, String>>,
}

impl LadeFile {
    pub fn from_path(path: &Path) -> Result<LadeFile> {
        let file = File::open(path).unwrap();
        let mut config: serde_yaml::Value = serde_yaml::from_reader(file)?;
        config.apply_merge()?;
        let config: LadeFile = serde_yaml::from_value(config)?;
        Ok(config)
    }

    pub fn build(path: PathBuf) -> Result<Config> {
        let mut configs: Vec<LadeFile> = Vec::default();
        let mut path = path;

        while {
            let yaml = path.join("lade.yaml");
            if yaml.exists() {
                configs.push(LadeFile::from_path(&yaml)?);
            } else {
                let yml = path.join("lade.yml");
                if yml.exists() {
                    configs.push(LadeFile::from_path(&yml)?);
                }
            }

            match path.parent() {
                Some(parent) => {
                    path = parent.to_path_buf();
                    true
                }
                None => false,
            }
        } {}

        let mut matches = Vec::default();

        configs.reverse();
        for config in configs.into_iter() {
            for (key, value) in config.commands.into_iter() {
                matches.push((Regex::new(&key)?, value));
            }
        }

        Ok(Config { matches })
    }
}

pub struct Config {
    matches: Vec<(Regex, HashMap<String, String>)>,
}

impl Config {
    fn collect(&self, command: String) -> Result<HashMap<String, String>> {
        let mut ret: HashMap<String, String> = HashMap::default();
        for (regex, env) in self.matches.iter() {
            if regex.is_match(&command) {
                ret.extend(env.clone());
            }
        }
        Ok(ret)
    }

    pub async fn collect_hydrate(&self, command: String) -> Result<HashMap<String, String>> {
        hydrate(self.collect(command)?).await
    }

    pub fn collect_keys(&self, command: String) -> Result<Vec<String>> {
        Ok(self.collect(command)?.keys().cloned().collect())
    }
}