fleetmacros 0.4.0

Macros for fleet
Documentation
/**
* Copyright(2024,)Institute of Software, Chinese Academy of Sciences
* author: yangzichao21@otcaix.iscas.ac.cn
* since: 0.0.0.1
*
**/
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Expr, Lit};
use std::collections::HashMap;

// 生成资源枚举的实现
// attributes: api_version 字符串类型,表示资源版本
// attributes: plural 字符串类型,表示资源复数形式
// attributes: singular 字符串类型,表示资源单数形式
pub fn do_fleet_resources(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    // let enum_name = &ast.ident;
    // println!("enum_name: {:?}", enum_name);

    let variants = if let Data::Enum(resource_enums) = &ast.data {
        &resource_enums.variants
    } else {
        panic!("Resources can only be derived for enums");
    };

    // println!("{:?} enum variants: {:?}", enum_name, variants);

    let mut resource_impl_codes = Vec::new();
    let mut kind_to_metas = HashMap::new();
    for variant in variants.iter() {
        let mut api_version_opt = None;
        let mut plural_opt = None;
        let mut singular_opt = None;
        let parse_opt = |attr: &syn::Attribute, attr_name: &str, opt: &mut Option<String>| {
            let meta = &attr.meta;
            let nv = meta.require_name_value().expect("expected name-value attribute");
            if nv.path.is_ident(attr_name) {
                if let Expr::Lit(expr_lit) = &nv.value {
                    if let Lit::Str(lit_str) = &expr_lit.lit {
                        *opt = Some(lit_str.value().to_string());
                    }
                }
            }
        };
        for attr in variant.attrs.iter() {
            parse_opt(attr, "api_version", &mut api_version_opt);
            parse_opt(attr, "plural", &mut plural_opt);
            parse_opt(attr, "singular", &mut singular_opt);
        }
        // println!("api_version_opt: {:?}", api_version_opt);
        // println!("plural_opt: {:?}", plural_opt);
        // println!("singular_opt: {:?}", singular_opt);
    
        // 获取字段的类型作为字符串(这里只做简单比较)
        // println!("full variant: {:?}, variant.fields: {:?}", quote! { #variant }.to_string(), variant.fields.to_token_stream().to_string());
        let api_version = api_version_opt.unwrap_or("v1".to_string());
        let plural = plural_opt.expect("plural 属性是必须的");
        let singular = singular_opt.unwrap_or(pluralizer::pluralize(plural.as_str(), 1, false));
        let variant_ident = &variant.ident;
        let kind = variant_ident.to_string();
        let resource_impl_code = quote! {
            impl Resource for #variant_ident {
                fn get_kind(&self) -> &str {
                    self.kind.as_str()
                }

                fn get_api_version(&self) -> &str {
                    self.api_version.as_str()
                }

                fn get_metadata(&self) -> &Option<Metadata> {
                    &self.metadata
                }

                fn get_resource_meta() -> ResourceMeta {
                    ResourceMeta::from_kind(#kind).unwrap()
                }
            }
        };
        resource_impl_codes.push(resource_impl_code);
        kind_to_metas.insert(kind.to_string(), (api_version, plural, singular));
    }
    
    let get_meta_code = {
        let matches = kind_to_metas.iter().map(|(kind, (api_version, plural, singular))| {
            quote! {
                #kind => Ok((#api_version.to_string(), #plural.to_string(), #singular.to_string())),
            }
        });
        quote! {
            match kind {
                #(#matches)*
                _ => return Err(anyhow::anyhow!("unknown kind: {}", kind)),
            }
        }
    };
    let plural_to_kind = kind_to_metas.iter().map(|(kind, (_, plural, _))| {
        (plural.to_string(), kind.to_string())
    }).collect::<HashMap<String, String>>();
    let singular_to_kind = kind_to_metas.iter().map(|(kind, (_, _, singular))| {
        (singular.to_string(), kind.to_string())
    }).collect::<HashMap<String, String>>();
    let plural_to_kind_code = {
        let matches = plural_to_kind.iter().map(|(plural, kind)| {
            quote! {
                #plural => Ok(#kind.to_string()),
            }
        });
        quote! {
            match plural {
                #(#matches)*
                _ => return Err(anyhow::anyhow!("unknown plural: {}", plural)),
            }
        }
    };
    let singular_to_kind_code = {
        let matches = singular_to_kind.iter().map(|(singular, kind)| {
            quote! {
                #singular => Ok(#kind.to_string()),
            }
        });
        quote! {
            match singular {
                #(#matches)*
                _ => return Err(anyhow::anyhow!("unknown singular: {}", singular)),
            }
        }
    };
    let resource_meta_impl_code = quote! {
        impl ResourceMeta {
            pub fn from_kind(kind: &str) -> anyhow::Result<Self> {
                if kind.is_empty() {
                    return Err(anyhow::anyhow!("kind is empty"));
                }
                let res = #get_meta_code;
                let (api_version, plural, singular) = match res {
                    Ok((api_version, plural, singular)) => (api_version, plural, singular),
                    Err(e) => return Err(e),
                };
                Ok(Self {
                    kind: kind.to_string(),
                    api_version: api_version.to_string(),
                    plural: plural.to_string(),
                    singular: singular.to_string(),
                })
            }

            pub fn plural_to_kind(plural: &str) -> anyhow::Result<String> {
                #plural_to_kind_code
            }

            pub fn singular_to_kind(singular: &str) -> anyhow::Result<String> {
                #singular_to_kind_code
            }
        }
    };

    let from_resource_meta_code = quote! {
        impl From<ResourceMeta> for ResourceKind {
            fn from(resource_meta: ResourceMeta) -> Self {
                Self::from_str(&resource_meta.kind).unwrap()
            }
        }
    };

    let from_resource_kind_code = quote! {
        impl From<ResourceKind> for ResourceMeta {
            fn from(resource_kind: ResourceKind) -> Self {
                ResourceMeta::from_kind(&resource_kind.to_string()).unwrap()
            }
        }
    };
    
    let expanded = quote! {
        #(#resource_impl_codes)*
        #resource_meta_impl_code
        #from_resource_meta_code
        #from_resource_kind_code
    };
    // println!("expanded: {:?}", expanded.to_string());
    
    TokenStream::from(expanded)
}