518 lines
19 KiB
Rust
518 lines
19 KiB
Rust
#![allow(clippy::option_if_let_else)]
|
|
|
|
use std::convert::identity;
|
|
|
|
use convert_case::{Case, Casing};
|
|
use darling::{
|
|
ast::{self, Data},
|
|
util::{Flag, Override, PathList},
|
|
Error, FromDeriveInput, FromField, FromMeta,
|
|
};
|
|
use proc_macro2::{Span, TokenStream};
|
|
use quote::{format_ident, quote};
|
|
use syn::{parse_quote, spanned::Spanned};
|
|
|
|
#[derive(Debug, FromField)]
|
|
#[darling(attributes(config))]
|
|
#[darling(forward_attrs(doc))]
|
|
pub struct ConfigField {
|
|
ident: Option<syn::Ident>,
|
|
vis: syn::Visibility,
|
|
ty: syn::Type,
|
|
attrs: Vec<syn::Attribute>,
|
|
min: Option<Override<syn::Expr>>,
|
|
max: Option<Override<syn::Expr>>,
|
|
default: Option<Override<syn::Expr>>,
|
|
typ: Option<syn::Type>,
|
|
get: Option<syn::Path>,
|
|
set: Option<syn::Path>,
|
|
no_serde: Flag,
|
|
#[darling(skip)]
|
|
description: Option<syn::Expr>,
|
|
#[darling(skip)]
|
|
parent_ident: Option<syn::Ident>,
|
|
}
|
|
|
|
impl ConfigField {
|
|
pub(crate) fn needs_newtype(&self) -> bool {
|
|
self.helper_keys().len() > 1 || self.typ.is_some()
|
|
}
|
|
|
|
pub(crate) fn helper(&self, attrs: &[syn::Attribute]) -> (Option<TokenStream>, syn::Type) {
|
|
if self.needs_newtype() {
|
|
let default = self.helper_default();
|
|
let derives = attrs
|
|
.iter()
|
|
.find(|attr| attr.path().is_ident("derive"))
|
|
.map(|attr| {
|
|
let mut derives = (*PathList::from_meta(&attr.meta).unwrap()).clone();
|
|
derives.retain(|derive| {
|
|
derive.segments.last().map(|s| {
|
|
let derive = s.ident.to_string();
|
|
derive.contains("Tree")
|
|
|| derive.contains("Serialize")
|
|
|| derive.contains("Deserialize")
|
|
|| (default.is_some() && derive.contains("Default"))
|
|
}) == Some(false)
|
|
});
|
|
quote! {#[derive(#(#derives,)*)]}
|
|
});
|
|
let new_type_ident = self.helper_ident();
|
|
let vis = &self.vis;
|
|
let ty = &self.ty;
|
|
|
|
let new = self.helper_new();
|
|
let serde = self.helper_serde();
|
|
let tree = self.helper_tree();
|
|
|
|
(
|
|
Some(quote! {
|
|
#derives
|
|
#vis struct #new_type_ident(#ty);
|
|
|
|
#new
|
|
#default
|
|
#serde
|
|
#tree
|
|
}),
|
|
parse_quote!(#new_type_ident),
|
|
)
|
|
} else {
|
|
(None, self.ty.clone())
|
|
}
|
|
}
|
|
|
|
pub(crate) fn helper_ident(&self) -> syn::Ident {
|
|
format_ident!(
|
|
"__{}{}",
|
|
self.parent_ident
|
|
.as_ref()
|
|
.unwrap_or(&syn::Ident::new("Struct", Span::mixed_site())),
|
|
self.ident
|
|
.as_ref()
|
|
.map_or("Field".to_owned(), |v| v.to_string().to_case(Case::Pascal))
|
|
)
|
|
}
|
|
|
|
pub(crate) fn has_custom_limits(&self) -> bool {
|
|
self.typ.is_none()
|
|
&& !((self.min.is_none() || self.min == Some(Override::Inherit))
|
|
&& (self.max.is_none() || self.max == Some(Override::Inherit)))
|
|
}
|
|
|
|
pub(crate) fn helper_new(&self) -> TokenStream {
|
|
let ident = self.helper_ident();
|
|
let ty = &self.ty;
|
|
if self.has_custom_limits() {
|
|
let min = self.min.as_ref().and_then(|v| v.as_ref().explicit());
|
|
let max = self.max.as_ref().and_then(|v| v.as_ref().explicit());
|
|
quote! {
|
|
impl #ident {
|
|
pub fn new(value: #ty) -> Option<Self> {
|
|
if (#min..=#max).contains(&value) {
|
|
Some(Self(value))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub const unsafe fn new_unchecked(value: #ty) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl ::core::ops::Deref for #ident {
|
|
type Target = #ty;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
quote! {
|
|
impl #ident {
|
|
pub const fn new(value: #ty) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl ::core::ops::Deref for #ident {
|
|
type Target = #ty;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl ::core::ops::DerefMut for #ident {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn helper_default(&self) -> Option<TokenStream> {
|
|
if self.typ.is_some() {
|
|
return None;
|
|
}
|
|
self.default.as_ref().map(|default| {
|
|
let ident = self.helper_ident();
|
|
let default_default = parse_quote!(::core::default::Default::default());
|
|
let default = default.as_ref().unwrap_or(&default_default);
|
|
quote! {
|
|
impl ::core::default::Default for #ident {
|
|
fn default() -> Self {
|
|
Self(#default)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn helper_serde(&self) -> Option<TokenStream> {
|
|
if self.no_serde.is_present() {
|
|
return None;
|
|
}
|
|
let ident = self.helper_ident();
|
|
let conversion = if self.has_custom_limits() {
|
|
quote! {
|
|
Self::new(value).ok_or_else(|| {
|
|
<D::Error as ::serde::de::Error>::custom("checking value bounds")
|
|
})
|
|
}
|
|
} else {
|
|
quote! {
|
|
Ok(Self::new(value))
|
|
}
|
|
};
|
|
let ty = &self.ty;
|
|
Some(quote! {
|
|
impl ::serde::Serialize for #ident {
|
|
fn serialize<S>(&self, serializer: S) -> ::core::result::Result::<S::Ok, S::Error>
|
|
where
|
|
S: ::serde::Serializer,
|
|
{
|
|
self.0.serialize(serializer)
|
|
}
|
|
}
|
|
|
|
impl<'de> ::serde::Deserialize<'de> for #ident {
|
|
fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
|
|
where
|
|
D: ::serde::Deserializer<'de>,
|
|
{
|
|
type T = #ty;
|
|
let value = T::deserialize(deserializer)?;
|
|
#conversion
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn helper_tree(&self) -> TokenStream {
|
|
let mut tokens = self.helper_tree_key();
|
|
tokens.extend(self.helper_tree_serialize());
|
|
tokens.extend(self.helper_tree_deserialize());
|
|
tokens.extend(self.helper_tree_any());
|
|
tokens
|
|
}
|
|
|
|
pub(crate) fn helper_keys(&self) -> Vec<(&'static str, syn::Expr)> {
|
|
macro_rules! field_to_key {
|
|
($field: ident, $default: expr) => {
|
|
self.$field.as_ref().map(|value| {
|
|
let ty = self.typ.as_ref().unwrap_or(&self.ty);
|
|
(stringify!($field),
|
|
value.clone().explicit().map(|val| {
|
|
parse_quote!({
|
|
fn type_ascribe(val: #ty) -> #ty {val}
|
|
type_ascribe(#val)
|
|
})
|
|
}).unwrap_or_else(|| {
|
|
parse_quote!(#ty::$default)
|
|
}))
|
|
})
|
|
};
|
|
($field: ident) => {
|
|
self.$field.as_ref().map(|value| {
|
|
(stringify!($field), value.clone())
|
|
})
|
|
}
|
|
}
|
|
[
|
|
Some(("value", parse_quote!(self))),
|
|
field_to_key!(default, default()),
|
|
field_to_key!(min, MIN),
|
|
field_to_key!(max, MAX),
|
|
field_to_key!(description),
|
|
]
|
|
.into_iter()
|
|
.flatten()
|
|
.collect::<Vec<_>>()
|
|
}
|
|
|
|
pub(crate) fn helper_tree_key(&self) -> TokenStream {
|
|
let ident = self.helper_ident();
|
|
let keys = self.helper_keys();
|
|
let num_keys = keys.len();
|
|
let keys = keys.iter().map(|(name, _)| name);
|
|
let max_length = keys.clone().map(|v| v.len()).max();
|
|
quote! {
|
|
impl ::miniconf::KeyLookup for #ident {
|
|
const LEN: usize = #num_keys;
|
|
const NAMES: &'static [&'static str] = &[#(#keys,)*];
|
|
|
|
fn name_to_index(value: &str) -> Option<usize> {
|
|
Self::NAMES.iter().position(|name| *name == value)
|
|
}
|
|
}
|
|
|
|
impl ::miniconf::TreeKey<1> for #ident {
|
|
fn metadata() -> ::miniconf::Metadata {
|
|
let mut metadata = ::miniconf::Metadata::default();
|
|
metadata.max_depth = 1;
|
|
metadata.count = #num_keys;
|
|
metadata.max_length = #max_length;
|
|
metadata
|
|
}
|
|
|
|
fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> ::core::result::Result<usize, ::miniconf::Error<E>>
|
|
where
|
|
K: ::miniconf::Keys,
|
|
// Writing this to return an iterator instead of using a callback
|
|
// would have worse performance (O(n^2) instead of O(n) for matching)
|
|
F: FnMut(usize, Option<&'static str>, usize) -> ::core::result::Result<(), E>,
|
|
{
|
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else { return ::core::result::Result::Ok(0) };
|
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
let name = <Self as ::miniconf::KeyLookup>::NAMES
|
|
.get(index)
|
|
.ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
func(index, Some(name), #num_keys).map_err(|err| ::miniconf::Error::Inner(1, err))?;
|
|
::core::result::Result::Ok(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn helper_tree_serialize(&self) -> TokenStream {
|
|
let matches = self
|
|
.helper_keys()
|
|
.iter()
|
|
.enumerate()
|
|
.skip(1)
|
|
.map(|(i, (_, expr))| {
|
|
quote! {
|
|
#i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let ident = self.helper_ident();
|
|
let get = self
|
|
.get
|
|
.as_ref()
|
|
.map_or_else(|| quote! {self}, |getter| quote! {#getter(&self.0)});
|
|
quote! {
|
|
impl ::miniconf::TreeSerialize<1> for #ident {
|
|
fn serialize_by_key<K, S>(
|
|
&self,
|
|
mut keys: K,
|
|
ser: S,
|
|
) -> ::core::result::Result<usize, ::miniconf::Error<S::Error>>
|
|
where
|
|
K: ::miniconf::Keys,
|
|
S: ::serde::Serializer,
|
|
{
|
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
return ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)).map(|_| 0);
|
|
};
|
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(0))?;
|
|
if !keys.finalize() {
|
|
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(0).into());
|
|
}
|
|
match index {
|
|
0 => ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
|
#(#matches)*
|
|
_ => unreachable!(),
|
|
}?;
|
|
Ok(0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn helper_tree_deserialize(&self) -> TokenStream {
|
|
let ident = self.helper_ident();
|
|
let num_keys = self.helper_keys().len();
|
|
let set = self.set.as_ref().map_or_else(
|
|
|| quote!((|val| {*self = val; core::result::Result::<_, ::miniconf::Traversal>::Ok(())})),
|
|
|set| quote!((|val| #set(&mut self.0, val).map_err(|e| ::miniconf::Traversal::Invalid(0, e)))),
|
|
);
|
|
let typ = self
|
|
.typ
|
|
.as_ref()
|
|
.map_or_else(|| quote!(Self), |typ| quote!(#typ));
|
|
let match_range = if num_keys > 1 {
|
|
Some(
|
|
quote!(1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(0, "Cannot write limits").into()),),
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
quote! {
|
|
impl<'de> ::miniconf::TreeDeserialize<'de, 1> for #ident {
|
|
fn deserialize_by_key<K, D>(
|
|
&mut self,
|
|
mut keys: K,
|
|
de: D,
|
|
) -> ::core::result::Result<usize, ::miniconf::Error<D::Error>>
|
|
where
|
|
K: ::miniconf::Keys,
|
|
D: ::serde::Deserializer<'de>,
|
|
{
|
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
#set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
|
|
return ::core::result::Result::Ok(0);
|
|
};
|
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
if !keys.finalize() {
|
|
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1).into());
|
|
}
|
|
match index {
|
|
0 => {
|
|
#set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
|
|
Ok(0)
|
|
}
|
|
#match_range
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn helper_tree_any(&self) -> TokenStream {
|
|
let ident = self.helper_ident();
|
|
let num_keys = self.helper_keys().len();
|
|
let ref_mut = if self.has_custom_limits() {
|
|
quote! {::core::result::Result::Err(::miniconf::Traversal::Access(0, "field has custom limits"))}
|
|
} else {
|
|
quote!(::core::result::Result::Ok(&mut *self))
|
|
};
|
|
let match_range = if num_keys > 1 {
|
|
Some(
|
|
quote!(1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),),
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
quote! {
|
|
impl ::miniconf::TreeAny<1> for #ident {
|
|
fn ref_any_by_key<K>(&self, mut keys: K) -> ::core::result::Result<&dyn ::core::any::Any, ::miniconf::Traversal>
|
|
where
|
|
K: ::miniconf::Keys,
|
|
{
|
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
return ::core::result::Result::Ok(&*self);
|
|
};
|
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
if !keys.finalize() {
|
|
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1));
|
|
}
|
|
match index {
|
|
0 => ::core::result::Result::Ok(&*self),
|
|
#match_range
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn mut_any_by_key<K>(&mut self, mut keys: K) -> ::core::result::Result<&mut dyn ::core::any::Any, ::miniconf::Traversal>
|
|
where
|
|
K: ::miniconf::Keys,
|
|
{
|
|
let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
|
|
return #ref_mut;
|
|
};
|
|
let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
|
|
if !keys.finalize() {
|
|
return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1));
|
|
}
|
|
match index {
|
|
0 => #ref_mut,
|
|
#match_range
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, FromDeriveInput)]
|
|
#[darling(forward_attrs(derive))]
|
|
#[darling(supports(struct_any))]
|
|
pub struct Config {
|
|
ident: syn::Ident,
|
|
// generics: syn::Generics,
|
|
// vis: syn::Visibility,
|
|
data: ast::Data<(), ConfigField>,
|
|
pub attrs: Vec<syn::Attribute>,
|
|
}
|
|
|
|
impl Config {
|
|
pub(crate) fn parse(input: &syn::DeriveInput) -> Result<Self, Error> {
|
|
let mut config = Self::from_derive_input(input)?;
|
|
let ident = config.ident.clone();
|
|
let fields = config.fields_mut();
|
|
for field in fields.iter_mut() {
|
|
field.description = field
|
|
.attrs
|
|
.iter()
|
|
.find(|attr| attr.path().is_ident("doc"))
|
|
.map(|attr| {
|
|
let description = syn::LitStr::from_meta(&attr.meta).unwrap().value();
|
|
let description = description.trim();
|
|
parse_quote!(#description)
|
|
});
|
|
field.parent_ident = Some(ident.clone());
|
|
let get_set_typ = [
|
|
field.typ.is_some(),
|
|
field.get.is_some(),
|
|
field.set.is_some(),
|
|
];
|
|
if get_set_typ.iter().copied().any(identity) && !get_set_typ.into_iter().all(identity) {
|
|
return Err(
|
|
Error::custom("need all or none of 'typ', 'get', 'set'").with_span(
|
|
&field.typ.as_ref().map_or_else(
|
|
|| {
|
|
field.get.as_ref().map_or_else(
|
|
|| field.set.as_ref().unwrap().span(),
|
|
Spanned::span,
|
|
)
|
|
},
|
|
Spanned::span,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
Ok(config)
|
|
}
|
|
|
|
pub(crate) fn fields(&self) -> &Vec<ConfigField> {
|
|
let Data::Struct(fields) = &self.data else {
|
|
unreachable!()
|
|
};
|
|
&fields.fields
|
|
}
|
|
|
|
pub(crate) fn fields_mut(&mut self) -> &mut Vec<ConfigField> {
|
|
let Data::Struct(fields) = &mut self.data else {
|
|
unreachable!()
|
|
};
|
|
&mut fields.fields
|
|
}
|
|
}
|