1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
use crate::key_value::KeyValue;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{bracketed, token, Token};

/// Holds an outer attribute filled with key-value options.
///
/// Streams in the following form will be parsed successfully:
/// ```text
/// #[key1 = value1, bool_key2, key3 = value]
/// ```
///
/// The value part of an option is optional.
/// Thus, `bool_key2` will have the value `default`.
#[cfg_attr(any(test, feature = "extra-traits"), derive(Eq, PartialEq, Debug))]
#[derive(Default)]
pub struct OptionsAttribute {
    pub pound_token: Token![#],
    pub bracket_token: token::Bracket,
    pub options: Punctuated<KeyValue, Token![,]>,
}

/// Make OptionsAttribute parsable from a token stream
impl Parse for OptionsAttribute {
    fn parse(input: ParseStream) -> Result<Self> {
        // Used to hold the stream inside square brackets
        let content;

        Ok(OptionsAttribute {
            pound_token: input.parse()?,
            bracket_token: bracketed!(content in input), // Use `syn` to extract the stream inside the square bracket group
            options: content.parse_terminated(KeyValue::parse, Token![,])?,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;
    use syn::parse_str;

    type Result = std::result::Result<(), Box<dyn std::error::Error>>;

    #[test]
    fn parse() -> Result {
        let actual: OptionsAttribute =
            parse_str("#[tmpl = {trait To {};}, no_default, other = Top]")?;
        let mut expected = OptionsAttribute {
            pound_token: Default::default(),
            bracket_token: Default::default(),
            options: Punctuated::new(),
        };

        expected.options.push(parse_str("tmpl = {trait To {};}")?);
        expected.options.push(parse_str("no_default")?);
        expected.options.push(parse_str("other = Top")?);

        assert_eq!(actual, expected);
        Ok(())
    }
}