Skip to main content

pyro_macro/module/
parse.rs

1use syn::{
2    Ident, Result, Token, parenthesized,
3    parse::{Parse, ParseStream},
4    punctuated::Punctuated,
5};
6
7/// The three supported output patterns
8pub enum OutputSpec {
9    /// Single field: `output = "field_name"`
10    SingleField(Ident),
11    /// Tuple fields: `output = (field1, field2, ...)`  
12    TupleFields(Vec<Ident>),
13    /// Existing struct: `output = StructName`
14    Struct,
15}
16
17/// Parsed attributes for #[module(...)]
18pub struct ModuleAttrs {
19    pub output: OutputSpec,
20}
21
22impl Parse for ModuleAttrs {
23    fn parse(input: ParseStream) -> Result<Self> {
24        // Expect: output = ...
25        let ident: Ident = input.parse()?;
26        if ident != "output" {
27            return Err(syn::Error::new(ident.span(), "Expected `output = ...`"));
28        }
29
30        input.parse::<Token![=]>()?;
31
32        // Determine which pattern based on what follows
33        let output = if input.peek(syn::token::Paren) {
34            // Pattern 2: (field1, field2, ...)
35            let content;
36            parenthesized!(content in input);
37            let fields: Punctuated<Ident, Token![,]> =
38                content.parse_terminated(Ident::parse, Token![,])?;
39            OutputSpec::TupleFields(fields.into_iter().collect())
40        } else {
41            // Could be Pattern 1 (lowercase field) or Pattern 3 (PascalCase struct)
42            let name: Ident = input.parse()?;
43            let name_str = name.to_string();
44
45            // Heuristic: PascalCase = struct, snake_case/lowercase = field
46            if name_str
47                .chars()
48                .next()
49                .map(|c| c.is_uppercase())
50                .unwrap_or(false)
51            {
52                OutputSpec::Struct
53            } else {
54                OutputSpec::SingleField(name)
55            }
56        };
57
58        Ok(ModuleAttrs { output })
59    }
60}