1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 parse_macro_input, AngleBracketedGenericArguments, DeriveInput, FnArg, GenericArgument, Ident,
5 ImplItem, ItemImpl, Pat, PathArguments, Type, TypePath,
6};
7
8#[proc_macro_derive(Command)]
9pub fn command_trait(input: TokenStream) -> TokenStream {
10 let ast = parse_macro_input!(input as DeriveInput);
11
12 let name = &ast.ident;
13 quote! {
14 impl CommandDescriptor for #name {}
15 }
16 .into()
17}
18
19#[proc_macro_attribute]
20pub fn command_extension(_attr: TokenStream, item: TokenStream) -> TokenStream {
21 let input = parse_macro_input!(item as ItemImpl);
22
23 let target_type = &*input.self_ty;
24
25 let (_, generic_arg_type) = match target_type {
27 Type::Path(TypePath { path, .. }) => {
28 let first_segment = path.segments.first().expect("Expected a path segment");
29 if first_segment.ident != "Command" {
30 return syn::Error::new_spanned(
31 target_type,
32 "command_extension only works on impl blocks for Command<T>",
33 )
34 .to_compile_error()
35 .into();
36 }
37 match &first_segment.arguments {
39 PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) => {
40 if args.len() != 1 {
41 return syn::Error::new_spanned(
42 &first_segment.arguments,
43 "Expected exactly one generic argument",
44 )
45 .to_compile_error()
46 .into();
47 }
48 let generic_arg = args.first().unwrap();
49 match generic_arg {
50 GenericArgument::Type(ty) => (first_segment.ident.clone(), ty.clone()),
51 _ => {
52 return syn::Error::new_spanned(
53 generic_arg,
54 "Expected a type as the generic argument",
55 )
56 .to_compile_error()
57 .into();
58 }
59 }
60 }
61 _ => {
62 return syn::Error::new_spanned(
63 &first_segment.arguments,
64 "Expected angle bracketed generic arguments",
65 )
66 .to_compile_error()
67 .into();
68 }
69 }
70 }
71 _ => {
72 return syn::Error::new_spanned(
73 target_type,
74 "command_extension can only be applied to impl blocks for Command<T>",
75 )
76 .to_compile_error()
77 .into();
78 }
79 };
80
81 let inner_type_ident = match generic_arg_type {
82 Type::Path(TypePath { ref path, .. }) => path
83 .segments
84 .last()
85 .expect("Expected at least one segment in the generic type")
86 .ident
87 .clone(),
88 _ => {
89 return syn::Error::new_spanned(
90 generic_arg_type,
91 "Expected a simple type for the generic parameter",
92 )
93 .to_compile_error()
94 .into();
95 }
96 };
97
98 let trait_name_str = format!("{}Ext", inner_type_ident);
101 let trait_ident = Ident::new(&trait_name_str, proc_macro2::Span::call_site());
102 let builder_trait_name_str = format!("{}BuilderExt", inner_type_ident);
104 let builder_trait_ident = Ident::new(&builder_trait_name_str, proc_macro2::Span::call_site());
105
106 let mut cmd_trait_methods = Vec::new();
108 let mut cmd_impl_methods = Vec::new();
109
110 let mut builder_trait_methods = Vec::new();
111 let mut builder_impl_methods = Vec::new();
112
113 for item in input.items.iter() {
115 if let ImplItem::Fn(method) = item {
116 let sig = &method.sig;
117 let attrs = &method.attrs;
118
119 let trait_method = quote! {
121 #(#attrs)*
122 #sig;
123 };
124 cmd_trait_methods.push(trait_method);
125
126 let block = &method.block;
127 let impl_method = quote! {
128 #(#attrs)*
129 #sig #block
130 };
131 cmd_impl_methods.push(impl_method);
132
133 let method_name = sig.ident.to_string();
135 if let Some(stripped) = method_name.strip_prefix("set_") {
136 let builder_method_ident = Ident::new(stripped, sig.ident.span());
138
139 let mut builder_inputs = Vec::new();
144 let mut arg_idents = Vec::new();
145 for input in sig.inputs.iter().skip(1) {
147 builder_inputs.push(input);
148 if let FnArg::Typed(pat_type) = input {
150 if let Pat::Ident(pat_ident) = *pat_type.pat.clone() {
152 arg_idents.push(pat_ident.ident);
153 }
154 }
155 }
156
157 let builder_sig = quote! {
160 fn #builder_method_ident(self, #(#builder_inputs),* ) -> Self;
161 };
162
163 builder_trait_methods.push(builder_sig);
164
165 let setter_ident = sig.ident.clone();
167
168 let builder_impl = quote! {
170 fn #builder_method_ident(mut self, #(#builder_inputs),* ) -> Self {
171 self.command.#setter_ident( #(#arg_idents),* );
172 self
173 }
174 };
175
176 builder_impl_methods.push(builder_impl);
177 }
178 }
179 }
180
181 let cmd_trait_def = quote! {
183 pub trait #trait_ident {
184 #(#cmd_trait_methods)*
185 }
186 };
187
188 let cmd_impl_block = quote! {
189 impl #trait_ident for #target_type {
190 #(#cmd_impl_methods)*
191 }
192 };
193
194 let builder_trait_def = quote! {
196 pub trait #builder_trait_ident {
197 #(#builder_trait_methods)*
198 }
199 };
200
201 let builder_target = quote! { CommandBuilder<#generic_arg_type> };
203
204 let builder_impl_block = quote! {
205 impl #builder_trait_ident for #builder_target {
206 #(#builder_impl_methods)*
207 }
208 };
209
210 let output = quote! {
214 #cmd_trait_def
215 #cmd_impl_block
216
217 #builder_trait_def
218 #builder_impl_block
219 };
220
221 output.into()
222}