extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{
parse_macro_input, ItemFn, FnArg, Pat, PatIdent, PatType,
Ident, LitStr, ItemStruct, Expr, ExprLit, Lit,
Meta, MetaNameValue, Token, parse::Parse, parse::ParseStream,
Result, punctuated::Punctuated
};
#[proc_macro_attribute]
pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let fn_name = &input.sig.ident;
let fn_vis = &input.vis;
let attr_str = attr.to_string();
let description = if attr_str.is_empty() {
format!("Action {}", fn_name)
} else {
attr_str.trim_matches('"').to_string()
};
let meta_fn_name = format_ident!("{}_metadata", fn_name);
let mut param_defs = Vec::new();
for arg in &input.sig.inputs {
if let FnArg::Typed(pat_type) = arg {
if let Pat::Ident(pat_ident) = &*pat_type.pat {
let param_name = &pat_ident.ident;
if param_name == "self" {
continue;
}
let param_name_str = param_name.to_string();
param_defs.push(quote! {
ActionParameter {
name: #param_name_str.to_string(),
description: format!("Parameter {}", #param_name_str),
required: true,
param_type: ParamType::String, default_value: None,
}
});
}
}
}
let meta_fn = if param_defs.is_empty() {
quote! {
fn #meta_fn_name() -> ActionDefinition {
ActionDefinition {
name: #fn_name.to_string(),
description: #description.to_string(),
parameters: vec![],
}
}
}
} else {
quote! {
fn #meta_fn_name() -> ActionDefinition {
ActionDefinition {
name: #fn_name.to_string(),
description: #description.to_string(),
parameters: vec![
#(#param_defs),*
],
}
}
}
};
let result = quote! {
#input
#meta_fn
};
result.into()
}
struct ParamDef {
name: Ident,
description: String,
param_type: Ident,
required: bool,
default_value: Option<Expr>,
}
impl Parse for ParamDef {
fn parse(input: ParseStream) -> Result<Self> {
let content;
syn::parenthesized!(content in input);
let name: Ident = content.parse()?;
content.parse::<Token![,]>()?;
let desc_lit: LitStr = content.parse()?;
let description = desc_lit.value();
content.parse::<Token![,]>()?;
let param_type: Ident = content.parse()?;
content.parse::<Token![,]>()?;
let required_ident: Ident = content.parse()?;
let required = required_ident == "required";
let default_value = if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
Some(content.parse()?)
} else {
None
};
Ok(ParamDef {
name,
description,
param_type,
required,
default_value,
})
}
}
struct CpiActionMeta {
description: String,
params: Vec<ParamDef>,
}
impl Parse for CpiActionMeta {
fn parse(input: ParseStream) -> Result<Self> {
let mut description = String::new();
let mut params = Vec::new();
let attrs = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
for attr in attrs {
match attr {
Meta::NameValue(nv) if nv.path.is_ident("description") => {
if let Expr::Lit(expr_lit) = &nv.value {
if let Lit::Str(s) = &expr_lit.lit {
description = s.value();
}
}
},
Meta::List(list) if list.path.is_ident("param") => {
let param: ParamDef = syn::parse2(list.tokens)?;
params.push(param);
},
_ => return Err(syn::Error::new_spanned(attr, "Expected 'description' or 'param'")),
}
}
Ok(CpiActionMeta {
description,
params,
})
}
}
struct CpiExtensionMeta {
name: String,
provider_type: String,
}
impl Parse for CpiExtensionMeta {
fn parse(input: ParseStream) -> Result<Self> {
let mut name = None;
let mut provider_type = None;
let attrs = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
for attr in attrs {
match attr {
Meta::NameValue(nv) if nv.path.is_ident("name") => {
if let Expr::Lit(expr_lit) = &nv.value {
if let Lit::Str(s) = &expr_lit.lit {
name = Some(s.value());
}
}
},
Meta::NameValue(nv) if nv.path.is_ident("provider_type") => {
if let Expr::Lit(expr_lit) = &nv.value {
if let Lit::Str(s) = &expr_lit.lit {
provider_type = Some(s.value());
}
}
},
_ => return Err(syn::Error::new_spanned(attr, "Expected 'name' or 'provider_type'")),
}
}
Ok(CpiExtensionMeta {
name: name.unwrap_or_default(),
provider_type: provider_type.unwrap_or_else(|| "command".to_string()),
})
}
}
#[proc_macro_attribute]
pub fn cpi_action(attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let meta = parse_macro_input!(attr as CpiActionMeta);
let fn_name = &input_fn.sig.ident;
let fn_name_str = fn_name.to_string();
let vis = &input_fn.vis;
let attrs = &input_fn.attrs;
let block = &input_fn.block;
let output = &input_fn.sig.output;
let description = meta.description.clone();
let params = &input_fn.sig.inputs;
let param_names = meta.params.iter().map(|p| &p.name).collect::<Vec<_>>();
let param_descriptions = meta.params.iter().map(|p| &p.description).collect::<Vec<_>>();
let param_types = meta.params.iter().map(|p| &p.param_type).collect::<Vec<_>>();
let param_requireds = meta.params.iter().map(|p| p.required).collect::<Vec<_>>();
let param_defaults = meta.params.iter().map(|p| p.default_value.as_ref()).collect::<Vec<_>>();
let meta_fn_name = format_ident!("{}_metadata", fn_name);
let mut param_defs = Vec::new();
for (i, param) in meta.params.iter().enumerate() {
let name = ¶m.name;
let name_str = name.to_string();
let description = ¶m.description;
let param_type = ¶m.param_type;
let required = param.required;
let param_def = if let Some(default_val) = ¶m.default_value {
quote! {
param!(
#name_str,
#description,
ParamType::#param_type,
if #required { required } else { optional },
#default_val
)
}
} else {
quote! {
param!(
#name_str,
#description,
ParamType::#param_type,
if #required { required } else { optional }
)
}
};
param_defs.push(param_def);
}
let meta_fn = quote! {
fn #meta_fn_name() -> ActionDefinition {
ActionDefinition {
name: #fn_name_str.to_string(),
description: #description.to_string(),
parameters: vec![
#(#param_defs),*
],
}
}
};
let result = quote! {
#(#attrs)*
#[action]
#vis fn #fn_name(#params) #output #block
#meta_fn
};
result.into()
}
#[proc_macro_attribute]
pub fn cpi_extension(attr: TokenStream, item: TokenStream) -> TokenStream {
let input_struct = parse_macro_input!(item as ItemStruct);
let meta = parse_macro_input!(attr as CpiExtensionMeta);
let struct_name = &input_struct.ident;
let name = &meta.name;
let provider_type = &meta.provider_type;
let result = quote! {
#input_struct
impl CpiExtension for #struct_name {
fn name(&self) -> &str {
#name
}
fn provider_type(&self) -> &str {
#provider_type
}
fn list_actions(&self) -> Vec<String> {
vec![
]
}
fn get_action_definition(&self, action: &str) -> Option<ActionDefinition> {
match action {
_ => None,
}
}
fn execute_action(&self, action: &str, params: &std::collections::HashMap<String, serde_json::Value>) -> ActionResult {
match action {
_ => Err(format!("Action '{}' not found", action)),
}
}
}
};
result.into()
}