Skip to main content

itools_macros/
lib.rs

1#![warn(missing_docs)]
2
3//! iTools CLI 核心宏模块
4//!
5//! 提供类似 clap derive 的宏 API,支持位置参数、选项参数和子命令,
6//! 以及基于 i18n 的错误处理宏。
7
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::*;
11
12/// 命令行参数解析宏
13///
14/// 用于替代 clap::Parser,提供类型安全的参数解析。
15///
16/// # 示例
17/// ```rust
18/// #[itools_macros::Command]
19/// pub struct Cli {
20///     /// 命令
21///     pub command: Command,
22/// }
23///
24/// #[itools_macros::Command]
25/// pub enum Command {
26///     /// 初始化新项目
27///     Init {
28///         /// 项目名称
29///         name: String,
30///     },
31///     /// 构建项目
32///     Build,
33/// }
34/// ```
35#[proc_macro_attribute]
36pub fn command(_args: TokenStream, input: TokenStream) -> TokenStream {
37    let input = parse_macro_input!(input as DeriveInput);
38
39    match input.data {
40        Data::Struct(ref data) => handle_struct(&input, data),
41        Data::Enum(ref data) => handle_enum(&input, data),
42        _ => {
43            let error: proc_macro2::TokenStream = parse_quote! {
44                compile_error!("#[Command] can only be used on structs and enums");
45            };
46            error.into()
47        }
48    }
49}
50
51/// 错误处理宏
52///
53/// 用于替代 thiserror,提供基于 i18n 的错误处理。
54///
55/// # 示例
56/// ```rust
57/// #[itools_macros::Error]
58/// pub enum AppError {
59///     /// 网络错误
60///     #[error("network_error")]
61///     NetworkError,
62///     /// 文件未找到
63///     #[error("file_not_found")]
64///     FileNotFound { path: String },
65/// }
66/// ```
67#[proc_macro_attribute]
68pub fn error(_args: TokenStream, input: TokenStream) -> TokenStream {
69    let input = parse_macro_input!(input as DeriveInput);
70
71    match input.data {
72        Data::Struct(ref data) => handle_error_struct(&input, data),
73        Data::Enum(ref data) => handle_error_enum(&input, data),
74        _ => {
75            let error: proc_macro2::TokenStream = parse_quote! {
76                compile_error!("#[Error] can only be used on structs and enums");
77            };
78            error.into()
79        }
80    }
81}
82
83/// 处理结构体
84fn handle_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream {
85    let name = &input.ident;
86    let generics = &input.generics;
87    let where_clause = &generics.where_clause;
88
89    let expanded = match &data.fields {
90        Fields::Named(fields) => {
91            let field_parsers = fields.named.iter().map(|field| {
92                let ident = field.ident.as_ref().unwrap();
93                let ty = &field.ty;
94                quote! {
95                    #ident: <#ty as itools::Parse>::parse(args),
96                }
97            });
98            quote! {
99                #input
100
101                impl itools::Parse for #name #generics #where_clause {
102                    fn parse(args: &mut Vec<String>) -> Self {
103                        Self {
104                            #(#field_parsers)*
105                        }
106                    }
107                }
108
109                impl #name #generics #where_clause {
110                    /// 创建构建器
111                    pub fn builder() -> itools::Builder {
112                        itools::Builder::new()
113                    }
114                }
115            }
116        }
117        Fields::Unnamed(fields) => {
118            let field_parsers = fields.unnamed.iter().map(|field| {
119                let ty = &field.ty;
120                quote! {
121                    <#ty as itools::Parse>::parse(args),
122                }
123            });
124            quote! {
125                #input
126
127                impl itools::Parse for #name #generics #where_clause {
128                    fn parse(args: &mut Vec<String>) -> Self {
129                        Self(
130                            #(#field_parsers)*
131                        )
132                    }
133                }
134
135                impl #name #generics #where_clause {
136                    /// 创建构建器
137                    pub fn builder() -> itools::Builder {
138                        itools::Builder::new()
139                    }
140                }
141            }
142        }
143        Fields::Unit => quote! {
144            #input
145
146            impl itools::Parse for #name #generics #where_clause {
147                fn parse(args: &mut Vec<String>) -> Self {
148                    Self
149                }
150            }
151
152            impl #name #generics #where_clause {
153                /// 创建构建器
154                pub fn builder() -> itools::Builder {
155                    itools::Builder::new()
156                }
157            }
158        },
159    };
160
161    expanded.into()
162}
163
164/// 处理枚举
165fn handle_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
166    let name = &input.ident;
167    let generics = &input.generics;
168    let where_clause = &generics.where_clause;
169
170    let variants = data.variants.iter().map(|variant| {
171        let variant_ident = &variant.ident;
172        // 将驼峰命名转换为连字符分隔的命令名称
173        let variant_str = {
174            let s = variant_ident.to_string();
175            let mut result = String::new();
176            for (i, c) in s.chars().enumerate() {
177                if i > 0 && c.is_uppercase() {
178                    result.push('-');
179                }
180                result.push(c.to_lowercase().next().unwrap());
181            }
182            result
183        };
184
185        match &variant.fields {
186            Fields::Named(fields) => {
187                let field_parsers = fields.named.iter().map(|field| {
188                    let ident = field.ident.as_ref().unwrap();
189                    let ty = &field.ty;
190                    quote! {
191                        #ident: <#ty as itools::Parse>::parse(args),
192                    }
193                });
194                quote! {
195                    #variant_str => {
196                        Self::#variant_ident {
197                            #(#field_parsers)*
198                        }
199                    }
200                }
201            }
202            Fields::Unnamed(fields) => {
203                let field_parsers = fields.unnamed.iter().map(|field| {
204                    let ty = &field.ty;
205                    quote! {
206                        <#ty as itools::Parse>::parse(args),
207                    }
208                });
209                quote! {
210                    #variant_str => {
211                        Self::#variant_ident(
212                            #(#field_parsers)*
213                        )
214                    }
215                }
216            }
217            Fields::Unit => {
218                quote! {
219                    #variant_str => Self::#variant_ident,
220                }
221            }
222        }
223    });
224
225    let expanded = quote! {
226        #input
227
228        impl itools::Parse for #name #generics #where_clause {
229            fn parse(args: &mut Vec<String>) -> Self {
230                if args.is_empty() {
231                    panic!("Expected command");
232                }
233                let cmd = args.remove(0);
234                match cmd.as_str() {
235                    #(#variants)*
236                    _ => panic!("Unknown command: {}", cmd),
237                }
238            }
239        }
240
241        impl #name #generics #where_clause {
242            /// 创建构建器
243            pub fn builder() -> itools::Builder {
244                itools::Builder::new()
245            }
246        }
247    };
248
249    expanded.into()
250}
251
252/// 处理错误结构体
253fn handle_error_struct(input: &DeriveInput, _data: &DataStruct) -> TokenStream {
254    let name = &input.ident;
255    let generics = &input.generics;
256    let where_clause = &generics.where_clause;
257
258    let error_message = get_error_message(&input.attrs);
259
260    let expanded = quote! {
261        #input
262
263        impl std::fmt::Debug for #name #generics #where_clause {
264            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265                std::fmt::Debug::fmt(self, f)
266            }
267        }
268
269        impl std::fmt::Display for #name #generics #where_clause {
270            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271                write!(f, "{}", self.localize())
272            }
273        }
274
275        impl std::error::Error for #name #generics #where_clause {}
276
277        impl itools_core::LocalizableError for #name #generics #where_clause {
278            fn localize(&self) -> String {
279                itools::t(#error_message)
280            }
281        }
282    };
283
284    expanded.into()
285}
286
287/// 处理错误枚举
288fn handle_error_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
289    let name = &input.ident;
290    let generics = &input.generics;
291    let where_clause = &generics.where_clause;
292
293    let variants = data.variants.iter().map(|variant| {
294        let variant_ident = &variant.ident;
295        let error_message = get_error_message(&variant.attrs);
296
297        match &variant.fields {
298            Fields::Named(fields) => {
299                let field_names = fields.named.iter().map(|field| {
300                    let ident = field.ident.as_ref().unwrap();
301                    quote! { #ident }
302                });
303                quote! {
304                    Self::#variant_ident { #(#field_names),* } => itools::t(#error_message)
305                }
306            }
307            Fields::Unnamed(fields) => {
308                let field_names = (0..fields.unnamed.len()).map(|i| {
309                    let i = syn::Index::from(i);
310                    quote! { self.#i }
311                });
312                quote! {
313                    Self::#variant_ident(#(#field_names),*) => itools::t(#error_message)
314                }
315            }
316            Fields::Unit => {
317                quote! {
318                    Self::#variant_ident => itools::t(#error_message)
319                }
320            }
321        }
322    });
323
324    let expanded = quote! {
325        #input
326
327        impl std::fmt::Debug for #name #generics #where_clause {
328            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329                std::fmt::Debug::fmt(self, f)
330            }
331        }
332
333        impl std::fmt::Display for #name #generics #where_clause {
334            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335                write!(f, "{}", self.localize())
336            }
337        }
338
339        impl std::error::Error for #name #generics #where_clause {}
340
341        impl itools_core::LocalizableError for #name #generics #where_clause {
342            fn localize(&self) -> String {
343                match self {
344                    #(#variants)*
345                }
346            }
347        }
348    };
349
350    expanded.into()
351}
352
353/// 获取错误消息
354fn get_error_message(attrs: &[Attribute]) -> proc_macro2::TokenStream {
355    for attr in attrs {
356        if attr.path().is_ident("error") {
357            // 简单处理:直接提取字符串字面量
358            if let Ok(lit_str) = attr.parse_args::<syn::LitStr>() {
359                return quote! { #lit_str };
360            }
361        }
362    }
363    quote! { "unknown_error" }
364}
365
366/// 翻译宏
367///
368/// 用于获取翻译,支持重载:
369/// - `t!("key")` - 基本翻译
370/// - `t!("key", &[("name", "value")])` - 带参数的翻译
371///
372/// # 示例
373/// ```rust
374/// // 基本翻译
375/// let greeting = t!("hello");
376///
377/// // 带参数的翻译
378/// let greeting = t!("greeting", &[("name", "World")]);
379/// ```
380#[proc_macro]
381pub fn t(input: TokenStream) -> TokenStream {
382    // 尝试解析为单个字符串字面量(基本翻译)
383    if let Ok(lit_str) = syn::parse::<syn::LitStr>(input.clone()) {
384        let key = lit_str.value();
385
386        // 简单的编译时检查:确保键不为空
387        if key.is_empty() {
388            let error: proc_macro2::TokenStream = parse_quote! {
389                compile_error!("t! macro expects a non-empty string literal as translation key");
390            };
391            return error.into();
392        }
393
394        let expanded = quote! {
395            itools_localization::t(#lit_str)
396        };
397
398        return expanded.into();
399    }
400
401    // 尝试解析为元组(带参数的翻译)
402    if let Ok(expr_tuple) = syn::parse::<syn::ExprTuple>(input) {
403        if expr_tuple.elems.len() != 2 {
404            let error: proc_macro2::TokenStream = parse_quote! {
405                compile_error!("t! macro expects either one argument (key) or two arguments (key, args)");
406            };
407            return error.into();
408        }
409
410        let key = &expr_tuple.elems[0];
411        let args = &expr_tuple.elems[1];
412
413        let expanded = quote! {
414            itools_localization::t_with_args(#key, #args)
415        };
416
417        return expanded.into();
418    }
419
420    // 解析失败
421    let error: proc_macro2::TokenStream = parse_quote! {
422        compile_error!("t! macro expects either a string literal or a tuple of (string literal, args)");
423    };
424    error.into()
425}