119 lines
3.4 KiB
Rust
119 lines
3.4 KiB
Rust
//! This crate creates `miniconf::Tree` implementations fields in a struct. These carry some extra
|
|
//! 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`
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// use macroconf::config;
|
|
///
|
|
/// #[config]
|
|
/// struct Config {
|
|
/// /// This will be parsed as description
|
|
/// // this value will have a minimum of i32::MIN,
|
|
/// // a maximum of 50 and a default of 42.
|
|
/// #[config(min, max = "50", default = "42")]
|
|
/// field1: i32,
|
|
/// }
|
|
/// ```
|
|
#[proc_macro_attribute]
|
|
pub fn config(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
let mut input = parse_macro_input!(item as DeriveInput);
|
|
let config = match parser::Config::parse(&input) {
|
|
Ok(c) => c,
|
|
Err(e) => return e.write_errors().into(),
|
|
};
|
|
|
|
let newtypes = config
|
|
.fields()
|
|
.iter()
|
|
.map(|field| field.helper(&config.attrs[..]));
|
|
let newtype_types = newtypes.clone().map(|(gen, ty)| (ty, gen.is_none()));
|
|
let newtypes = newtypes.map(|(gen, _)| gen);
|
|
|
|
let syn::Data::Struct(DataStruct {
|
|
struct_token: _,
|
|
ref mut fields,
|
|
semi_token: _,
|
|
}) = input.data
|
|
else {
|
|
unreachable!()
|
|
};
|
|
|
|
// change types of fields to newtypes and remove the config attributes
|
|
for (field, (ty, skip)) in fields.iter_mut().zip(newtype_types) {
|
|
if skip {
|
|
continue;
|
|
}
|
|
field.ty = ty;
|
|
field.attrs.retain(|attr| !attr.path().is_ident("config"));
|
|
field.attrs.push(parse_quote!(#[tree(depth=1)]));
|
|
}
|
|
|
|
if let Some(derive) = input
|
|
.attrs
|
|
.iter_mut()
|
|
.find(|attr| attr.path().is_ident("derive"))
|
|
{
|
|
match PathList::from_meta(&derive.meta) {
|
|
Ok(derives) => {
|
|
if !derives.iter().any(|path| {
|
|
path.segments
|
|
.last()
|
|
.map(|s| s.ident.to_string().contains("Tree"))
|
|
== Some(true)
|
|
}) {
|
|
input.attrs.push(parse_quote!(#[derive(::miniconf::Tree)]));
|
|
}
|
|
}
|
|
Err(e) => return e.write_errors().into(),
|
|
}
|
|
} else {
|
|
input.attrs.push(parse_quote!(#[derive(::miniconf::Tree)]));
|
|
}
|
|
|
|
quote! {
|
|
#input
|
|
#(#newtypes)*
|
|
}
|
|
.into()
|
|
}
|