use proc_macro::TokenStream;
use heck::ToLowerCamelCase;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Data, DeriveInput, Fields, LitStr, Type};
pub fn do_fleet_comment(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let struct_name = &ast.ident;
let fields = if let Data::Struct(data_struct) = &ast.data {
if let Fields::Named(fields_named) = &data_struct.fields {
&fields_named.named
} else {
panic!("Commentable can only be derived for structs with named fields");
}
} else {
panic!("Commentable can only be derived for structs");
};
let mut direct_inserts = Vec::new();
let mut nested_inserts = Vec::new();
for field in fields.iter() {
let mut comment_opt = None;
for attr in field.attrs.iter() {
if attr.path().is_ident("with_comment") {
let lit_str: LitStr = attr.parse_args().expect("with_comment 属性需要一个字符串文字");
comment_opt = Some(lit_str.value());
}
}
let comment = comment_opt.unwrap_or("".to_string());
let type_string = field.ty.to_token_stream().to_string().replace(" ", "");
let skip_res = [
"^String$", "^Option<String>$", "^HashMap<String,String>$", "^Vec<String>$", "^HashMap<String,String,>$",
"^i32$", "^i64$", "^f32$", "Option<DateTime<Local>>", "^Option<i32>$", "^Option<i64>$", "^Option<f32>$",
"^Option<bool>$",
];
let skip_res = skip_res.iter().map(|x| regex::Regex::new(x).unwrap()).collect::<Vec<_>>();
let skip_re_match = skip_res.iter().any(|re| re.is_match(&type_string));
let vec_re = regex::Regex::new(r"^Vec<(\w+),?>$").unwrap();
let option_re = regex::Regex::new(r"^Option<(\w+),?>$").unwrap();
let hash_map_re = regex::Regex::new(r"^HashMap<String,(\w+),?>$").unwrap();
let field_ident = field.ident.as_ref().unwrap();
let field_ident_string = field_ident.to_string().to_lower_camel_case().to_string();
direct_inserts.push(quote! {
map.insert(#field_ident_string.to_string(), #comment.to_string());
});
if skip_re_match {
continue;
} else {
let ty = if hash_map_re.is_match(&type_string) {
let matched = hash_map_re.captures(&type_string).unwrap().get(1).unwrap();
let ty: Type = syn::parse_str(matched.as_str()).expect("Failed to parse hash map type");
ty
} else if vec_re.is_match(&type_string) {
let matched = vec_re.captures(&type_string).unwrap().get(1).unwrap();
let ty: Type = syn::parse_str(matched.as_str()).expect("Failed to parse vec type");
ty
} else if option_re.is_match(&type_string) {
let matched = option_re.captures(&type_string).unwrap().get(1).unwrap();
let ty: Type = syn::parse_str(matched.as_str()).expect("Failed to parse option type");
ty
} else {
field.ty.clone()
};
if hash_map_re.is_match(&type_string) {
nested_inserts.push(quote! {
for (k, v) in <#ty>::get_comments() {
map.insert(format!(r"{}\.[\w-]+\.{}", #field_ident_string, k), v);
}
});
} else {
nested_inserts.push(quote! {
for (k, v) in <#ty>::get_comments() {
map.insert(format!(r"{}\.{}", #field_ident_string, k), v);
}
});
}
}
}
let expanded = quote! {
impl Commentable for #struct_name {
fn get_comments() -> std::collections::HashMap<String, String> {
let mut map: HashMap<String, String> = std::collections::HashMap::new();
#(#direct_inserts)*
#(#nested_inserts)*
map
}
}
};
TokenStream::from(expanded)
}