1#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
4
5use std::io::Write as _;
6
7use proc_macro::TokenStream;
8use quote::ToTokens;
9use syn::{LitStr, Token, parse_macro_input, punctuated::Punctuated};
10use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
11
12#[proc_macro]
27pub fn error(input: TokenStream) -> TokenStream {
28 do_alert(Color::Red, input);
29 panic!("Build failed");
30}
31
32#[proc_macro]
46pub fn warning(input: TokenStream) -> TokenStream {
47 do_alert(Color::Yellow, input)
48}
49
50#[proc_macro]
57pub fn assert_unique_features(input: TokenStream) -> TokenStream {
58 let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
59 .into_iter()
60 .collect::<Vec<_>>();
61
62 let unique = impl_unique_features(&features, "exactly zero or one");
63
64 quote::quote! {
65 #unique
66 }
67 .into()
68}
69
70#[proc_macro]
78pub fn assert_used_features(input: TokenStream) -> TokenStream {
79 let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
80 .into_iter()
81 .collect::<Vec<_>>();
82
83 let used = impl_used_features(&features, "at least one");
84
85 quote::quote! {
86 #used
87 }
88 .into()
89}
90
91#[proc_macro]
98pub fn assert_unique_used_features(input: TokenStream) -> TokenStream {
99 let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
100 .into_iter()
101 .collect::<Vec<_>>();
102
103 let unique = impl_unique_features(&features, "exactly one");
104 let used = impl_used_features(&features, "exactly one");
105
106 quote::quote! {
107 #unique
108 #used
109 }
110 .into()
111}
112
113fn impl_unique_features(features: &[LitStr], expectation: &str) -> impl ToTokens + use<> {
117 let pairs = unique_pairs(features);
118 let unique_cfgs = pairs
119 .iter()
120 .map(|(a, b)| quote::quote! { all(feature = #a, feature = #b) });
121
122 let message = format!(
123 r#"
124ERROR: expected {expectation} enabled feature from feature group:
125 {:?}
126"#,
127 features.iter().map(|lit| lit.value()).collect::<Vec<_>>(),
128 );
129
130 quote::quote! {
131 #[cfg(any(#(#unique_cfgs),*))]
132 ::esp_build::error! { #message }
133 }
134}
135
136fn impl_used_features(features: &[LitStr], expectation: &str) -> impl ToTokens + use<> {
137 let message = format!(
138 r#"
139ERROR: expected {expectation} enabled feature from feature group:
140 {:?}
141 "#,
142 features.iter().map(|lit| lit.value()).collect::<Vec<_>>()
143 );
144
145 quote::quote! {
146 #[cfg(not(any(#(feature = #features),*)))]
147 ::esp_build::error! { #message }
148 }
149}
150
151fn do_alert(color: Color, input: TokenStream) -> TokenStream {
154 let message = parse_macro_input!(input as LitStr).value();
155
156 let stderr = &mut StandardStream::stderr(ColorChoice::Auto);
157 let color_spec = ColorSpec::new().set_fg(Some(color)).clone();
158
159 let mut has_nonspace = false;
160
161 for mut line in message.lines() {
162 if !has_nonspace {
163 let (maybe_heading, rest) = split_heading(line);
164
165 if let Some(heading) = maybe_heading {
166 stderr.set_color(color_spec.clone().set_bold(true)).ok();
167 write!(stderr, "\n{heading}").ok();
168 has_nonspace = true;
169 }
170
171 line = rest;
172 }
173
174 if line.is_empty() {
175 writeln!(stderr).ok();
176 } else {
177 stderr.set_color(&color_spec).ok();
178 writeln!(stderr, "{line}").ok();
179
180 has_nonspace = has_nonspace || line.contains(|ch: char| ch != ' ');
181 }
182 }
183
184 stderr.reset().ok();
185 writeln!(stderr).ok();
186
187 TokenStream::new()
188}
189
190fn split_heading(s: &str) -> (Option<&str>, &str) {
193 let mut end = 0;
194 while end < s.len() && s[end..].starts_with(|ch: char| ch.is_ascii_uppercase()) {
195 end += 1;
196 }
197
198 if end >= 3 && (end == s.len() || s[end..].starts_with(':')) {
199 let (heading, rest) = s.split_at(end);
200 (Some(heading), rest)
201 } else {
202 (None, s)
203 }
204}
205
206fn unique_pairs(features: &[LitStr]) -> Vec<(&LitStr, &LitStr)> {
207 let mut pairs = Vec::new();
208
209 let mut i = 0;
210 let mut j = 0;
211
212 while i < features.len() {
213 let a = &features[i];
214 let b = &features[j];
215
216 if a.value() != b.value() {
217 pairs.push((a, b));
218 }
219
220 j += 1;
221
222 if j >= features.len() {
223 i += 1;
224 j = i;
225 }
226 }
227
228 pairs
229}