Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-config/src/macros.rs')
| -rw-r--r-- | helix-config/src/macros.rs | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/helix-config/src/macros.rs b/helix-config/src/macros.rs new file mode 100644 index 00000000..69e9c75c --- /dev/null +++ b/helix-config/src/macros.rs @@ -0,0 +1,130 @@ +/// This macro allows specifiying a trait of related config +/// options with a struct like syntax. From that information +/// two things are generated: +/// +/// * A `init_config` function that registers the config options with the +/// `OptionRegistry` registry. +/// * A **trait** definition with an accessor for every config option that is +/// implemented for `OptionManager`. +/// +/// The accessors on the trait allow convenient statically typed access to +/// config fields. The accessors return `Guard<T>` (which allows derferecning to +/// &T). Any type that implements copy can be returned as a copy instead by +/// specifying `#[read = copy]`. Collections like `List<T>` and `String` are not +/// copy However, they usually implement deref (to &[T] and &str respectively). +/// Working with the dereferneced &str/&[T] is more convenient then &String and &List<T>. The +/// accessor will return these if `#[read = deref]` is specified. +/// +/// The doc comments will be retained for the accessors and also stored in the +/// option registrry for dispaly in the UI and documentation. +/// +/// The name of a config option can be changed with #[name = "<name>"], +/// otherwise the name of the field is used directly. The OptionRegistry +/// automatically converts all names to kebab-case so a name attribute is only +/// required if the name is supposed to be significantly altered. +/// +/// In some cases more complex validation may be necssary. In that case the +/// valiidtator can be provided with an exprission that implements the `Validator` +/// trait: `#[validator = create_validator()]`. +#[macro_export] +macro_rules! options { + ( + $(use $use: ident::*;)* + $($(#[$($meta: tt)*])* struct $ident: ident { + $( + $(#[doc = $option_desc: literal])* + $(#[name = $option_name: literal])? + $(#[validator = $option_validator: expr])? + $(#[read = $($extra: tt)*])? + $option: ident: $ty: ty = $default: expr + ),+$(,)? + })+ + ) => { + $(pub use $use::*;)* + $($(#[$($meta)*])* pub trait $ident { + $( + $(#[doc = $option_desc])* + fn $option(&self) -> $crate::options!(@ret_ty $($($extra)*)? $ty); + )+ + })+ + pub fn init_config(registry: &mut $crate::OptionRegistry) { + $($use::init_config(registry);)* + $($( + let name = $crate::options!(@name $option $($option_name)?); + let docs = concat!("" $(,$option_desc,)" "*); + $crate::options!(@register registry name docs $default, $ty $(,$option_validator)?); + )+)+ + } + $(impl $ident for $crate::OptionManager { + $( + $(#[doc = $option_desc])* + fn $option(&self) -> $crate::options!(@ret_ty $($($extra)*)? $ty) { + let name = $crate::options!(@name $option $($option_name)?); + $crate::options!(@get $($($extra)*)? self, $ty, name) + } + )+ + })+ + }; + (@register $registry: ident $name: ident $desc: ident $default: expr, $ty:ty) => {{ + use $crate::IntoTy; + let val: $ty = $default.into_ty(); + $registry.register($name, $desc, val); + }}; + (@register $registry: ident $name: ident $desc: ident $default: expr, $ty:ty, $validator: expr) => {{ + use $crate::IntoTy; + let val: $ty = $default.into_ty(); + $registry.register_with_validator($name, $desc, val, $validator); + }}; + (@name $ident: ident) => { + ::std::stringify!($ident) + }; + (@name $ident: ident $name: literal) => { + $name + }; + (@ret_ty copy $ty: ty) => { + $ty + }; + (@ret_ty map($fn: expr, $ret_ty: ty) $ty: ty) => { + $ret_ty + }; + (@ret_ty fold($init: expr, $fn: expr, $ret_ty: ty) $ty: ty) => { + $ret_ty + }; + (@ret_ty deref $ty: ty) => { + $crate::Guard<'_, <$ty as ::std::ops::Deref>::Target> + }; + (@ret_ty $ty: ty) => { + $crate::Guard<'_, $ty> + }; + (@get map($fn: expr, $ret_ty: ty) $config: ident, $ty: ty, $name: ident) => { + let val = $config.get::<$ty>($name); + $fn(val) + }; + (@get fold($init: expr, $fn: expr, $ret_ty: ty) $config: ident, $ty: ty, $name: ident) => { + $config.get_folded::<$ty, $ret_ty>($name, $init, $fn) + }; + (@get copy $config: ident, $ty: ty, $name: ident) => { + *$config.get::<$ty>($name) + }; + (@get deref $config: ident, $ty: ty, $name: ident) => { + $config.get_deref::<$ty>($name) + }; + (@get $config: ident, $ty: ty, $name: ident) => { + $config.get::<$ty>($name) + }; +} + +#[macro_export] +macro_rules! config_serde_adapter { + ($ty: ident) => { + impl $crate::Ty for $ty { + fn to_value(&self) -> $crate::Value { + $crate::to_value(self).unwrap() + } + fn from_value(val: $crate::Value) -> ::anyhow::Result<Self> { + let val = $crate::from_value(val)?; + Ok(val) + } + } + }; +} |