llm_toolkit_macros/
lib.rs1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{DeriveInput, Meta, parse_macro_input, punctuated::Punctuated};
4
5#[proc_macro_derive(ToPrompt, attributes(prompt))]
6pub fn to_prompt_derive(input: TokenStream) -> TokenStream {
7 let input = parse_macro_input!(input as DeriveInput);
8
9 let attr = input
10 .attrs
11 .iter()
12 .find(|attr| attr.path().is_ident("prompt"))
13 .expect("`#[derive(ToPrompt)]` requires a `#[prompt(...)]` attribute.");
14
15 let name_value = attr
17 .parse_args_with(Punctuated::<Meta, syn::Token![,]>::parse_terminated)
18 .expect("Failed to parse `prompt` attribute arguments")
19 .into_iter()
20 .find_map(|meta| match meta {
21 Meta::NameValue(nv) if nv.path.is_ident("template") => Some(nv),
22 _ => None,
23 })
24 .expect("`#[prompt(...)]` must contain `template = \"...\"`");
25
26 let template_str = if let syn::Expr::Lit(expr_lit) = name_value.value {
27 if let syn::Lit::Str(lit_str) = expr_lit.lit {
28 lit_str.value()
29 } else {
30 panic!("'template' attribute value must be a string literal.");
31 }
32 } else {
33 panic!("'template' attribute must have a literal value.");
34 };
35
36 let name = input.ident;
37 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
38
39 let expanded = quote! {
40 impl #impl_generics llm_toolkit::prompt::ToPrompt for #name #ty_generics #where_clause {
41 fn to_prompt(&self) -> String {
42 llm_toolkit::prompt::render_prompt(#template_str, self).unwrap_or_else(|e| {
43 format!("Failed to render prompt: {}", e)
44 })
45 }
46 }
47 };
48
49 TokenStream::from(expanded)
50}