use std::{ collections::HashMap, ffi::OsStr, fs::{self}, path::PathBuf, }; use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; pub(crate) const MAIN_CONFIG_NAME: &str = "rollup.toml"; pub(crate) const DROP_IN_NAME: &str = ".rollup.owner"; #[derive(Serialize, Deserialize, Debug)] pub(crate) struct MainConfig { pub(crate) owners: Vec, pub(crate) drop_ins: HashMap, } impl MainConfig { pub(crate) fn get_owner(&self, owner: &str) -> Result<&Owner> { if let Some(own) = self.owners.iter().find(|o| o.name == owner) { Ok(own) } else { bail!("Failed to find owner with name: `{}`", owner) } } } #[derive(Serialize, Deserialize, Debug)] pub(crate) struct Owner { pub(crate) name: String, pub(crate) applies_to: PathBuf, pub(crate) last_applied_tag: String, } #[derive(Serialize, Deserialize, Debug)] pub(crate) struct DropInConfig { pub(crate) owners: Vec, } pub(crate) fn load(root: &PathBuf) -> Result { let mut main: MainConfig = { let path = root.join(MAIN_CONFIG_NAME); let content = fs::read_to_string(path)?; toml::from_str(&content)? }; let drop_ins = collect_drop_ins(root)?; for (path, config) in drop_ins { assert!(main.drop_ins.insert(path, config).is_none()); } Ok(main) } fn collect_drop_ins(path: &PathBuf) -> Result> { let mut output = vec![]; for entry in fs::read_dir(path)? { let entry = entry?; let ft = entry.file_type()?; if ft.is_dir() { output.extend(collect_drop_ins(&entry.path())?); } else if ft.is_file() && entry.file_name() == OsStr::new(DROP_IN_NAME) { let drop_in: toml::Value = { let content = fs::read_to_string(entry.path())?; toml::from_str(&content)? }; let drop_in_config = DropInConfig { owners: { let Some(owner) = drop_in.get("owner") else { bail!("Drop in config without `owner` field") }; if owner.is_str() { vec![owner.as_str().expect("We checked").to_owned()] } else if owner.is_array() { let mut output = vec![]; for v in owner.as_array().expect("We checked") { if let Some(v) = v.as_str() { output.push(v.to_owned()) } else { bail!("Owner value in arry not string: {}", v); } } output } else { bail!("Owner value in drop in config not string or array") } }, }; output.push(( entry .path() .parent() .expect("All drop-ins have a parent") .to_owned(), drop_in_config, )); } } Ok(output) }