1 // Copyright 2021-2022 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
5 use syn::{parse_macro_input, parse_quote};
6 use syn::{Data, DataStruct, DeriveInput, LitStr, Meta, NestedMeta};
7 use quote::{quote, quote_spanned, ToTokens};
8 use proc_macro2::{Literal, TokenStream};
10 use std::cell::RefCell;
12 use itertools::Itertools;
14 /// Generates config resolver method
16 /// Each field ends up having an SKL and a method.
17 /// The method actually looks up the value in a particular link context.
18 /// SKL is passed to the method, which usually uses it to decide which
19 /// sections to look in. But it is also used by general validation,
20 /// unconditionally, to reject settings in the wrong section.
24 /// * `limited`, `server`, `client`: cooked sets of settings;
25 /// default `SKL` is `PerClient` except for `limited`
26 /// * `global` and `per_client`: set the SKL.
27 /// * `special(method, SKL)`
32 /// impl<'c> ResolveContext<'c> {
34 /// // SKL here is used by SectionKindList::contains()
35 /// const FIELDS: &'static [(&'static str, SectionKindList)] = &[ ... ];
38 /// fn resolve_instance(&self) -> InstanceConfig {
41 /// // SKL here is usually passed to first_of, but the method
42 /// // can do something more special.
43 /// max_batch_down: self.limited("max_batch_down", SKL::PerClient)?,
49 /// pub struct InstanceConfigCommon { ... }
50 /// impl InstanceConfigCommon {
51 /// pub fn from(l: &[InstanceConfig]) { InstanceConfigCommon {
52 /// field: <Type as ResolveGlobal>::resolve(l.iter().map(|e| &e.field)),
57 #[proc_macro_derive(ResolveConfig, attributes(
58 limited, server, client, computed, special,
61 pub fn resolve(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
62 let input = parse_macro_input!(input as DeriveInput);
64 let (fields, top_ident) = match input {
67 data: Data::Struct(DataStruct {
68 fields: syn::Fields::Named(ref f),
76 let target = &input.ident;
78 let mut names = vec![];
79 let mut output = vec![];
80 let mut global_fields = vec![];
81 let mut global_assignments = vec![];
82 let mut inspects = vec![];
83 for field in &fields.named {
85 let fname = &field.ident.as_ref().unwrap();
87 let fname_span = fname.span();
88 let skl = RefCell::new(None);
90 let mut skl = skl.borrow_mut();
91 if let Some(old) = &*skl { panic!("dup SKL {} and {} for field {}",
95 let mut method = quote_spanned!{fname_span=> ordinary };
96 for attr in &field.attrs {
97 let atspan = attr.path.segments.last().unwrap().ident.span();
98 if attr.tokens.is_empty() {
99 if &attr.path == &parse_quote!{ per_client } {
100 set_skl(quote_spanned!{fname_span=> SectionKindList::PerClient });
102 } else if &attr.path == &parse_quote!{ global } {
103 set_skl(quote_spanned!{fname_span=> SectionKindList::Global });
104 global_fields.push(syn::Field {
108 global_assignments.push(quote_spanned!(fname_span=>
109 #fname: <#ty as ResolveGlobal>::resolve
110 (l.iter().map(|e| &e.#fname)),
114 method = attr.path.to_token_stream();
115 if &attr.path == &parse_quote!{ limited } {
116 set_skl(quote_spanned!{atspan=> SectionKindList::Limited });
117 } else if &attr.path == &parse_quote!{ client } {
118 set_skl(quote_spanned!{atspan=> SectionKindList::PerClient });
119 } else if &attr.path == &parse_quote!{ computed } {
120 set_skl(quote_spanned!{atspan=> SectionKindList::None });
122 } else if &attr.path == &parse_quote!{ special } {
123 let meta = match attr.parse_meta().unwrap() {
124 Meta::List(list) => list,
127 let (tmethod, tskl) = meta.nested.iter().collect_tuple().unwrap();
128 fn get_path(meta: &NestedMeta) -> TokenStream {
130 NestedMeta::Meta(Meta::Path(ref path)) => path.to_token_stream(),
134 method = get_path(tmethod);
135 *skl.borrow_mut() = Some(get_path(tskl));
138 let fname_string = fname.to_string();
139 let fname_lit = Literal::string( &fname_string );
140 let skl = skl.into_inner()
141 .expect(&format!("SKL not specified! (field {})!", fname));
148 #fname: rctx. #method ( #fname_lit, #skl )?,
150 inspects.push(quote!{
151 #fname_lit => &self.#fname,
153 //eprintln!("{:?} method={:?} skl={:?}", field.ident, method, skl);
157 let global = syn::Ident::new(&format!("{}Global", top_ident),
160 let output = quote! {
162 const FIELDS: &'static [(&'static str, SectionKindList)]
165 fn resolve_instance(rctx: &ResolveContext)
166 -> ::std::result::Result<#target, anyhow::Error>
168 ::std::result::Result::Ok(#target {
173 pub fn inspect_key(&self, field: &'_ str)
174 -> Option<&dyn InspectableConfigValue> {
184 #( #global_fields ),*
188 pub fn from(l: &[#top_ident]) -> #global { #global {
189 #( #global_assignments )*
194 //eprintln!("{}", &output);
199 pub fn into_crlfs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
200 let input: proc_macro2::TokenStream = input.into();
201 let token: LitStr = syn::parse2(input).expect("expected literal");
202 let input = token.value();
203 let output = input.split_inclusive('\n')
204 .map(|s| s.trim_start_matches(&[' ','\t'][..]))
205 .map(|s| match s.strip_suffix("\n") {
207 Some(l) => [l, "\r\n"],
210 .collect::<String>();
212 let output = LitStr::new(&output, token.span());
213 let output = quote!(#output);