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);
}
}