use core::fmt;
use std::{any::TypeId, cmp::Ordering, mem, ops, sync::Arc};
use zng_app_context::app_local;
use zng_state_map::{OwnedStateMap, StateId, StateMapMut, StateMapRef, StateValue};
use zng_txt::Txt;
use zng_var::{impl_from_and_into_var, var, AnyVar, AnyVarHookArgs, AnyVarValue, BoxedAnyVar, BoxedVar, IntoVar, LocalVar, Var};
use crate::{Config, ConfigKey, ConfigValue, FallbackConfigReset, CONFIG};
pub struct SETTINGS;
impl SETTINGS {
pub fn register(&self, f: impl Fn(&mut SettingsBuilder) + Send + Sync + 'static) {
SETTINGS_SV.write().sources.push(Box::new(f))
}
pub fn register_categories(&self, f: impl Fn(&mut CategoriesBuilder) + Send + Sync + 'static) {
SETTINGS_SV.write().sources_cat.push(Box::new(f))
}
pub fn get(&self, mut filter: impl FnMut(&ConfigKey, &CategoryId) -> bool, sort: bool) -> Vec<(Category, Vec<Setting>)> {
self.get_impl(&mut filter, sort)
}
fn get_impl(&self, filter: &mut dyn FnMut(&ConfigKey, &CategoryId) -> bool, sort: bool) -> Vec<(Category, Vec<Setting>)> {
let sv = SETTINGS_SV.read();
let mut settings = SettingsBuilder { settings: vec![], filter };
for source in sv.sources.iter() {
source(&mut settings);
}
let settings = settings.settings;
let mut categories = CategoriesBuilder {
categories: vec![],
filter: &mut |cat| settings.iter().any(|s| &s.category == cat),
};
for source in sv.sources_cat.iter() {
source(&mut categories);
}
let categories = categories.categories;
let mut result: Vec<_> = categories.into_iter().map(|c| (c, vec![])).collect();
for s in settings {
if let Some(i) = result.iter().position(|(c, _)| c.id == s.category) {
result[i].1.push(s);
} else {
tracing::warn!("missing category metadata for {}", s.category);
result.push((
Category {
id: s.category.clone(),
order: u16::MAX,
name: LocalVar(s.category.0.clone()).boxed(),
meta: Arc::new(OwnedStateMap::new()),
},
vec![s],
));
}
}
if sort {
self.sort(&mut result);
}
result
}
pub fn any(&self, mut filter: impl FnMut(&ConfigKey, &CategoryId) -> bool) -> bool {
self.any_impl(&mut filter)
}
fn any_impl(&self, filter: &mut dyn FnMut(&ConfigKey, &CategoryId) -> bool) -> bool {
let sv = SETTINGS_SV.read();
let mut any = false;
for source in sv.sources.iter() {
source(&mut SettingsBuilder {
settings: vec![],
filter: &mut |k, i| {
if filter(k, i) {
any = true;
}
false
},
});
if any {
break;
}
}
any
}
pub fn count(&self, mut filter: impl FnMut(&ConfigKey, &CategoryId) -> bool) -> usize {
self.count_impl(&mut filter)
}
fn count_impl(&self, filter: &mut dyn FnMut(&ConfigKey, &CategoryId) -> bool) -> usize {
let sv = SETTINGS_SV.read();
let mut count = 0;
for source in sv.sources.iter() {
source(&mut SettingsBuilder {
settings: vec![],
filter: &mut |k, i| {
if filter(k, i) {
count += 1;
}
false
},
});
}
count
}
pub fn categories(&self, mut filter: impl FnMut(&CategoryId) -> bool, include_empty: bool, sort: bool) -> Vec<Category> {
self.categories_impl(&mut filter, include_empty, sort)
}
fn categories_impl(&self, filter: &mut dyn FnMut(&CategoryId) -> bool, include_empty: bool, sort: bool) -> Vec<Category> {
let sv = SETTINGS_SV.read();
let mut categories = CategoriesBuilder {
categories: vec![],
filter,
};
for source in sv.sources_cat.iter() {
source(&mut categories);
}
let mut result = categories.categories;
if !include_empty {
let mut non_empty = vec![];
for source in sv.sources.iter() {
source(&mut SettingsBuilder {
settings: vec![],
filter: &mut |_, cat| {
if !non_empty.contains(cat) {
non_empty.push(cat.clone());
}
false
},
});
}
result.retain(|c| {
if let Some(i) = non_empty.iter().position(|id| &c.id == id) {
non_empty.swap_remove(i);
true
} else {
false
}
});
for missing in non_empty {
tracing::warn!("missing category metadata for {}", missing);
result.push(Category::unknown(missing));
}
}
if sort {
self.sort_categories(&mut result)
}
result
}
pub fn sort_settings(&self, settings: &mut [Setting]) {
settings.sort_by(|a, b| {
let c = a.order.cmp(&b.order);
if matches!(c, Ordering::Equal) {
return a.name.with(|a| b.name.with(|b| a.cmp(b)));
}
c
});
}
pub fn sort_categories(&self, categories: &mut [Category]) {
categories.sort_by(|a, b| {
let c = a.order.cmp(&b.order);
if matches!(c, Ordering::Equal) {
return a.name.with(|a| b.name.with(|b| a.cmp(b)));
}
c
});
}
pub fn sort(&self, settings: &mut [(Category, Vec<Setting>)]) {
settings.sort_by(|a, b| {
let c = a.0.order.cmp(&b.0.order);
if matches!(c, Ordering::Equal) {
return a.0.name.with(|a| b.0.name.with(|b| a.cmp(b)));
}
c
});
for (_, s) in settings {
self.sort_settings(s);
}
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash, Default, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct CategoryId(pub Txt);
impl_from_and_into_var! {
fn from(id: Txt) -> CategoryId {
CategoryId(id)
}
fn from(id: String) -> CategoryId {
CategoryId(id.into())
}
fn from(id: &'static str) -> CategoryId {
CategoryId(id.into())
}
}
impl ops::Deref for CategoryId {
type Target = Txt;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for CategoryId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Clone)]
pub struct Category {
id: CategoryId,
order: u16,
name: BoxedVar<Txt>,
meta: Arc<OwnedStateMap<Category>>,
}
impl Category {
pub fn id(&self) -> &CategoryId {
&self.id
}
pub fn order(&self) -> u16 {
self.order
}
pub fn name(&self) -> &BoxedVar<Txt> {
&self.name
}
pub fn meta(&self) -> StateMapRef<Category> {
self.meta.borrow()
}
pub fn unknown(missing: CategoryId) -> Self {
Self {
id: missing.clone(),
order: u16::MAX,
name: LocalVar(missing.0).boxed(),
meta: Arc::default(),
}
}
}
impl PartialEq for Category {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Category {}
impl fmt::Debug for Category {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Category").field("id", &self.id).finish_non_exhaustive()
}
}
#[cfg(test)]
fn _setting_in_var(s: Setting) {
let _x = LocalVar(s).get();
}
pub struct Setting {
key: ConfigKey,
order: u16,
name: BoxedVar<Txt>,
description: BoxedVar<Txt>,
category: CategoryId,
meta: Arc<OwnedStateMap<Setting>>,
value: BoxedAnyVar,
value_type: TypeId,
reset: Arc<dyn SettingReset>,
}
impl Clone for Setting {
fn clone(&self) -> Self {
Self {
key: self.key.clone(),
order: self.order,
name: self.name.clone(),
description: self.description.clone(),
category: self.category.clone(),
meta: self.meta.clone(),
value: self.value.clone(),
value_type: self.value_type,
reset: self.reset.clone(),
}
}
}
impl Setting {
pub fn key(&self) -> &ConfigKey {
&self.key
}
pub fn order(&self) -> u16 {
self.order
}
pub fn name(&self) -> &BoxedVar<Txt> {
&self.name
}
pub fn description(&self) -> &BoxedVar<Txt> {
&self.description
}
pub fn category(&self) -> &CategoryId {
&self.category
}
pub fn meta(&self) -> StateMapRef<Setting> {
self.meta.borrow()
}
pub fn value_is_set(&self) -> bool {
self.value_type != TypeId::of::<SettingValueNotSet>()
}
pub fn value(&self) -> &BoxedAnyVar {
&self.value
}
pub fn value_type(&self) -> TypeId {
self.value_type
}
pub fn value_downcast<T: ConfigValue>(&self) -> Option<BoxedVar<T>> {
if self.value_type == std::any::TypeId::of::<T>() {
let v = self.value.clone().double_boxed_any().downcast::<BoxedVar<T>>().unwrap();
Some(*v)
} else {
None
}
}
pub fn can_reset(&self) -> BoxedVar<bool> {
self.reset.can_reset(&self.key, &self.value)
}
pub fn reset(&self) {
self.reset.reset(&self.key, &self.value);
}
pub fn search_index(&self, search: &str) -> Option<usize> {
if let Some(key) = search.strip_prefix("@key:") {
return if self.key.contains(key) {
Some(self.key.len() - search.len())
} else {
None
};
}
let r = self.name.with(|s| {
let s = s.to_lowercase();
if s.contains(search) {
Some(s.len() - search.len())
} else {
None
}
});
if r.is_some() {
return r;
}
self.description.with(|s| {
let s = s.to_lowercase();
if s.contains(search) {
Some(s.len() - search.len() + usize::MAX / 2)
} else {
None
}
})
}
}
impl PartialEq for Setting {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl Eq for Setting {}
impl fmt::Debug for Setting {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Setting").field("key", &self.key).finish_non_exhaustive()
}
}
app_local! {
static SETTINGS_SV: SettingsService = SettingsService {
sources: vec![],
sources_cat: vec![],
};
}
struct SettingsService {
sources: Vec<Box<dyn Fn(&mut SettingsBuilder) + Send + Sync + 'static>>,
sources_cat: Vec<Box<dyn Fn(&mut CategoriesBuilder) + Send + Sync + 'static>>,
}
pub struct SettingsBuilder<'a> {
settings: Vec<Setting>,
filter: &'a mut dyn FnMut(&ConfigKey, &CategoryId) -> bool,
}
impl<'c> SettingsBuilder<'c> {
pub fn entry(
&mut self,
config_key: impl Into<ConfigKey>,
category_id: impl Into<CategoryId>,
builder: impl for<'a, 'b> FnOnce(&'a mut SettingBuilder<'b>) -> &'a mut SettingBuilder<'b>,
) -> &mut Self {
if let Some(mut e) = self.entry_impl(config_key.into(), category_id.into()) {
builder(&mut e);
}
self
}
fn entry_impl(&mut self, config_key: ConfigKey, category_id: CategoryId) -> Option<SettingBuilder> {
if (self.filter)(&config_key, &category_id) {
if let Some(i) = self.settings.iter().position(|s| s.key == config_key) {
let existing = self.settings.swap_remove(i);
Some(SettingBuilder {
settings: &mut self.settings,
config_key,
category_id,
order: existing.order,
name: Some(existing.name),
description: Some(existing.description),
meta: Arc::try_unwrap(existing.meta).unwrap(),
value: None,
reset: None,
})
} else {
Some(SettingBuilder {
settings: &mut self.settings,
config_key,
category_id,
order: u16::MAX,
name: None,
description: None,
meta: OwnedStateMap::new(),
value: None,
reset: None,
})
}
} else {
None
}
}
}
pub struct SettingBuilder<'a> {
settings: &'a mut Vec<Setting>,
config_key: ConfigKey,
category_id: CategoryId,
order: u16,
name: Option<BoxedVar<Txt>>,
description: Option<BoxedVar<Txt>>,
meta: OwnedStateMap<Setting>,
value: Option<(BoxedAnyVar, TypeId)>,
reset: Option<Arc<dyn SettingReset>>,
}
impl<'a> SettingBuilder<'a> {
pub fn key(&self) -> &ConfigKey {
&self.config_key
}
pub fn category(&self) -> &CategoryId {
&self.category_id
}
pub fn order(&mut self, order: u16) -> &mut Self {
self.order = order;
self
}
pub fn name(&mut self, name: impl IntoVar<Txt>) -> &mut Self {
self.name = Some(name.into_var().read_only().boxed());
self
}
pub fn description(&mut self, description: impl IntoVar<Txt>) -> &mut Self {
self.description = Some(description.into_var().read_only().boxed());
self
}
pub fn set<T: StateValue>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>) -> &mut Self {
self.meta.borrow_mut().set(id, value);
self
}
pub fn flag(&mut self, id: impl Into<StateId<()>>) -> &mut Self {
self.meta.borrow_mut().flag(id);
self
}
pub fn meta(&mut self) -> StateMapMut<Setting> {
self.meta.borrow_mut()
}
pub fn value<T: ConfigValue>(&mut self, default: T) -> &mut Self {
self.cfg_value(&mut CONFIG, default)
}
pub fn cfg_value<T: ConfigValue>(&mut self, cfg: &mut impl Config, default: T) -> &mut Self {
let value = cfg.get(self.config_key.clone(), default, false);
self.value = Some((value.boxed_any(), TypeId::of::<T>()));
self
}
pub fn reset(&mut self, resetter: Box<dyn FallbackConfigReset>, strip_key_prefix: impl Into<Txt>) -> &mut Self {
self.reset = Some(Arc::new(FallbackReset {
resetter,
strip_key_prefix: strip_key_prefix.into(),
}));
self
}
pub fn default<T: ConfigValue>(&mut self, default: T) -> &mut Self {
let reset: Box<dyn AnyVarValue> = Box::new(default);
self.reset = Some(Arc::new(reset));
self
}
}
impl<'a> Drop for SettingBuilder<'a> {
fn drop(&mut self) {
let (cfg, cfg_type) = self
.value
.take()
.unwrap_or_else(|| (LocalVar(SettingValueNotSet).boxed_any(), TypeId::of::<SettingValueNotSet>()));
self.settings.push(Setting {
key: mem::take(&mut self.config_key),
order: self.order,
name: self.name.take().unwrap_or_else(|| var(Txt::from_static("")).boxed()),
description: self.description.take().unwrap_or_else(|| var(Txt::from_static("")).boxed()),
category: mem::take(&mut self.category_id),
meta: Arc::new(mem::take(&mut self.meta)),
value: cfg,
value_type: cfg_type,
reset: self.reset.take().unwrap_or_else(|| Arc::new(SettingValueNotSet)),
})
}
}
#[derive(Clone, PartialEq, Debug)]
struct SettingValueNotSet;
pub struct CategoriesBuilder<'f> {
categories: Vec<Category>,
filter: &'f mut dyn FnMut(&CategoryId) -> bool,
}
impl<'f> CategoriesBuilder<'f> {
pub fn entry(
&mut self,
category_id: impl Into<CategoryId>,
builder: impl for<'a, 'b> FnOnce(&'a mut CategoryBuilder<'b>) -> &'a mut CategoryBuilder<'b>,
) -> &mut Self {
if let Some(mut e) = self.entry_impl(category_id.into()) {
builder(&mut e);
}
self
}
fn entry_impl(&mut self, category_id: CategoryId) -> Option<CategoryBuilder> {
if (self.filter)(&category_id) {
if let Some(i) = self.categories.iter().position(|s| s.id == category_id) {
let existing = self.categories.swap_remove(i);
Some(CategoryBuilder {
categories: &mut self.categories,
category_id,
order: existing.order,
name: Some(existing.name),
meta: Arc::try_unwrap(existing.meta).unwrap(),
})
} else {
Some(CategoryBuilder {
categories: &mut self.categories,
category_id,
order: u16::MAX,
name: None,
meta: OwnedStateMap::new(),
})
}
} else {
None
}
}
}
pub struct CategoryBuilder<'a> {
categories: &'a mut Vec<Category>,
category_id: CategoryId,
order: u16,
name: Option<BoxedVar<Txt>>,
meta: OwnedStateMap<Category>,
}
impl<'a> CategoryBuilder<'a> {
pub fn id(&self) -> &CategoryId {
&self.category_id
}
pub fn order(&mut self, order: u16) -> &mut Self {
self.order = order;
self
}
pub fn name(&mut self, name: impl IntoVar<Txt>) -> &mut Self {
self.name = Some(name.into_var().read_only().boxed());
self
}
pub fn set<T: StateValue>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>) -> &mut Self {
self.meta.borrow_mut().set(id, value);
self
}
pub fn flag(&mut self, id: impl Into<StateId<()>>) -> &mut Self {
self.meta.borrow_mut().flag(id);
self
}
pub fn meta(&mut self) -> StateMapMut<Category> {
self.meta.borrow_mut()
}
}
impl<'a> Drop for CategoryBuilder<'a> {
fn drop(&mut self) {
self.categories.push(Category {
id: mem::take(&mut self.category_id),
order: self.order,
name: self.name.take().unwrap_or_else(|| var(Txt::from_static("")).boxed()),
meta: Arc::new(mem::take(&mut self.meta)),
})
}
}
trait SettingReset: Send + Sync + 'static {
fn can_reset(&self, key: &ConfigKey, value: &BoxedAnyVar) -> BoxedVar<bool>;
fn reset(&self, key: &ConfigKey, value: &BoxedAnyVar);
}
struct FallbackReset {
resetter: Box<dyn FallbackConfigReset>,
strip_key_prefix: Txt,
}
impl SettingReset for FallbackReset {
fn can_reset(&self, key: &ConfigKey, _: &BoxedAnyVar) -> BoxedVar<bool> {
match key.strip_prefix(self.strip_key_prefix.as_str()) {
Some(k) => self.resetter.can_reset(ConfigKey::from_str(k)),
None => self.resetter.can_reset(key.clone()),
}
}
fn reset(&self, key: &ConfigKey, _: &BoxedAnyVar) {
match key.strip_prefix(self.strip_key_prefix.as_str()) {
Some(k) => self.resetter.reset(&ConfigKey::from_str(k)),
None => self.resetter.reset(key),
}
}
}
impl SettingReset for Box<dyn AnyVarValue> {
fn can_reset(&self, _: &ConfigKey, value: &BoxedAnyVar) -> BoxedVar<bool> {
let mut initial = false;
value.with_any(&mut |v| {
initial = v.eq_any(&**self);
});
let map = var(initial);
let map_in = map.clone();
let dft = self.clone_boxed();
value
.hook_any(Box::new(move |args: &AnyVarHookArgs| {
map_in.set(args.value().eq_any(&*dft));
true
}))
.perm();
map.clone().boxed()
}
fn reset(&self, _: &ConfigKey, value: &BoxedAnyVar) {
let _ = value.set_any(self.clone_boxed());
}
}
impl SettingReset for SettingValueNotSet {
fn can_reset(&self, _: &ConfigKey, _: &BoxedAnyVar) -> BoxedVar<bool> {
LocalVar(false).boxed()
}
fn reset(&self, _: &ConfigKey, _: &BoxedAnyVar) {}
}