tusks_lib/parsing/attribute/
parse.rs

1use syn::{Ident, LitBool, LitInt, LitStr, Token, parenthesized, parse::{Parse, ParseStream}};
2
3use crate::parsing::attribute::models::{TasksConfig, TusksAttr};
4
5impl Parse for TusksAttr {
6    /// Parses the `#[tusks(...)]` attribute and extracts all configuration options.
7    /// 
8    /// Supports the following syntax:
9    /// - Boolean flags: `debug`, `root`, `derive_debug_for_parameters`
10    ///   - Can be specified as just the flag name (implies `true`)
11    ///   - Or with explicit value: `debug = true` or `debug = false`
12    /// - Nested configuration: `tasks(max_groupsize=5, max_depth=20, separator=".")`
13    /// 
14    /// # Example
15    /// ```ignore
16    /// #[tusks(root, debug, tasks(max_groupsize=10, separator="/"))]
17    /// ```
18    /// 
19    /// # Errors
20    /// Returns an error if:
21    /// - An unknown attribute name is encountered
22    /// - The syntax is malformed (missing commas, invalid values, etc.)
23    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" => attr.tasks = Some(parse_nested_config(input)?),
36                other => return Err(unknown_attribute_error(&ident, other)),
37            }
38            
39            parse_trailing_comma(input)?;
40        }
41        
42        Ok(attr)
43    }
44}
45
46impl Parse for TasksConfig {
47    /// Parses the task configuration parameters inside `tasks(...)`.
48    /// 
49    /// All parameters are optional and will use default values if not specified:
50    /// - `max_groupsize`: defaults to 5
51    /// - `max_depth`: defaults to 20
52    /// - `separator`: defaults to "."
53    /// 
54    /// # Example
55    /// ```ignore
56    /// tasks(max_groupsize=10, separator="/")
57    /// // Results in: max_groupsize=10, max_depth=20 (default), separator="/"
58    /// ```
59    /// 
60    /// # Errors
61    /// Returns an error if:
62    /// - An unknown parameter name is encountered
63    /// - A parameter value has the wrong type (e.g., string instead of integer)
64    /// - The syntax is malformed (missing `=`, invalid literals, etc.)
65    fn parse(input: ParseStream) -> syn::Result<Self> {
66        let mut config = TasksConfig::default();
67        
68        while !input.is_empty() {
69            let ident: Ident = input.parse()?;
70            input.parse::<Token![=]>()?;
71            
72            match ident.to_string().as_str() {
73                "max_groupsize" => config.max_groupsize = parse_usize(input)?,
74                "max_depth" => config.max_depth = parse_usize(input)?,
75                "separator" => config.separator = parse_string(input)?,
76                other => return Err(unknown_parameter_error(&ident, other)),
77            }
78            
79            parse_trailing_comma(input)?;
80        }
81        
82        Ok(config)
83    }
84}
85
86// Helper functions
87
88/// Parse an optional boolean flag that can be either `flag` or `flag = true/false`
89fn parse_bool_flag(input: ParseStream) -> syn::Result<bool> {
90    if input.peek(Token![=]) {
91        input.parse::<Token![=]>()?;
92        let value: LitBool = input.parse()?;
93        Ok(value.value)
94    } else {
95        Ok(true)
96    }
97}
98
99/// Parse a nested configuration like `tasks(...)`
100fn parse_nested_config<T: Parse>(input: ParseStream) -> syn::Result<T> {
101    let content;
102    parenthesized!(content in input);
103    content.parse::<T>()
104}
105
106/// Parse a trailing comma if present
107fn parse_trailing_comma(input: ParseStream) -> syn::Result<()> {
108    if !input.is_empty() {
109        input.parse::<Token![,]>()?;
110    }
111    Ok(())
112}
113
114/// Parse a usize literal
115fn parse_usize(input: ParseStream) -> syn::Result<usize> {
116    let value: LitInt = input.parse()?;
117    value.base10_parse()
118}
119
120/// Parse a string literal
121fn parse_string(input: ParseStream) -> syn::Result<String> {
122    let value: LitStr = input.parse()?;
123    Ok(value.value())
124}
125
126/// Create error for unknown attribute
127fn unknown_attribute_error(ident: &Ident, name: &str) -> syn::Error {
128    syn::Error::new(
129        ident.span(),
130        format!("unknown tusks attribute: {}", name)
131    )
132}
133
134/// Create error for unknown parameter
135fn unknown_parameter_error(ident: &Ident, name: &str) -> syn::Error {
136    syn::Error::new(
137        ident.span(),
138        format!("unknown tasks parameter: {}", name)
139    )
140}