| use proc_macro::TokenStream; |
| use quote::{ToTokens, quote}; |
| use syn::{DeriveInput, Expr, ExprLit, Lit, parse_macro_input}; |
|
|
| |
| extern crate proc_macro; |
|
|
| #[proc_macro_attribute] |
| pub fn tool_description_file(_attr: TokenStream, _item: TokenStream) -> TokenStream { |
| |
| |
| _item |
| } |
|
|
| #[proc_macro_derive(ToolDescription, attributes(tool_description_file))] |
| pub fn derive_description(input: TokenStream) -> TokenStream { |
| |
| let input = parse_macro_input!(input as DeriveInput); |
| let name = &input.ident; |
| let generics = &input.generics; |
|
|
| |
| let mut description_file = None; |
| for attr in &input.attrs { |
| if attr.path().is_ident("tool_description_file") |
| && let syn::Meta::NameValue(name_value) = &attr.meta |
| && let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) = &name_value.value |
| { |
| description_file = Some(lit_str.value()); |
| } |
| } |
|
|
| |
| let doc_string = if let Some(file_path) = description_file { |
| std::fs::read_to_string(&file_path) |
| .unwrap_or_else(|e| { |
| panic!( |
| "Failed to read tool description file '{}': {}", |
| file_path, e |
| ) |
| }) |
| .trim() |
| .to_string() |
| } else { |
| |
| let mut doc_lines = Vec::new(); |
| for attr in &input.attrs { |
| if attr.path().is_ident("doc") { |
| |
| let doc_string = attr.meta.to_token_stream().to_string(); |
| |
| let clean = doc_string.trim_start_matches("=").trim_matches('"').trim(); |
| if !clean.is_empty() { |
| doc_lines.push(clean.to_string()); |
| } |
| } |
| } |
|
|
| if doc_lines.is_empty() { |
| panic!("No doc comment found for {name}"); |
| } |
| doc_lines.join("\n") |
| }; |
|
|
| |
| let expanded = if generics.params.is_empty() { |
| quote! { |
| impl ToolDescription for #name { |
| fn description(&self) -> String { |
| #doc_string.into() |
| } |
| } |
| } |
| } else { |
| quote! { |
| impl #generics ToolDescription for #name #generics { |
| fn description(&self) -> String { |
| #doc_string.into() |
| } |
| } |
| } |
| }; |
|
|
| expanded.into() |
| } |
|
|