#![allow(clippy::style)]
#![allow(clippy::expect_fun_call)]
#![allow(clippy::map_flatten)]
#![allow(clippy::single_char_pattern)]
use syn::{parse_macro_input, parse_quote};
use syn::{Data, DataStruct, DeriveInput, LitStr, Meta, NestedMeta};
use quote::{quote, quote_spanned, ToTokens};
use proc_macro2::{Literal, TokenStream};
use std::cell::RefCell;
use itertools::Itertools;
#[proc_macro_derive(ResolveConfig, attributes(
limited, server, client, computed, special,
per_client, global,
))]
pub fn resolve(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let (fields, top_ident) = match input {
DeriveInput {
ref ident,
data: Data::Struct(DataStruct {
fields: syn::Fields::Named(ref f),
..
}),
..
} => (f, ident),
_ => panic!(),
};
let target = &input.ident;
let mut names = vec![];
let mut output = vec![];
let mut global_fields = vec![];
let mut global_assignments = vec![];
let mut g_inspects = vec![];
let mut t_inspects = vec![];
for field in &fields.named {
let fname = &field.ident.as_ref().unwrap();
let fname_string = fname.to_string();
let fname_lit = Literal::string( &fname_string );
let ty = &field.ty;
let fname_span = fname.span();
let skl = RefCell::new(None);
let set_skl = |new| {
let mut skl = skl.borrow_mut();
if let Some(old) = &*skl { panic!("dup SKL {} and {} for field {}",
old, new, &fname); }
*skl = Some(new);
};
let mut method = quote_spanned!{fname_span=> ordinary };
for attr in &field.attrs {
let atspan = attr.path.segments.last().unwrap().ident.span();
if attr.tokens.is_empty() {
let inspect = quote!{
#fname_lit => &self.#fname,
};
t_inspects.push(inspect.clone());
if attr.path == parse_quote!{ per_client } {
set_skl(quote_spanned!{fname_span=> SectionKindList::PerClient });
continue;
} else if attr.path == parse_quote!{ global } {
set_skl(quote_spanned!{fname_span=> SectionKindList::Global });
global_fields.push(syn::Field {
attrs: vec![],
..field.clone()
});
global_assignments.push(quote_spanned!(fname_span=>
#fname: <#ty as ResolveGlobal>::resolve
(l.iter().map(|e| &e.#fname)),
));
g_inspects.push(inspect);
continue;
}
method = attr.path.to_token_stream();
if attr.path == parse_quote!{ limited } {
set_skl(quote_spanned!{atspan=> SectionKindList::Limited });
} else if attr.path == parse_quote!{ client } {
set_skl(quote_spanned!{atspan=> SectionKindList::PerClient });
} else if attr.path == parse_quote!{ computed } {
set_skl(quote_spanned!{atspan=> SectionKindList::None });
}
} else if attr.path == parse_quote!{ special } {
let meta = match attr.parse_meta().unwrap() {
Meta::List(list) => list,
_ => panic!(),
};
let (tmethod, tskl) = meta.nested.iter().collect_tuple().unwrap();
fn get_path(meta: &NestedMeta) -> TokenStream {
match meta {
NestedMeta::Meta(Meta::Path(ref path)) => path.to_token_stream(),
_ => panic!(),
}
}
method = get_path(tmethod);
*skl.borrow_mut() = Some(get_path(tskl));
}
}
let skl = skl.into_inner()
.expect(&format!("SKL not specified! (field {})!", fname));
names.push(quote!{
(#fname_lit, #skl),
});
output.push(quote!{
#fname: rctx. #method ( #fname_lit, #skl )?,
});
}
let global = syn::Ident::new(&format!("{}Global", top_ident),
top_ident.span());
let mk_inspects = |self_, inspects: Vec<_>| quote! {
impl InspectableConfigAuto for #self_ {
fn inspect_key_auto(&self, field: &'_ str)
-> Option<&dyn InspectableConfigValue> {
Some(match field {
#( #inspects )*
_ => return None,
})
}
}
};
let g_inspects = mk_inspects(&global, g_inspects);
let t_inspects = mk_inspects(&target, t_inspects);
let output = quote! {
impl #target {
const FIELDS: &'static [(&'static str, SectionKindList)]
= &[ #( #names )* ];
fn resolve_instance(rctx: &ResolveContext)
-> ::std::result::Result<#target, anyhow::Error>
{
::std::result::Result::Ok(#target {
#( #output )*
})
}
}
#t_inspects
#[derive(Debug)]
pub struct #global {
#( #global_fields ),*
}
impl #global {
pub fn from(l: &[#top_ident]) -> #global { #global {
#( #global_assignments )*
} }
}
#g_inspects
};
output.into()
}
#[proc_macro]
pub fn into_crlfs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: proc_macro2::TokenStream = input.into();
let token: LitStr = syn::parse2(input).expect("expected literal");
let input = token.value();
let output = input.split_inclusive('\n')
.map(|s| s.trim_start_matches(&[' ','\t'][..]))
.map(|s| match s.strip_suffix("\n") {
None => [s, ""],
Some(l) => [l, "\r\n"],
})
.flatten()
.collect::<String>();
let output = LitStr::new(&output, token.span());
let output = quote!(#output);
output.into()
}