use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{FnArg, ItemFn};
use super::{attrs::ComponentAttrs, dependency::DependencyInfo, utils};
pub fn generate_plugin_impl(
attrs: &ComponentAttrs,
func: &ItemFn,
dependencies: &[DependencyInfo],
) -> TokenStream {
let func_name = &func.sig.ident;
let return_type = match utils::extract_return_type(&func.sig.output) {
Some(ty) => ty,
None => {
return quote! {
compile_error!("#[component] function must return a value");
}
}
};
let plugin_struct_name = generate_plugin_struct_name(attrs, &return_type);
let plugin_logical_name = attrs
.name
.clone()
.unwrap_or_else(|| format!("__Create{}Plugin", extract_type_name(&return_type)));
let dependency_names: Vec<_> = dependencies
.iter()
.map(|dep| {
dep.explicit_plugin_name
.clone()
.unwrap_or_else(|| dep.inferred_plugin_name.clone())
})
.collect();
let param_extractions = generate_param_extractions(func, dependencies);
let param_names = extract_param_names(func);
let func_call = quote! { #func_name(#(#param_names),*) };
let is_async = func.sig.asyncness.is_some();
let await_token = if is_async {
quote! { .await }
} else {
quote! {}
};
let is_result = is_result_type(&return_type);
let result_handling = if is_result {
quote! {
let component = #func_call #await_token
.expect(&format!("Failed to create component in {}", #plugin_logical_name));
}
} else {
quote! {
let component = #func_call #await_token;
}
};
quote! {
struct #plugin_struct_name;
#[::summer::async_trait]
impl ::summer::plugin::Plugin for #plugin_struct_name {
async fn build(&self, app: &mut ::summer::app::AppBuilder) {
use ::summer::config::ConfigRegistry;
use ::summer::plugin::{ComponentRegistry, MutableComponentRegistry};
#(#param_extractions)*
#result_handling
app.add_component(component);
}
fn name(&self) -> &str {
#plugin_logical_name
}
fn dependencies(&self) -> Vec<&str> {
vec![#(#dependency_names),*]
}
}
}
}
pub fn generate_inventory_submit(attrs: &ComponentAttrs, func: &ItemFn) -> TokenStream {
let return_type = match utils::extract_return_type(&func.sig.output) {
Some(ty) => ty,
None => return quote! {},
};
let plugin_struct_name = generate_plugin_struct_name(attrs, &return_type);
quote! {
::summer::submit_component_plugin!(#plugin_struct_name);
}
}
fn generate_plugin_struct_name(attrs: &ComponentAttrs, return_type: &syn::Type) -> syn::Ident {
if let Some(name) = &attrs.name {
format_ident!("__{}", name)
} else {
let type_name = extract_type_name(return_type);
format_ident!("__Create{}Plugin", type_name)
}
}
fn extract_type_name(ty: &syn::Type) -> String {
match ty {
syn::Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
segment.ident.to_string()
} else {
"Unknown".to_string()
}
}
_ => "Unknown".to_string(),
}
}
fn is_result_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Result";
}
}
false
}
fn generate_param_extractions(
func: &ItemFn,
dependencies: &[DependencyInfo],
) -> Vec<TokenStream> {
let mut extractions = Vec::new();
let mut dep_index = 0;
for param in &func.sig.inputs {
if let FnArg::Typed(pat_type) = param {
let pat = &pat_type.pat;
let ty = &*pat_type.ty;
if is_config_type(ty) {
if let Some(config_type) = utils::extract_generic_type(ty) {
extractions.push(quote! {
let #pat = {
let config = app.get_config::<#config_type>()
.expect(&format!("Config {} not found", stringify!(#config_type)));
::summer::extractor::Config(config)
};
});
}
} else if is_component_type(ty) {
if dep_index < dependencies.len() {
let component_type = &dependencies[dep_index].component_type;
extractions.push(quote! {
let #pat = {
let component = app.get_component::<#component_type>()
.expect(&format!("Component {} not found", stringify!(#component_type)));
::summer::extractor::Component(component)
};
});
dep_index += 1;
}
}
}
}
extractions
}
fn extract_param_names(func: &ItemFn) -> Vec<&syn::Pat> {
func.sig
.inputs
.iter()
.filter_map(|param| {
if let FnArg::Typed(pat_type) = param {
Some(&*pat_type.pat)
} else {
None
}
})
.collect()
}
fn is_config_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Config";
}
}
false
}
fn is_component_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Component";
}
}
false
}