implement enum variant generation
This commit is contained in:
parent
9d3289c3d3
commit
a5bf501ebd
29
src/lib.rs
29
src/lib.rs
@ -2,12 +2,41 @@
|
||||
//! extra information about the field.
|
||||
|
||||
use darling::{util::PathList, FromMeta};
|
||||
use parser::Enum;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, parse_quote, DataStruct, DeriveInput};
|
||||
|
||||
mod parser;
|
||||
|
||||
/// Implements `miniconf::Tree` for the given enum.
|
||||
///
|
||||
/// This implementation of `miniconf::Tree` adds a value and a variants path.
|
||||
/// The value path serializes the enum.
|
||||
/// The variants path serializes all possible variants as an array.
|
||||
/// Optionally default and description paths can be generated.
|
||||
/// The description is generated from the docstring describing the enum.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use macroconf::ConfigEnum;
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
///
|
||||
/// /// Description
|
||||
/// #[derive(Default, ConfigEnum, Serialize, Deserialize)]
|
||||
/// enum Test {
|
||||
/// #[default]
|
||||
/// Variant1,
|
||||
/// Variant2,
|
||||
/// Variant3,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(ConfigEnum, attributes(default))]
|
||||
pub fn config_enum(item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as Enum);
|
||||
input.generate_tree().into()
|
||||
}
|
||||
|
||||
/// Creates structs for the values to extend them with extra metadata.
|
||||
///
|
||||
/// supported metadata is `min`, `max` and `default`. Doc comments are parsed as `description`
|
||||
|
255
src/parser.rs
255
src/parser.rs
@ -10,7 +10,7 @@ use darling::{
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_quote, spanned::Spanned};
|
||||
use syn::{braced, parse::Parse, parse_quote, spanned::Spanned, Token};
|
||||
|
||||
#[derive(Debug, FromField)]
|
||||
#[darling(attributes(config))]
|
||||
@ -515,3 +515,256 @@ impl Config {
|
||||
&mut fields.fields
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Enum {
|
||||
doc: Option<String>,
|
||||
ident: syn::Ident,
|
||||
variants: Vec<syn::Ident>,
|
||||
default: Option<syn::Ident>,
|
||||
}
|
||||
|
||||
impl Enum {
|
||||
pub fn generate_tree(&self) -> TokenStream {
|
||||
let mut tokens = self.generate_tree_key();
|
||||
tokens.extend(self.generate_tree_serialize());
|
||||
tokens.extend(self.generate_tree_deserialize());
|
||||
tokens.extend(self.generate_tree_any());
|
||||
tokens
|
||||
}
|
||||
|
||||
fn keys(&self) -> Vec<(&'static str, syn::Expr)> {
|
||||
let variants = &self.variants;
|
||||
let mut keys = vec![
|
||||
("value", parse_quote!(self)),
|
||||
("variants", parse_quote!([#(Self::#variants,)*])),
|
||||
];
|
||||
if let Some(ref doc) = self.doc {
|
||||
keys.push(("description", parse_quote!(#doc)));
|
||||
}
|
||||
if let Some(ref default) = self.default {
|
||||
keys.push(("default", parse_quote!(Self::#default)));
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
fn generate_tree_key(&self) -> TokenStream {
|
||||
let ident = &self.ident;
|
||||
let keys = self.keys();
|
||||
let num_keys = keys.len();
|
||||
let max_length = keys.iter().map(|(path, _)| path.len()).max();
|
||||
let keys = keys.iter().map(|(path, _)| path);
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tree_serialize(&self) -> TokenStream {
|
||||
let ident = &self.ident;
|
||||
let matches = self
|
||||
.keys()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (_, expr))| {
|
||||
quote! {
|
||||
#i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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(self, 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 {
|
||||
#(#matches)*
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tree_deserialize(&self) -> TokenStream {
|
||||
let ident = &self.ident;
|
||||
let num_keys = self.keys().len();
|
||||
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 {
|
||||
<Self 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 => {
|
||||
<Self as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?;
|
||||
Ok(0)
|
||||
}
|
||||
1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(0, "Cannot write limits").into()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tree_any(&self) -> TokenStream {
|
||||
let ident = &self.ident;
|
||||
let num_keys = self.keys().len();
|
||||
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),
|
||||
1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),
|
||||
_ => 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 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 => Ok(self),
|
||||
1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Enum {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let attrs = input.call(syn::Attribute::parse_outer)?;
|
||||
let doc = attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("doc"))
|
||||
.map(|attr| {
|
||||
let meta = attr.meta.require_name_value()?;
|
||||
if let syn::Expr::Lit(syn::ExprLit {
|
||||
attrs: _,
|
||||
lit: syn::Lit::Str(ref lit),
|
||||
}) = meta.value
|
||||
{
|
||||
Ok(lit.value().trim().to_owned())
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
&meta.value,
|
||||
"Expected string literal for doc comment",
|
||||
))
|
||||
}
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let _vis = input.parse::<syn::Visibility>()?;
|
||||
let _enum_token = input.parse::<Token![enum]>()?;
|
||||
|
||||
let ident = input.parse::<syn::Ident>()?;
|
||||
|
||||
let content;
|
||||
let _brace = braced!(content in input);
|
||||
let variants = content.parse_terminated(syn::Variant::parse, Token![,])?;
|
||||
if let Some(variant) = variants.iter().find(|variant| !variant.fields.is_empty()) {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&variant.fields,
|
||||
"only unit variants are supported for now",
|
||||
));
|
||||
}
|
||||
let default = variants
|
||||
.iter()
|
||||
.find(|variant| {
|
||||
variant
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("default"))
|
||||
})
|
||||
.map(|variant| variant.ident.clone());
|
||||
let variants = variants.into_iter().map(|variant| variant.ident).collect();
|
||||
|
||||
Ok(Self {
|
||||
doc,
|
||||
ident,
|
||||
variants,
|
||||
default,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user