xtask_cmdwrap_macro/
lib.rs1use darling::{ast::NestedMeta, FromMeta};
2use itertools::Itertools;
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{
6 parse_macro_input, parse_quote, ExprMethodCall, Field, FnArg, Ident, ImplItemFn, ItemImpl,
7 ItemStruct,
8};
9
10#[derive(Debug, FromMeta)]
12struct CmdMeta {
13 #[darling(default)]
14 name: Option<String>,
15}
16
17fn default_prefix() -> String {
21 "--".to_string()
22}
23
24#[derive(Debug, FromMeta)]
26struct CmdOptMeta {
27 #[darling(default)]
28 no_val: bool,
29
30 #[darling(default)]
31 no_opt: bool,
32
33 #[darling(default)]
34 name: Option<String>,
35
36 #[darling(default=default_prefix)]
37 prefix: String,
38}
39
40fn constructor(ident: &Ident, program: String) -> ImplItemFn {
42 parse_quote! {
43 pub fn new() -> #ident {
44 #ident(std::process::Command::new(#program))
45 }
46 }
47}
48
49fn setter(field: &Field) -> ImplItemFn {
51 let ident = field.ident.as_ref().unwrap();
53 let ty = &(field.ty);
54 let doc_attr = field.attrs.iter().find(|a| a.path().is_ident("doc"));
55 let arg_attr = field.attrs.iter().find(|a| a.path().is_ident("arg"));
56
57 let cmd_opt;
59 let mut errors = proc_macro2::TokenStream::new();
60 if let Some(a) = arg_attr {
61 cmd_opt = CmdOptMeta::from_meta(&a.meta)
62 .map_err(|e| errors = e.write_errors())
63 .ok();
64 } else {
65 cmd_opt = None;
66 errors = quote! {compile_error!(concat!("Missing #[arg(...)] attribute on field: ", stringify!(#ident)))};
67 }
68 if !errors.is_empty() {
72 let dummy_ident = format_ident!("{}_err", ident);
73 return parse_quote! {
74 pub fn #dummy_ident() {
75 #errors
76 }
77 };
78 }
79
80 let cmd_opt = cmd_opt.unwrap();
82 let opt_prefix = cmd_opt.prefix;
83 let opt_name: String = match cmd_opt.name {
84 Some(s) => s,
85 None => ident.to_string().to_lowercase(),
86 };
87 let opt = format!("{}{}", opt_prefix, opt_name);
88 let param_val: Option<FnArg> = match cmd_opt.no_val {
89 true => None,
90 false => Some(parse_quote! {val: #ty}),
91 };
92 let arg_val: Option<ExprMethodCall> = match cmd_opt.no_val {
93 true => None,
94 false => Some(parse_quote! {self.0.arg(val)}),
95 };
96 let arg_opt: Option<ExprMethodCall> = match cmd_opt.no_opt {
97 true => None,
98 false => Some(parse_quote! {self.0.arg(#opt)}),
99 };
100
101 parse_quote! {
103 #doc_attr
104 pub fn #ident(&mut self, #param_val) -> &mut Self {
105 #arg_opt;
106 #arg_val;
107 self
108 }
109 }
110}
111
112fn string_repr() -> ImplItemFn {
114 parse_quote! {
115 pub fn string_repr(&self) -> String {
116 let program = self.0.get_program().to_string_lossy();
117 let args = self.0
118 .get_args()
119 .map(|a| a.to_string_lossy())
120 .fold(String::new(), |s, a| s + " " + &a);
121 format!("{}{}", program, args)
122 }
123 }
124}
125
126fn get_inner() -> ImplItemFn {
127 parse_quote! {
128 pub fn cmd(self) -> std::process::Command {
129 self.0
130 }
131 }
132}
133
134fn into_std_command(ident: &Ident) -> ItemImpl {
136 parse_quote! {
137 impl Into<std::process::Command> for #ident {
138 fn into(self) -> std::process::Command {
139 self.cmd()
140 }
141 }
142 }
143}
144
145#[proc_macro_attribute]
146pub fn cmd(attr: TokenStream, input: TokenStream) -> TokenStream {
147 let attr = match NestedMeta::parse_meta_list(attr.into()) {
149 Ok(v) => v,
150 Err(e) => return TokenStream::from(e.into_compile_error()),
151 };
152 let attr = match CmdMeta::from_list(&attr) {
153 Ok(v) => v,
154 Err(e) => return TokenStream::from(e.write_errors()),
155 };
156
157 let item = parse_macro_input!(input as ItemStruct);
159
160 let ident = &(item.ident);
162 let name = attr.name.unwrap_or(ident.to_string().to_lowercase());
163 let vis = &(item.vis);
164
165 let constructor = constructor(ident, name);
167
168 let setters = item.fields.iter().map(|f| setter(f)).collect_vec();
170
171 let string_repr = string_repr();
173
174 let inner = get_inner();
176
177 let into_std_command = into_std_command(ident);
179
180 let expanded = quote! {
182 #vis struct #ident(std::process::Command);
183 impl #ident {
184 #constructor
185 #inner
186 #(#setters)*
187 #string_repr
188 }
189 #into_std_command
190 };
191
192 TokenStream::from(expanded)
193}