chiark / gitweb /
move effective_http_timeout into InstanceConfig
[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 /// Atrributes:
15 ///
16 ///  * `limited`, `server`, `client`: cooked sets of settings;
17 ///    default `SKL` is `Ordinary` except for `limited`
18 ///  * `special(method, SKL)`
19 ///
20 /// Generated code
21 ///
22 /// ```no_run
23 /// impl<'c> ResolveContext<'c> {
24 ///
25 ///   const FIELDS: &'static [(&'static str, SectionKindList)] = &[ ... ];
26 ///
27 ///   #[throws(AE)]
28 ///   fn resolve_instance(&self) -> InstanceConfig {
29 ///     InstanceConfig {
30 ///       ...
31 ///        max_batch_down: self.limited("max_batch_down")?,
32 ///        ...
33 ///      }
34 ///   }
35 /// }
36 /// ```
37 #[proc_macro_derive(ResolveConfig, attributes(
38   limited, server, client, computed, special
39 ))]
40 pub fn resolve(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
41   let input = parse_macro_input!(input as DeriveInput);
42
43   let fields = match input.data {
44     Data::Struct(DataStruct { fields: syn::Fields::Named(ref f),.. }) => f,
45     _ => panic!(),
46   };
47
48   let target = &input.ident;
49
50   let mut names = vec![];
51   let mut output = vec![];
52   for field in &fields.named {
53     //dbg!(field);
54     let fname = &field.ident.as_ref().unwrap();
55     let fname_span = fname.span();
56     let mut skl = quote_spanned!{fname_span=> SectionKindList::Ordinary };
57     let mut method = quote_spanned!{fname_span=> ordinary };
58     for attr in &field.attrs {
59       if attr.tokens.is_empty() {
60         let atspan = attr.path.segments.last().unwrap().ident.span();
61         method = attr.path.to_token_stream();
62         if &attr.path == &parse_quote!{ limited } {
63           skl = quote_spanned!{atspan=> SectionKindList::Limited };
64         }
65       } else if &attr.path == &parse_quote!{ special } {
66         let meta = match attr.parse_meta().unwrap() {
67           Meta::List(list) => list,
68           _ => panic!(),
69         };
70         let (tmethod, tskl) = meta.nested.iter().collect_tuple().unwrap();
71         fn get_path(meta: &NestedMeta) -> TokenStream {
72           match meta {
73             NestedMeta::Meta(Meta::Path(ref path)) => path.to_token_stream(),
74             _ => panic!(),
75           }
76         }
77         method = get_path(tmethod);
78         skl    = get_path(tskl);
79       }
80     }
81     let fname_string = fname.to_string();
82     let fname_lit = Literal::string( &fname_string );
83
84     names.push(quote!{
85       (#fname_lit, #skl),
86     });
87     //dbg!(&method);
88     output.push(quote!{
89       #fname: rctx. #method ( #fname_lit )?,
90     });
91     //eprintln!("{:?} method={:?} skl={:?}", field.ident, method, skl);
92   }
93   //dbg!(&output);
94
95   let output = quote! {
96     impl #target {
97       const FIELDS: &'static [(&'static str, SectionKindList)]
98         = &[ #( #names )* ];
99
100       fn resolve_instance(rctx: &ResolveContext)
101           -> ::std::result::Result<#target, anyhow::Error>
102       {
103         ::std::result::Result::Ok(#target {
104           #( #output )*
105         })
106       }
107     }
108   };
109   //eprintln!("{}", &output);
110   output.into()
111 }
112
113 #[proc_macro]
114 pub fn into_crlfs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
115   let input: proc_macro2::TokenStream = input.into();
116   let token: LitStr = syn::parse2(input).expect("expected literal");
117   let input = token.value();
118   let output = input.split_inclusive('\n')
119     .map(|s| s.trim_start_matches(&[' ','\t'][..]))
120     .map(|s| match s.strip_suffix("\n") {
121       None => [s, ""],
122       Some(l) => [l, "\r\n"],
123     })
124     .flatten()
125     .collect::<String>();
126   //dbg!(&output);
127   let output = LitStr::new(&output, token.span());
128   let output = quote!(#output);
129   output.into()
130 }