use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Expr, Lit};
use std::collections::HashMap;
pub fn do_fleet_resources(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let variants = if let Data::Enum(resource_enums) = &ast.data {
&resource_enums.variants
} else {
panic!("Resources can only be derived for enums");
};
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);
}
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
};
TokenStream::from(expanded)
}