1mod options_metadata;
2
3use proc_macro::TokenStream;
4use quote::{quote, quote_spanned};
5use syn::spanned::Spanned;
6use syn::{Attribute, DeriveInput, ImplItem, ItemImpl, LitStr, parse_macro_input};
7
8#[proc_macro_derive(OptionsMetadata, attributes(option, doc, option_group))]
9pub fn derive_options_metadata(input: TokenStream) -> TokenStream {
10 let input = parse_macro_input!(input as DeriveInput);
11
12 options_metadata::derive_impl(input)
13 .unwrap_or_else(syn::Error::into_compile_error)
14 .into()
15}
16
17#[proc_macro_derive(CombineOptions)]
18pub fn derive_combine(input: TokenStream) -> TokenStream {
19 let input = parse_macro_input!(input as DeriveInput);
20 impl_combine(&input)
21}
22
23fn impl_combine(ast: &DeriveInput) -> TokenStream {
24 let name = &ast.ident;
25 let fields = if let syn::Data::Struct(syn::DataStruct {
26 fields: syn::Fields::Named(ref fields),
27 ..
28 }) = ast.data
29 {
30 &fields.named
31 } else {
32 unimplemented!();
33 };
34
35 let combines = fields.iter().map(|f| {
36 let name = &f.ident;
37 quote! {
38 #name: self.#name.combine(other.#name)
39 }
40 });
41
42 let stream = quote! {
43 impl crate::Combine for #name {
44 fn combine(self, other: #name) -> #name {
45 #name {
46 #(#combines),*
47 }
48 }
49 }
50 };
51 stream.into()
52}
53
54fn get_doc_comment(attrs: &[Attribute]) -> String {
55 attrs
56 .iter()
57 .filter_map(|attr| {
58 if attr.path().is_ident("doc") {
59 if let syn::Meta::NameValue(meta) = &attr.meta {
60 if let syn::Expr::Lit(expr) = &meta.value {
61 if let syn::Lit::Str(str) = &expr.lit {
62 return Some(str.value().trim().to_string());
63 }
64 }
65 }
66 }
67 None
68 })
69 .collect::<Vec<_>>()
70 .join("\n")
71}
72
73fn get_env_var_pattern_from_attr(attrs: &[Attribute]) -> Option<String> {
74 attrs
75 .iter()
76 .find(|attr| attr.path().is_ident("attr_env_var_pattern"))
77 .and_then(|attr| attr.parse_args::<LitStr>().ok())
78 .map(|lit_str| lit_str.value())
79}
80
81fn get_added_in(attrs: &[Attribute]) -> Option<String> {
82 attrs
83 .iter()
84 .find(|a| a.path().is_ident("attr_added_in"))
85 .and_then(|attr| attr.parse_args::<LitStr>().ok())
86 .map(|lit_str| lit_str.value())
87}
88
89fn is_hidden(attrs: &[Attribute]) -> bool {
90 attrs.iter().any(|attr| attr.path().is_ident("attr_hidden"))
91}
92
93#[proc_macro_attribute]
95pub fn attribute_env_vars_metadata(_attr: TokenStream, input: TokenStream) -> TokenStream {
96 let ast = parse_macro_input!(input as ItemImpl);
97
98 let constants: Vec<_> = ast
99 .items
100 .iter()
101 .filter_map(|item| match item {
102 ImplItem::Const(item) if !is_hidden(&item.attrs) => {
103 let doc = get_doc_comment(&item.attrs);
104 let added_in = get_added_in(&item.attrs);
105 let syn::Expr::Lit(syn::ExprLit {
106 lit: syn::Lit::Str(lit),
107 ..
108 }) = &item.expr
109 else {
110 return None;
111 };
112 let name = lit.value();
113 Some((name, doc, added_in, item.ident.span()))
114 }
115 ImplItem::Fn(item) if !is_hidden(&item.attrs) => {
116 if let Some(pattern) = get_env_var_pattern_from_attr(&item.attrs) {
118 let doc = get_doc_comment(&item.attrs);
119 let added_in = get_added_in(&item.attrs);
120 Some((pattern, doc, added_in, item.sig.span()))
121 } else {
122 None }
124 }
125 _ => None,
126 })
127 .collect();
128
129 let added_in_errors: Vec<_> = constants
131 .iter()
132 .filter_map(|(name, _, added_in, span)| {
133 added_in.is_none().then_some({
134 let msg = format!(
135 "missing #[attr_added_in(\"x.y.z\")] on `{name}`\nnote: env vars for an upcoming release should be annotated with `#[attr_added_in(\"next release\")]`"
136 );
137 quote_spanned! {*span => compile_error!(#msg); }
138 })
139 })
140 .collect();
141
142 if !added_in_errors.is_empty() {
143 return quote! { #ast #(#added_in_errors)* }.into();
144 }
145
146 let struct_name = &ast.self_ty;
147 let pairs = constants.iter().map(|(name, doc, added_in, _span)| {
148 if let Some(added_in) = added_in {
149 quote! { (#name, #doc, Some(#added_in)) }
150 } else {
151 quote! { (#name, #doc, None) }
152 }
153 });
154
155 let expanded = quote! {
156 #ast
157
158 impl #struct_name {
159 pub fn metadata<'a>() -> &'a [(&'static str, &'static str, Option<&'static str>)] {
161 &[#(#pairs),*]
162 }
163 }
164 };
165
166 expanded.into()
167}
168
169#[proc_macro_attribute]
170pub fn attr_hidden(_attr: TokenStream, item: TokenStream) -> TokenStream {
171 item
172}
173
174#[proc_macro_attribute]
175pub fn attr_env_var_pattern(_attr: TokenStream, item: TokenStream) -> TokenStream {
176 item
177}
178
179#[proc_macro_attribute]
180pub fn attr_added_in(_attr: TokenStream, item: TokenStream) -> TokenStream {
181 item
182}