chiark / gitweb /
config macros: Provide `per_client` and `global` attrs
[hippotat.git] / macros / macros.rs
1 // Copyright 2021 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
4
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};
9
10 use itertools::Itertools;
11
12 /// Generates config resolver method
13 /// 
14 /// Each field ends up having an SKL and a method.
15 /// The method actually looks up the value in a particular link context.
16 /// SKL is passed to the method, which usually uses it to decide which
17 /// sections to look in.  But it is also used by general validation,
18 /// unconditionally, to reject settings in the wrong section.
19 ///
20 /// Atrributes:
21 ///
22 ///  * `limited`, `server`, `client`: cooked sets of settings;
23 ///    default `SKL` is `PerClient` except for `limited`
24 ///  * `global` and `per_client`: set the SKL.
25 ///  * `special(method, SKL)`
26 ///
27 /// Generated code
28 ///
29 /// ```no_run
30 /// impl<'c> ResolveContext<'c> {
31 ///
32 ///   // SKL here is used by SectionKindList::contains()
33 ///   const FIELDS: &'static [(&'static str, SectionKindList)] = &[ ... ];
34 ///
35 ///   #[throws(AE)]
36 ///   fn resolve_instance(&self) -> InstanceConfig {
37 ///     InstanceConfig {
38 ///       ...
39 ///        // SKL here is usually passed to first_of, but the method
40 ///        // can do something more special.
41 ///        max_batch_down: self.limited("max_batch_down", SKL::PerClient)?,
42 ///        ...
43 ///      }
44 ///   }
45 /// }
46 /// ```
47 #[proc_macro_derive(ResolveConfig, attributes(
48   limited, server, client, computed, special,
49   per_client, global,
50 ))]
51 pub fn resolve(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
52   let input = parse_macro_input!(input as DeriveInput);
53
54   let fields = match input.data {
55     Data::Struct(DataStruct { fields: syn::Fields::Named(ref f),.. }) => f,
56     _ => panic!(),
57   };
58
59   let target = &input.ident;
60
61   let mut names = vec![];
62   let mut output = vec![];
63   for field in &fields.named {
64     //dbg!(field);
65     let fname = &field.ident.as_ref().unwrap();
66     let fname_span = fname.span();
67     let mut skl = Some(quote_spanned!{fname_span=> SectionKindList::PerClient });
68     let mut method = quote_spanned!{fname_span=> ordinary };
69     for attr in &field.attrs {
70       let atspan = attr.path.segments.last().unwrap().ident.span();
71       if attr.tokens.is_empty() {
72         if &attr.path == &parse_quote!{ per_client } {
73           skl = Some(quote_spanned!{fname_span=> SectionKindList::PerClient });
74           continue;
75         } else if &attr.path == &parse_quote!{ global } {
76           skl = Some(quote_spanned!{fname_span=>
77                                     SectionKindList::Global });
78           continue;
79         }
80         method = attr.path.to_token_stream();
81         if &attr.path == &parse_quote!{ limited } {
82           skl = Some(quote_spanned!{atspan=> SectionKindList::Limited });
83         } else if &attr.path == &parse_quote!{ computed } {
84           skl = Some(quote_spanned!{atspan=> SectionKindList::None });
85         }
86       } else if &attr.path == &parse_quote!{ special } {
87         let meta = match attr.parse_meta().unwrap() {
88           Meta::List(list) => list,
89           _ => panic!(),
90         };
91         let (tmethod, tskl) = meta.nested.iter().collect_tuple().unwrap();
92         fn get_path(meta: &NestedMeta) -> TokenStream {
93           match meta {
94             NestedMeta::Meta(Meta::Path(ref path)) => path.to_token_stream(),
95             _ => panic!(),
96           }
97         }
98         method = get_path(tmethod);
99         skl = Some(get_path(tskl));
100       }
101     }
102     let fname_string = fname.to_string();
103     let fname_lit = Literal::string( &fname_string );
104     let skl = skl.expect(&format!("SKL not specified! (field {:?})!", fname));
105
106     names.push(quote!{
107       (#fname_lit, #skl),
108     });
109     //dbg!(&method);
110     output.push(quote!{
111       #fname: rctx. #method ( #fname_lit, #skl )?,
112     });
113     //eprintln!("{:?} method={:?} skl={:?}", field.ident, method, skl);
114   }
115   //dbg!(&output);
116
117   let output = quote! {
118     impl #target {
119       const FIELDS: &'static [(&'static str, SectionKindList)]
120         = &[ #( #names )* ];
121
122       fn resolve_instance(rctx: &ResolveContext)
123           -> ::std::result::Result<#target, anyhow::Error>
124       {
125         ::std::result::Result::Ok(#target {
126           #( #output )*
127         })
128       }
129     }
130   };
131   //eprintln!("{}", &output);
132   output.into()
133 }
134
135 #[proc_macro]
136 pub fn into_crlfs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
137   let input: proc_macro2::TokenStream = input.into();
138   let token: LitStr = syn::parse2(input).expect("expected literal");
139   let input = token.value();
140   let output = input.split_inclusive('\n')
141     .map(|s| s.trim_start_matches(&[' ','\t'][..]))
142     .map(|s| match s.strip_suffix("\n") {
143       None => [s, ""],
144       Some(l) => [l, "\r\n"],
145     })
146     .flatten()
147     .collect::<String>();
148   //dbg!(&output);
149   let output = LitStr::new(&output, token.span());
150   let output = quote!(#output);
151   output.into()
152 }