macroconf/src/lib.rs

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()
}