mod errors;
mod option_field;
mod scalar_field;
mod util;
mod vector_field;
use errors::OptStoreErr;
use util::identify_field_type;
use proc_macro::TokenStream;
use std::collections::HashMap;
#[proc_macro_derive(OptStore, attributes(opt))]
pub fn opt_store_derive(input: TokenStream) -> TokenStream {
let input = &syn::parse_macro_input!(input as syn::DeriveInput);
match generate_opt_store_impl(input) {
Ok(generated) => generated,
Err(err) => err.to_compile_error().into(),
}
}
fn generate_opt_store_impl(input: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
let struct_name = &input.ident;
let (impl_generics, _, where_clause) = &input.generics.split_for_impl();
let struct_data = match &input.data {
syn::Data::Struct(data) => data,
_ => return Err(OptStoreErr::MustPutOnStruct.at(input.ident.span())),
};
let struct_span = input.ident.span();
let mut cfg_vec = Vec::<proc_macro2::TokenStream>::new();
let mut init_vec = Vec::<proc_macro2::TokenStream>::new();
let mut set_vec = Vec::<proc_macro2::TokenStream>::new();
for field in &struct_data.fields {
collect_impl_for_field(
field,
&mut cfg_vec,
&mut init_vec,
&mut set_vec,
struct_span,
)?;
}
let expanded = quote::quote! {
#[automatically_derived]
impl #impl_generics #struct_name #where_clause {
pub fn with_defaults() -> #struct_name {
#struct_name {
#(#init_vec),*
}
}
}
#[automatically_derived]
impl #impl_generics cliargs::OptStore for #struct_name #where_clause {
fn make_opt_cfgs(&self) -> Vec<cliargs::OptCfg> {
vec![
#(#cfg_vec),*
]
}
fn set_field_values(&mut self, m: &std::collections::HashMap<&str, Vec<&str>>) -> Result<(), cliargs::errors::InvalidOption> {
#(#set_vec)*
Ok(())
}
}
};
Ok(expanded.into())
}
fn collect_impl_for_field(
field: &syn::Field,
cfg_vec: &mut Vec<proc_macro2::TokenStream>,
init_vec: &mut Vec<proc_macro2::TokenStream>,
set_vec: &mut Vec<proc_macro2::TokenStream>,
struct_span: proc_macro2::Span,
) -> Result<(), syn::Error> {
let field_name = match field.ident.as_ref() {
Some(ident) => ident,
None => return Err(OptStoreErr::MustNotPutOnTuple.at(struct_span)),
};
let field_span = field_name.span();
let mut attr_map = HashMap::<String, String>::new();
let mut attr_span: Option<proc_macro2::Span> = None;
for attr in &field.attrs {
if attr.path().is_ident("opt") {
let span = attr.path().get_ident().unwrap().span();
attr_span = Some(span);
collect_impl_for_field_attr(attr, &mut attr_map, span)?;
}
}
if let Some((ty_ident, in_vec, in_opt)) = identify_field_type(&field.ty) {
if in_opt {
return option_field::collect_impl(
field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
);
} else if in_vec {
return vector_field::collect_impl(
field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
);
} else {
return scalar_field::collect_impl(
field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
);
}
}
Err(OptStoreErr::BadFieldType.at(field_span))
}
fn collect_impl_for_field_attr(
attr: &syn::Attribute,
attr_map: &mut HashMap<String, String>,
attr_span: proc_macro2::Span,
) -> Result<(), syn::Error> {
let nested = attr.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
)?;
for meta in nested {
match meta {
syn::Meta::NameValue(meta) => {
if meta.path.is_ident("cfg") {
match meta.value {
syn::Expr::Lit(lit) => match lit.lit {
syn::Lit::Str(s) => {
let value = s.value();
match &value.split_once("=") {
Some((lhs, rhs)) => {
attr_map.insert("names".to_string(), lhs.to_string());
attr_map.insert("defaults".to_string(), rhs.to_string());
}
None => {
attr_map.insert("names".to_string(), value);
}
}
}
_ => return Err(OptStoreErr::BadAttrMetaValueCfg.at(attr_span)),
},
_ => return Err(OptStoreErr::BadAttrMetaValueCfg.at(attr_span)),
}
} else if meta.path.is_ident("desc") {
match meta.value {
syn::Expr::Lit(lit) => match lit.lit {
syn::Lit::Str(s) => {
attr_map.insert("desc".to_string(), s.value());
}
_ => return Err(OptStoreErr::BadAttrMetaValueDesc.at(attr_span)),
},
_ => return Err(OptStoreErr::BadAttrMetaValueDesc.at(attr_span)),
}
} else if meta.path.is_ident("arg") {
match meta.value {
syn::Expr::Lit(lit) => match lit.lit {
syn::Lit::Str(s) => {
attr_map.insert("arg".to_string(), s.value());
}
_ => return Err(OptStoreErr::BadAttrMetaValueArg.at(attr_span)),
},
_ => return Err(OptStoreErr::BadAttrMetaValueArg.at(attr_span)),
}
} else {
return Err(OptStoreErr::BadAttrMetaName.at(attr_span));
}
}
_ => return Err(OptStoreErr::BadAttrMetaName.at(attr_span)),
}
}
Ok(())
}