tiaojian_macro/
lib.rs

1mod metadata;
2mod package;
3
4use std::{sync::RwLock, collections::HashSet};
5
6use litrs::StringLit;
7use metadata::get_metadata;
8use once_cell::sync::OnceCell;
9use proc_macro::TokenStream;
10use syn::Token;
11
12use crate::metadata::extract_active_pkg;
13
14/// 存放 active_packages
15static STATIC_ACTIVE_PKGS: OnceCell<RwLock<HashSet<String>>> = OnceCell::new();
16
17fn get_active_packages() -> &'static RwLock<HashSet<String>> {
18    let cache = STATIC_ACTIVE_PKGS.get_or_init(|| {
19        // 解析 metadata
20        let metadata = get_metadata();
21        let active_pkgs = extract_active_pkg(&metadata);
22
23        RwLock::new(active_pkgs)
24    });
25
26    cache
27}
28
29#[derive(Debug)]
30struct Tiaojian {
31    packages: Vec<String>,
32    remove_macro: bool,
33}
34
35#[derive(Debug)]
36struct TiaojianClosure {
37    packages: Vec<String>,
38    body: proc_macro2::TokenStream,
39}
40
41#[derive(Debug)]
42struct RemoveMacro {
43    rest: TokenStream
44}
45
46#[proc_macro_attribute]
47pub fn tiaojian_a(attr: TokenStream, item: TokenStream) -> TokenStream {
48
49    // syn::parse_macro_input!() 是用来只解析 #[proc_macroXXX] 的 input 的,
50    // 它只能针对 #[proc_macroXXX] 能修饰的结构进行解析
51    // let expr_str: proc_macro2::TokenTree = syn::parse_macro_input!(attr);
52
53    match syn::parse::<Tiaojian>(attr) {
54        Err(e) => {
55            e.into_compile_error().into()
56        },
57        Ok(tj) => {
58            // 解析 metadata
59            let active_pkgs = get_active_packages();
60            let active_pkgs = active_pkgs.read().unwrap();
61
62            let all_active = tj.packages.iter().all(|pkg|
63                active_pkgs.iter().any(|act_pkg| act_pkg == pkg)
64            );
65
66            if all_active {
67                item
68            } else {
69                if tj.remove_macro {
70                    let tmp = syn::parse::<RemoveMacro>(item).unwrap();
71                    tmp.rest
72                } else {
73                    TokenStream::new()
74                }
75            }
76        },
77    }
78}
79
80///
81/// ### Example :
82/// 
83/// ```ignore
84/// tiaojian!( |packages1,| {
85///     // statement...
86/// });
87/// ````
88/// 
89#[proc_macro]
90pub fn tiaojian(input: TokenStream) -> TokenStream {
91    match syn::parse::<TiaojianClosure>(input) {
92        Err(e) => {
93            e.into_compile_error().into()
94        },
95        Ok(tj) => {
96            // 解析 metadata
97            let active_pkgs = get_active_packages();
98            let active_pkgs = active_pkgs.read().unwrap();
99
100            let all_active = tj.packages.iter().all(|pkg|
101                active_pkgs.iter().any(|act_pkg| act_pkg == pkg)
102            );
103
104            if all_active {
105                tj.body.into()
106            } else {
107                TokenStream::new()
108            }
109        },
110    }
111}
112
113impl  syn::parse::Parse for TiaojianClosure {
114    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
115        input.parse::<Token![|]>()?;
116
117        let pkg_input_iter = input.cursor().token_stream().into_iter();
118        let mut packages = vec![];
119        for item in pkg_input_iter {
120            match item {
121                proc_macro2::TokenTree::Literal(val) => {
122                    let val = StringLit::try_from(val).unwrap();
123                    packages.push(val.value().to_owned());
124                    input.parse::<syn::LitStr>()?;
125                },
126                proc_macro2::TokenTree::Punct(p) => {
127                    if p.to_string() =="|" {
128                        break;
129                    } else if p.to_string() =="," {
130                        input.parse::<Token![,]>()?;
131                    }
132                },
133                _ => Err(syn::Error::new(input.span(), "tiaojian macro not support the token".to_owned()))?,
134            }
135        }
136
137        input.parse::<Token![|]>()?;
138        let packages_input;
139        syn::braced!(packages_input in input);
140        let body = packages_input.parse::<proc_macro2::TokenStream>().unwrap();
141        Ok(
142            TiaojianClosure {
143                packages,
144                body,
145            }
146        )
147    }
148}
149
150impl  syn::parse::Parse for RemoveMacro {
151    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
152        match input.parse::<Token![#]>() {
153            Ok(_) => {
154                let body_buf;
155                syn::bracketed!(body_buf in input);
156                body_buf.parse::<proc_macro2::TokenStream>().unwrap();
157                Ok(
158                    Self {
159                        rest: input.parse::<proc_macro2::TokenStream>().unwrap().into()
160                    }
161                )
162            },
163            Err(_) => Ok(
164                Self {
165                    rest: input.parse::<proc_macro2::TokenStream>().unwrap().into()
166                }
167            ),
168        }
169    }
170}
171
172/// 注意:使用 syn::parse::Parse trait,就必须将 input 全部 pasrs 完,否则抛出异常
173impl syn::parse::Parse for Tiaojian {
174    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
175        let mut remove_macro = false;
176        let fn_name: syn::Ident =  input.parse()?;
177        if fn_name != "is_active" {
178            return Err(syn::Error::new(input.span(), "tiaojian macro is only support build-in fn 'is_actived'  now".to_owned()));
179        }
180        
181        let packages_input;
182        syn::parenthesized!(packages_input in input);
183        let pkg_input_iter = packages_input.cursor().token_stream().into_iter();
184        let mut packages = vec![];
185
186        for item in pkg_input_iter {
187            match item {
188                proc_macro2::TokenTree::Group(_) => {
189                    return Err(syn::Error::new(input.span(), "error not support group in expression".to_owned()))
190                },
191                proc_macro2::TokenTree::Ident(_) => {
192                    return Err(syn::Error::new(input.span(), "error not support ident in expression".to_owned()))
193                },
194                proc_macro2::TokenTree::Punct(v) => {
195                    if v.to_string() == "," {
196                        packages_input.parse::<Token![,]>()?;
197                    } else {
198                        return Err(syn::Error::new(input.span(), "error only support ',' in expression".to_owned()));
199                    }
200                },
201                proc_macro2::TokenTree::Literal(val) => {
202                    let val = StringLit::try_from(val).unwrap();
203                    packages.push(val.value().to_owned());
204                    packages_input.parse::<syn::LitStr>()?;
205                },
206            }
207        }
208
209        match input.parse::<Token![,]>() {
210            Ok(_) => match input.parse::<syn::Ident>() {
211                Ok(val) => {
212                    if val == "remove_macro" {
213                        remove_macro = true;
214                    }
215                },
216                Err(_) => 
217                    return Err(syn::Error::new(input.span(), "error need a ident".to_owned())),
218            },
219            Err(_) => {}
220        }
221
222        Ok(
223            Tiaojian {
224                packages,
225                remove_macro
226            }
227        )
228    }
229}