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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use proc_macro2::TokenStream as TokenStream2;
use quote::spanned::Spanned;

// TODO: add ident checks and other "token_trees"
/// Get macro input from macro call source text.
/// Input should be in format: `css_module! { ... }`
/// And can be retrived in function like proc-macro `Span::call_site().source_text()`.
///
/// Example:
/// ```rust
/// let input = r#"
/// css_module! {
///    .my-class {
///       color: red;
///   }
/// }
/// "#;
/// let macro_input = rcss_core::macro_helper::macro_input(input, false).unwrap();
/// assert_eq!(macro_input, ".my-class {\n      color: red;\n  }");
/// ```
pub fn macro_input(source_text: &str, skip_ident_arrow: bool) -> Option<String> {
    // 1. Find macro call group (any type of braces)
    // 2. skip whitespaces
    // 3. if skip_ident_arrow is present, skip ident, =, > at begginning
    // 4. return rest of the string or None, if group wasn't found
    let (_path, group_start) = source_text.split_once(|c| "{[(".contains(c))?;
    let (mut group, _end) = group_start.rsplit_once(|c| "}])".contains(c))?;

    if skip_ident_arrow {
        let mut iter = group.splitn(3, |c| c == '=' || c == '>');
        iter.next(); // Foo [, variable_name]
        iter.next(); // =(empty string between tokens)>
        group = iter.next().unwrap_or("").trim();
    }
    let trimed = group.trim();

    Some(trimed.to_owned())
}

/// Get macro input from macro call source text.
/// Using proc_macro2::TokenStream to parse input and skeep tokens.
///
/// Example:
/// ```rust
/// let input = r#"
/// css_module! {
///    .my-class {
///       color: red;
///   }
/// }
/// "#;
/// let macro_input = rcss_core::macro_helper::macro_input_with_token_stream(input, false).unwrap();
/// assert_eq!(macro_input, ".my-class {\n      color: red;\n  }");
/// ```
pub fn macro_input_with_token_stream(source_text: &str, skip_ident_arrow: bool) -> Option<String> {
    use std::str::FromStr;
    // 1. Find macro call group (any type of braces)
    // 2. skip whitespaces
    // 3. if skip_ident_arrow is present, skip ident, =, > at begginning
    // 4. return rest of the string or None, if group wasn't found
    proc_macro2::fallback::force();
    let stream = TokenStream2::from_str(source_text).unwrap();
    let mut stream_iter = stream.into_iter();

    // Skip path to the macro with exclamation mark:
    // example: "foo::module::bar! /*stop */ {...}"
    for tt in stream_iter.by_ref() {
        if let proc_macro2::TokenTree::Punct(p) = tt {
            if p.as_char() == '!' {
                break;
            }
        }
    }

    if let Some(proc_macro2::TokenTree::Group(g)) = stream_iter.next() {
        let mut stream_iter = g.stream().into_iter();
        if skip_ident_arrow {
            for tt in stream_iter.by_ref() {
                let proc_macro2::TokenTree::Punct(p) = tt else {
                    continue;
                };
                if p.as_char() == '=' {
                    // =
                    break;
                }
            }
            stream_iter.next(); // >
        }

        let source = stream_iter.collect::<TokenStream2>();

        source.__span().source_text()
    } else {
        None
    }
}
#[cfg(test)]
mod test {
    #[test]
    fn check_macro_input_extractor() {
        let input = r#"
        css_module! {
            .my-class {
                color: red;
            }
        }
        "#;
        let macro_input = super::macro_input_with_token_stream(input, false).unwrap();
        let compare_optimized = super::macro_input(input, false).unwrap();
        assert_eq!(
            macro_input,
            ".my-class {\n                color: red;\n            }"
        );
        assert_eq!(macro_input, compare_optimized);
    }

    #[test]
    fn check_macro_input_extractor_struct() {
        let input = r#"
        css_struct! {
            Foo =>
            .my-class {
                color: red;
            }
        }
        "#;
        let macro_input = super::macro_input_with_token_stream(input, true).unwrap();
        let compare_optimized = super::macro_input(input, true).unwrap();
        assert_eq!(
            macro_input,
            ".my-class {\n                color: red;\n            }"
        );
        assert_eq!(macro_input, compare_optimized);
    }
}