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