use std::any::Any; use std::fmt::Debug; use std::marker::PhantomData; use std::ops::Deref; use std::sync::Arc; use anyhow::bail; use hashbrown::hash_map::Entry; use hashbrown::HashMap; use indexmap::IndexMap; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use any::ConfigData; use convert::ty_into_value; pub use convert::IntoTy; pub use definition::{init_config, init_language_server_config}; pub use toml::read_toml_config; use validator::StaticValidator; pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator}; pub use value::{from_value, to_value, Value}; mod any; mod convert; mod definition; pub mod env; mod macros; mod toml; mod validator; mod value; pub type Guard<'a, T> = MappedRwLockReadGuard<'a, T>; pub type Map = IndexMap, T, ahash::RandomState>; pub type String = Box; pub type List = Box<[T]>; #[cfg(test)] mod tests; #[derive(Debug)] pub struct OptionInfo { pub name: Arc, pub description: Box, pub validator: Box, pub into_value: fn(&ConfigData) -> Value, } #[derive(Debug)] pub struct OptionManager { vals: RwLock, ConfigData>>, parent: Option>, } impl OptionManager { pub fn get(&self, option: &str) -> Guard<'_, T> { Guard::map(self.get_data(option), ConfigData::get) } pub fn get_data(&self, option: &str) -> Guard<'_, ConfigData> { let mut current_scope = self; loop { let lock = current_scope.vals.read(); if let Ok(res) = RwLockReadGuard::try_map(lock, |options| options.get(option)) { return res; } let Some(new_scope) = current_scope.parent.as_deref() else{ unreachable!("option must be atleast defined in the global scope") }; current_scope = new_scope; } } pub fn get_deref(&self, option: &str) -> Guard<'_, T::Target> { Guard::map(self.get::(option), T::deref) } pub fn get_folded( &self, option: &str, init: R, mut fold: impl FnMut(&T, R) -> R, ) -> R { let mut res = init; let mut current_scope = self; loop { let options = current_scope.vals.read(); if let Some(option) = options.get(option).map(|val| val.get()) { res = fold(option, res); } let Some(new_scope) = current_scope.parent.as_deref() else{ break }; current_scope = new_scope; } res } pub fn get_value( &self, option: impl Into>, registry: &OptionRegistry, ) -> anyhow::Result { let option: Arc = option.into(); let Some(opt) = registry.get(&option) else { bail!("unknown option {option:?}") }; let data = self.get_data(&option); let val = (opt.into_value)(&data); Ok(val) } pub fn create_scope(self: &Arc) -> OptionManager { OptionManager { vals: RwLock::default(), parent: Some(self.clone()), } } pub fn set_parent_scope(&mut self, parent: Arc) { self.parent = Some(parent) } pub fn set_unchecked(&self, option: Arc, val: ConfigData) { self.vals.write().insert(option, val); } pub fn append( &self, option: impl Into>, val: impl Into, registry: &OptionRegistry, max_depth: usize, ) -> anyhow::Result<()> { let val = val.into(); let option: Arc = option.into(); let Some(opt) = registry.get(&option) else { bail!("unknown option {option:?}") }; let old_data = self.get_data(&option); let mut old = (opt.into_value)(&old_data); old.append(val, max_depth); let val = opt.validator.validate(old)?; self.set_unchecked(option, val); Ok(()) } /// Sets the value of a config option. Returns an error if this config /// option doesn't exist or the provided value is not valid. pub fn set( &self, option: impl Into>, val: impl Into, registry: &OptionRegistry, ) -> anyhow::Result<()> { let option: Arc = option.into(); let val = val.into(); let Some(opt) = registry.get(&option) else { bail!("unknown option {option:?}") }; let val = opt.validator.validate(val)?; self.set_unchecked(option, val); Ok(()) } /// unsets an options so that its value will be read from /// the parent scope instead pub fn unset(&self, option: &str) { self.vals.write().remove(option); } } #[derive(Debug)] pub struct OptionRegistry { options: HashMap, OptionInfo>, defaults: Arc, } impl OptionRegistry { pub fn new() -> Self { Self { options: HashMap::with_capacity(1024), defaults: Arc::new(OptionManager { vals: RwLock::new(HashMap::with_capacity(1024)), parent: None, }), } } pub fn register(&mut self, name: &str, description: &str, default: T) { self.register_with_validator( name, description, default, StaticValidator:: { ty: PhantomData }, ); } pub fn register_with_validator( &mut self, name: &str, description: &str, default: T, validator: impl Validator, ) { let mut name: Arc = name.into(); // convert from snake case to kebab case in place without an additional // allocation this is save since we only replace ascii with ascii in // place std really ougth to have a function for this :/ // TODO: move to stdx as extension trait for byte in unsafe { Arc::get_mut(&mut name).unwrap().as_bytes_mut() } { if *byte == b'-' { *byte = b'_'; } } let default = default.into_ty(); match self.options.entry(name.clone()) { Entry::Vacant(e) => { // make sure the validator is correct if cfg!(debug_assertions) { validator.validate(T::Ty::to_value(&default)).unwrap(); } let opt = OptionInfo { name: name.clone(), description: description.into(), validator: Box::new(validator), into_value: ty_into_value::, }; e.insert(opt); } Entry::Occupied(ent) => { ent.get() .validator .validate(T::Ty::to_value(&default)) .unwrap(); } } self.defaults.set_unchecked(name, ConfigData::new(default)); } pub fn global_scope(&self) -> Arc { self.defaults.clone() } pub fn get(&self, name: &str) -> Option<&OptionInfo> { self.options.get(name) } } impl Default for OptionRegistry { fn default() -> Self { Self::new() } }