tusks_lib/parsing/attribute/
parse.rs1use syn::{Ident, LitBool, LitInt, LitStr, Token, parenthesized, parse::{Parse, ParseStream}};
2
3use crate::parsing::attribute::models::{TasksConfig, TusksAttr};
4
5impl Parse for TusksAttr {
6 fn parse(input: ParseStream) -> syn::Result<Self> {
24 let mut attr = TusksAttr::default();
25
26 while !input.is_empty() {
27 let ident: Ident = input.parse()?;
28
29 match ident.to_string().as_str() {
30 "debug" => attr.debug = parse_bool_flag(input)?,
31 "root" => attr.root = parse_bool_flag(input)?,
32 "derive_debug_for_parameters" => {
33 attr.derive_debug_for_parameters = parse_bool_flag(input)?
34 },
35 "tasks" => {
36 attr.tasks = Some(parse_optional_nested_config::<TasksConfig>(input)?);
37 },
38 other => return Err(unknown_attribute_error(&ident, other)),
39 }
40
41 parse_trailing_comma(input)?;
42 }
43
44 Ok(attr)
45 }
46}
47
48fn parse_optional_nested_config<T>(input: ParseStream) -> syn::Result<T>
52where
53 T: Parse + Default,
54{
55 if input.peek(syn::token::Paren) {
56 parse_nested_config(input)
57 } else {
58 Ok(T::default())
59 }
60}
61
62impl Parse for TasksConfig {
63 fn parse(input: ParseStream) -> syn::Result<Self> {
82 let mut config = TasksConfig::default();
83
84 while !input.is_empty() {
85 let ident: Ident = input.parse()?;
86
87 match ident.to_string().as_str() {
88 "max_groupsize" => config.max_groupsize = parse_required_value(input, parse_usize)?,
89 "max_depth" => config.max_depth = parse_required_value(input, parse_usize)?,
90 "separator" => config.separator = parse_required_value(input, parse_string)?,
91 "use_colors" => config.use_colors = parse_bool_flag(input)?,
92 other => return Err(unknown_parameter_error(&ident, other)),
93 }
94
95 parse_trailing_comma(input)?;
96 }
97
98 Ok(config)
99 }
100}
101
102fn parse_bool_flag(input: ParseStream) -> syn::Result<bool> {
106 if input.peek(Token![=]) {
107 input.parse::<Token![=]>()?;
108 let value: LitBool = input.parse()?;
109 Ok(value.value)
110 } else {
111 Ok(true)
112 }
113}
114
115fn parse_required_value<T, F>(input: ParseStream, parser: F) -> syn::Result<T>
117where
118 F: FnOnce(ParseStream) -> syn::Result<T>,
119{
120 input.parse::<Token![=]>()?;
121 parser(input)
122}
123
124fn parse_nested_config<T: Parse>(input: ParseStream) -> syn::Result<T> {
126 let content;
127 parenthesized!(content in input);
128 content.parse::<T>()
129}
130
131fn parse_trailing_comma(input: ParseStream) -> syn::Result<()> {
133 if !input.is_empty() {
134 input.parse::<Token![,]>()?;
135 }
136 Ok(())
137}
138
139fn parse_usize(input: ParseStream) -> syn::Result<usize> {
141 let value: LitInt = input.parse()?;
142 value.base10_parse()
143}
144
145fn parse_string(input: ParseStream) -> syn::Result<String> {
147 let value: LitStr = input.parse()?;
148 Ok(value.value())
149}
150
151fn unknown_attribute_error(ident: &Ident, name: &str) -> syn::Error {
153 syn::Error::new(
154 ident.span(),
155 format!("unknown tusks attribute: {}", name)
156 )
157}
158
159fn unknown_parameter_error(ident: &Ident, name: &str) -> syn::Error {
161 syn::Error::new(
162 ident.span(),
163 format!("unknown tasks parameter: {}", name)
164 )
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 fn parse_tusks_attr(input: &str) -> syn::Result<TusksAttr> {
172 syn::parse_str::<TusksAttr>(input)
173 }
174
175 fn parse_tasks_config(input: &str) -> syn::Result<TasksConfig> {
176 syn::parse_str::<TasksConfig>(input)
177 }
178
179 #[test]
182 fn empty_attr() {
183 let attr = parse_tusks_attr("").unwrap();
184 assert!(!attr.debug);
185 assert!(!attr.root);
186 assert!(!attr.derive_debug_for_parameters);
187 assert!(attr.tasks.is_none());
188 }
189
190 #[test]
191 fn root_flag() {
192 let attr = parse_tusks_attr("root").unwrap();
193 assert!(attr.root);
194 assert!(!attr.debug);
195 }
196
197 #[test]
198 fn debug_flag() {
199 let attr = parse_tusks_attr("debug").unwrap();
200 assert!(attr.debug);
201 }
202
203 #[test]
204 fn derive_debug_flag() {
205 let attr = parse_tusks_attr("derive_debug_for_parameters").unwrap();
206 assert!(attr.derive_debug_for_parameters);
207 }
208
209 #[test]
210 fn explicit_bool_true() {
211 let attr = parse_tusks_attr("root = true").unwrap();
212 assert!(attr.root);
213 }
214
215 #[test]
216 fn explicit_bool_false() {
217 let attr = parse_tusks_attr("root = false").unwrap();
218 assert!(!attr.root);
219 }
220
221 #[test]
222 fn multiple_flags() {
223 let attr = parse_tusks_attr("root, debug").unwrap();
224 assert!(attr.root);
225 assert!(attr.debug);
226 }
227
228 #[test]
229 fn all_flags() {
230 let attr = parse_tusks_attr("root, debug, derive_debug_for_parameters").unwrap();
231 assert!(attr.root);
232 assert!(attr.debug);
233 assert!(attr.derive_debug_for_parameters);
234 }
235
236 #[test]
237 fn unknown_attribute_errors() {
238 let err = parse_tusks_attr("unknown").unwrap_err();
239 assert!(err.to_string().contains("unknown tusks attribute"));
240 }
241
242 #[test]
243 fn tasks_without_parens_uses_defaults() {
244 let attr = parse_tusks_attr("root, tasks").unwrap();
245 assert!(attr.root);
246 let tasks = attr.tasks.unwrap();
247 assert_eq!(tasks.max_groupsize, 5);
248 assert_eq!(tasks.max_depth, 20);
249 assert_eq!(tasks.separator, ".");
250 assert!(tasks.use_colors);
251 }
252
253 #[test]
254 fn tasks_with_custom_config() {
255 let attr = parse_tusks_attr(
256 "root, tasks(max_groupsize = 10, separator = \"/\", max_depth = 3)"
257 ).unwrap();
258 let tasks = attr.tasks.unwrap();
259 assert_eq!(tasks.max_groupsize, 10);
260 assert_eq!(tasks.max_depth, 3);
261 assert_eq!(tasks.separator, "/");
262 }
263
264 #[test]
265 fn tasks_use_colors_false() {
266 let attr = parse_tusks_attr("tasks(use_colors = false)").unwrap();
267 let tasks = attr.tasks.unwrap();
268 assert!(!tasks.use_colors);
269 }
270
271 #[test]
272 fn tasks_use_colors_flag_only() {
273 let attr = parse_tusks_attr("tasks(use_colors)").unwrap();
274 let tasks = attr.tasks.unwrap();
275 assert!(tasks.use_colors);
276 }
277
278 #[test]
281 fn tasks_config_defaults() {
282 let config = parse_tasks_config("").unwrap();
283 assert_eq!(config.max_groupsize, 5);
284 assert_eq!(config.max_depth, 20);
285 assert_eq!(config.separator, ".");
286 assert!(config.use_colors);
287 }
288
289 #[test]
290 fn tasks_config_partial_override() {
291 let config = parse_tasks_config("max_groupsize = 3").unwrap();
292 assert_eq!(config.max_groupsize, 3);
293 assert_eq!(config.max_depth, 20); }
295
296 #[test]
297 fn tasks_config_unknown_parameter_errors() {
298 let err = parse_tasks_config("unknown = 5").unwrap_err();
299 assert!(err.to_string().contains("unknown tasks parameter"));
300 }
301
302 #[test]
303 fn tasks_config_custom_separator() {
304 let config = parse_tasks_config("separator = \"::\"").unwrap();
305 assert_eq!(config.separator, "::");
306 }
307}