1extern crate syn;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::ext::IdentExt;
6use syn::parse::Parse;
7use syn::parse::ParseStream;
8use syn::punctuated::Punctuated;
9use syn::token::Comma;
10use syn::{parse_macro_input, Expr};
11
12#[derive(Debug)]
16struct GetoptArgs {
17 iter: Expr,
18 parsers: Punctuated<Expr, Comma>,
19}
20
21impl Parse for GetoptArgs {
22 fn parse(input: ParseStream) -> syn::Result<Self> {
23 let mut parsers = Punctuated::new();
24 let iter: Expr = input.parse()?;
25 let _: Comma = input.parse()?;
26 let first: Expr = input.parse()?;
27
28 parsers.push(first);
29 while input.peek(Comma) {
30 parsers.push_punct(input.parse()?);
31 if input.is_empty() {
32 break;
33 } else {
34 parsers.push(input.parse()?);
35 }
36 }
37
38 Ok(Self { iter, parsers })
39 }
40}
41
42#[proc_macro]
43pub fn getoptd(input: TokenStream) -> TokenStream {
44 let getopt_args = parse_macro_input!(input as GetoptArgs);
45 let iterator = &getopt_args.iter;
46 let iterator = quote! { #iterator };
47
48 let mut getopt_init = quote! {
49 let mut parsers = vec![];
50 };
51
52 getopt_init.extend(getopt_args.parsers.iter().map(|p| match p {
53 Expr::Path(path) => {
54 quote! {
55 parsers.push(&mut #path);
56 }
57 }
58 Expr::Reference(reference) => {
59 if reference.mutability.is_some() {
60 quote! {
61 quote! {
62 parsers.push(#reference);
63 }
64 }
65 } else {
66 syn::Error::new_spanned(reference, "need an instance or a mutable reference")
67 .to_compile_error()
68 }
69 }
70 expr => {
71 quote! {
72 parsers.push(#expr);
73 }
74 }
75 }));
76
77 let ret = quote! {{
78 #getopt_init
79 getopt_dynparser(#iterator, parsers)
80 }};
81 ret.into()
82}
83
84#[proc_macro]
85pub fn getopt(input: TokenStream) -> TokenStream {
86 let getopt_args = parse_macro_input!(input as GetoptArgs);
87 let iterator = &getopt_args.iter;
88 let iterator = quote! { #iterator };
89
90 let mut getopt_init = quote! {
91 let mut parsers = vec![];
92 };
93
94 getopt_init.extend(getopt_args.parsers.iter().map(|p| match p {
95 Expr::Path(path) => {
96 quote! {
97 parsers.push(&mut #path);
98 }
99 }
100 Expr::Reference(reference) => {
101 if reference.mutability.is_some() {
102 quote! {
103 parsers.push(#reference);
104 }
105 } else {
106 syn::Error::new_spanned(reference, "need an instance or a mutable reference")
107 .to_compile_error()
108 }
109 }
110 expr => {
111 quote! {
112 parsers.push(#expr);
113 }
114 }
115 }));
116
117 let ret = quote! {{
118 #getopt_init
119 getopt_parser(#iterator, parsers)
120 }};
121 ret.into()
122}
123
124#[derive(Debug)]
128struct HelpArgs {
129 set: Expr,
130 cmds: Punctuated<Expr, Comma>,
131}
132
133impl Parse for HelpArgs {
134 fn parse(input: ParseStream) -> syn::Result<Self> {
135 let mut cmds = Punctuated::new();
136 let set: Expr = input.parse()?;
137
138 if input.peek(Comma) && input.parse::<Comma>().is_ok() {
139 while !input.is_empty() {
140 cmds.push(input.parse()?);
141 if input.peek(Comma) {
142 cmds.push_punct(input.parse()?);
143 } else {
144 break;
145 }
146 }
147 }
148
149 Ok(Self { set, cmds })
150 }
151}
152
153#[proc_macro]
154pub fn getopt_help(input: TokenStream) -> TokenStream {
155 let help_args = parse_macro_input!(input as HelpArgs);
156 let set = help_args.set;
157 let cmds = help_args.cmds;
158
159 let mut help_code = quote! {};
160
161 if cmds.is_empty() {
162 help_code.extend(quote! {{
163 let mut help = AppHelp::<std::io::Stdout, DefaultFormat>::default();
164 help.set_name(gstr(env!("CARGO_PKG_NAME")));
165 let global = help.store.get_global_mut();
166 for opt in #set.opt_iter() {
167 if opt.match_style(aopt::opt::Style::Pos) {
168 global.add_pos(PosStore::new(
169 opt.get_name(),
170 opt.get_hint(),
171 opt.get_help(),
172 opt.get_index().unwrap().to_string().into(),
173 opt.get_optional(),
174 ));
175 } else if opt.match_style(aopt::opt::Style::Argument)
176 || opt.match_style(aopt::opt::Style::Boolean)
177 || opt.match_style(aopt::opt::Style::Multiple)
178 {
179 global.add_opt(OptStore::new(
180 opt.get_name(),
181 opt.get_hint(),
182 opt.get_help(),
183 opt.get_type_name(),
184 opt.get_optional(),
185 ));
186 }
187 }
188 global.set_header(gstr(env!("CARGO_PKG_DESCRIPTION")));
189 global.set_footer(gstr(&format!(
190 "Create by {} v{}",
191 env!("CARGO_PKG_AUTHORS"),
192 env!("CARGO_PKG_VERSION")
193 )));
194 help
195 }});
196 } else {
197 help_code.extend(quote! {
198 let mut cmds_arg: Vec<&str> = vec![];
199 });
200 help_code.extend(cmds.iter().map(|p| {
201 quote! {
202 cmds_arg.push(#p);
203 }
204 }));
205 help_code.extend(quote! {{
206 let mut help = AppHelp::<std::io::Stdout, DefaultFormat>::default();
207 help.set_name(gstr(env!("CARGO_PKG_NAME")));
208 let version = gstr(&format!(
209 "Create by {} v{}",
210 env!("CARGO_PKG_AUTHORS"),
211 env!("CARGO_PKG_VERSION")
212 ));
213 help.store.new_sec(gstr("default")).set_help(gstr("Commands:")).commit();
214 let global = help.store.get_global_mut();
215 global.set_header(gstr(env!("CARGO_PKG_DESCRIPTION")));
216 global.set_footer(version.clone());
217 for cmd_name in cmds_arg {
218 if let Ok(Some(opt)) = #set.find(cmd_name) {
219 let mut search_cmd = help.store.new_cmd(gstr(cmd_name));
220 search_cmd
221 .set_footer(version.clone())
222 .set_hint(opt.get_hint())
223 .set_help(opt.get_help());
224 for opt in #set.opt_iter() {
225 if opt.match_style(aopt::opt::Style::Pos) {
226 search_cmd.add_pos(PosStore::new(
227 opt.get_name(),
228 opt.get_hint(),
229 opt.get_help(),
230 opt.get_index().unwrap().to_string().into(),
231 opt.get_optional(),
232 ));
233 } else if opt.match_style(aopt::opt::Style::Argument)
234 || opt.match_style(aopt::opt::Style::Boolean)
235 || opt.match_style(aopt::opt::Style::Multiple)
236 {
237 search_cmd.add_opt(OptStore::new(
238 opt.get_name(),
239 opt.get_hint(),
240 opt.get_help(),
241 opt.get_type_name(),
242 opt.get_optional(),
243 ));
244 }
245 }
246 search_cmd.commit();
247 help.store.attach_cmd(gstr("default"), gstr(cmd_name));
248 }
249 }
250 help
251 }});
252 }
253 let ret = quote! {{ #help_code }};
254
255 ret.into()
256}
257
258#[derive(Debug)]
259struct ParameterOrNormalArgs {
260 name: Option<syn::Ident>,
261 value: Expr,
262}
263
264impl Parse for ParameterOrNormalArgs {
265 fn parse(input: ParseStream) -> syn::Result<Self> {
266 let mut name = None;
267 let value;
268
269 if input.peek2(syn::token::Eq) {
270 if input.peek(syn::Ident::peek_any) {
271 name = Some(input.parse()?);
272 input.parse::<syn::Token![=]>()?;
273 value = input.parse()?;
274 } else if input.peek(syn::Token![default]) {
275 name = Some(syn::Ident::new("default", proc_macro2::Span::call_site()));
276 input.parse::<syn::Token![=]>()?;
277 value = input.parse()?;
278 } else {
279 value = input.parse()?;
280 }
281 } else {
282 value = input.parse()?;
283 }
284 Ok(Self { name, value })
285 }
286}
287
288#[derive(Debug)]
293struct CreateArgs {
294 parser: Expr,
295 init: Expr,
296 parameter_args: Punctuated<ParameterOrNormalArgs, Comma>,
297}
298
299impl Parse for CreateArgs {
300 fn parse(input: ParseStream) -> syn::Result<Self> {
301 let mut parameter_args = Punctuated::new();
302 let parser: Expr = input.parse()?;
303 let _: Comma = input.parse()?;
304 let init: Expr = input.parse()?;
305
306 if input.peek(Comma) && input.parse::<Comma>().is_ok() {
307 while !input.is_empty() {
308 let arg: ParameterOrNormalArgs = input.parse()?;
309
310 parameter_args.push(arg);
311 if input.peek(Comma) {
312 parameter_args.push_punct(input.parse()?);
313 } else {
314 break;
315 }
316 }
317 }
318
319 Ok(Self {
320 parser,
321 init,
322 parameter_args,
323 })
324 }
325}
326
327#[proc_macro]
328pub fn getopt_add(input: TokenStream) -> TokenStream {
329 let getopt_args = parse_macro_input!(input as CreateArgs);
330 let init = getopt_args.init;
331 let parser = getopt_args.parser;
332 let mut found_help = false;
333 let mut callback = None;
334 let mut output = quote! {
335 let init_string = #init.into();
336 let mut create_info = CreateInfo::parse(init_string, #parser.get_prefix());
337 };
338
339 output.extend(getopt_args.parameter_args.iter().map(|v| {
340 let name = &v.name;
341 let expr = &v.value;
342
343 if let Some(name) = name {
344 match name.to_string().as_str() {
345 "help" => {
346 quote! {{
347 let value = #expr.into();
348 create_info = create_info.and_then(|mut ci| { ci.set_help(value); Ok(ci) });
349 }}
350 }
351 "name" => {
352 quote! {{
353 let value = #expr.into();
354 create_info = create_info.and_then(|mut ci| { ci.set_name(value); Ok(ci) });
355 }}
356 }
357 "prefix" => {
358 quote! {{
359 let value = #expr.into();
360 create_info = create_info.and_then(|mut ci| { ci.set_prefix(value); Ok(ci) });
361 }}
362 }
363 "index" => {
364 quote! {{
365 let value = #expr.into();
366 create_info = create_info.and_then(|mut ci| { ci.set_index(value); Ok(ci) });
367 }}
368 }
369 "default" => {
370 quote! {{
371 let value = #expr;
372 create_info = create_info.and_then(|mut ci| { ci.set_default_value(value); Ok(ci) });
373 }}
374 }
375 "hint" => {
376 quote! {{
377 let value = #expr.into();
378 create_info = create_info.and_then(|mut ci| { ci.set_hint(value); Ok(ci) });
379 }}
380 }
381 "alias" => {
382 quote! {{
383 let value = #expr.into();
384 create_info = create_info.and_then(|mut ci| { ci.add_alias(value)?; Ok(ci) });
385 }}
386 }
387 "callback" => {
388 callback = Some(expr.clone());
389 quote! {}
390 }
391 _ => syn::Error::new_spanned(name, "Not support option field name").to_compile_error(),
392 }
393 }
394 else if !found_help {
395 found_help = true;
396 quote! {{
397 let value = #expr.into();
398 create_info = create_info.and_then(|mut ci| { ci.set_help(value); Ok(ci) });
399 }}
400 }
401 else if callback.is_none() {
402 callback = Some(expr.clone());
403 quote! { }
404 }
405 else {
406 syn::Error::new_spanned(syn::Ident::new("default", proc_macro2::Span::call_site()), "Not support more than three position arguments").to_compile_error()
407 }
408 }));
409
410 output.extend(quote! {
411 let uid = create_info.and_then(|mut ci| #parser.add_opt_ci(ci));
412 });
413
414 if let Some(callback) = callback {
415 output.extend(quote! {
416 let uid = uid.and_then(|uid| {
417 #parser.add_callback(uid, #callback);
418 Ok(uid)
419 });
420 });
421 }
422
423 let ret = quote! {{
424 #output
425 uid
426 }};
427
428 ret.into()
429}