auto_args_derive/
lib.rs

1// Copyright 2018 David Roundy <roundyd@physics.oregonstate.edu>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! This crate is custom derive for `AutoArgs`. It should not be used
10//! directly.
11
12#![recursion_limit="256"]
13
14extern crate proc_macro;
15extern crate syn;
16#[macro_use]
17extern crate quote;
18extern crate proc_macro2;
19
20use syn::*;
21
22fn get_doc_comment(attrs: &[syn::Attribute]) -> String {
23    let mut doc_comments: Vec<_> = attrs
24        .iter()
25        .filter_map(|attr| {
26            let path = &attr.path;
27            if quote!(#path).to_string() == "doc" {
28                attr.interpret_meta()
29            } else {
30                None
31            }
32        })
33        .filter_map(|attr| {
34            use Lit::*;
35            use Meta::*;
36            if let NameValue(MetaNameValue {ident, lit: Str(s), ..}) = attr {
37                if ident != "doc" {
38                    return None;
39                }
40                let value = s.value();
41                let text = value
42                    .trim_start_matches("//!")
43                    .trim_start_matches("///")
44                    .trim_start_matches("/*!")
45                    .trim_start_matches("/**")
46                    .trim_end_matches("*/")
47                    .trim();
48                if text.is_empty() {
49                    Some("\n\n".to_string())
50                } else {
51                    Some(text.to_string())
52                }
53            } else {
54                None
55            }
56        })
57        .collect();
58    if doc_comments.len() > 0 {
59        doc_comments.pop().unwrap_or("".to_string())
60    } else {
61        "".to_string()
62    }
63}
64
65fn return_with_fields(f: syn::Fields,
66                      name: proc_macro2::TokenStream,
67                      am_enum_variant: bool) -> proc_macro2::TokenStream {
68    let join_prefix = create_join_prefix();
69    match f {
70        syn::Fields::Named(ref fields) => {
71            let f: Vec<_> = fields.named.clone().into_iter().collect();
72            let names = f.iter().map(|x| snake_case_to_kebab(&x.ident.clone().unwrap().to_string()));
73            let types = f.iter().map(|x| x.ty.clone());
74            let types2 = types.clone();
75            let idents = f.iter().map(|x| x.ident.clone().unwrap());
76            let check_main_flag = if am_enum_variant {
77                quote!{
78                    if #( <#types2 as auto_args::AutoArgs>::REQUIRES_INPUT ||)* false {
79                        // Nothing special to do, something below requires input.
80                    } else if !bool::parse_internal(&_prefix, args)? {
81                        return Err(auto_args::Error::MissingOption(_prefix.clone()));
82                    }
83                }
84            } else {
85                quote!{}
86            };
87            quote! {
88                #check_main_flag
89                let join_prefix = #join_prefix;
90                return Ok( #name {
91                    #( #idents:
92                        <#types as auto_args::AutoArgs>::parse_internal(&join_prefix(#names),
93                                                                        args)?,  )*
94                });
95            }
96        },
97        syn::Fields::Unit => {
98            quote!{
99                if bool::parse_internal(&_prefix, args)? {
100                    return Ok( #name );
101                } else {
102                    return Err(auto_args::Error::MissingOption(_prefix.clone()));
103                }
104            }
105        },
106        syn::Fields::Unnamed(ref unnamed) if unnamed.unnamed.len() == 1 => {
107            let f = unnamed.unnamed.iter().next().expect("we should have one field");
108            let mytype = f.ty.clone();
109            quote!{
110                if let Ok(x) = <#mytype as auto_args::AutoArgs>::parse_internal(&_prefix, args) {
111                    return Ok(#name(x));
112                }
113            }
114        },
115        _ => {
116            panic!("AutoArgs only supports named fields so far!")
117        },
118    }
119}
120
121fn usage_with_fields(f: syn::Fields,
122                     _name: proc_macro2::TokenStream,
123                     am_enum_variant: bool) -> proc_macro2::TokenStream {
124    let join_prefix = create_join_prefix();
125    match f {
126        syn::Fields::Named(ref fields) => {
127            let f: Vec<_> = fields.named.clone().into_iter().collect();
128            let names = f.iter().map(|x| snake_case_to_kebab(&x.ident.clone().unwrap().to_string()));
129            let types = f.iter().map(|x| x.ty.clone());
130            let types2 = types.clone();
131            let check_main_flag = if am_enum_variant {
132                quote!{
133                    if #( <#types2 as auto_args::AutoArgs>::REQUIRES_INPUT ||)* false {
134                        // Nothing special to do, something below requires input.
135                    } else {
136                        doc.push_str(&format!("{} ", _prefix));
137                    }
138                }
139            } else {
140                quote!{}
141            };
142            quote! {
143                let mut doc = String::new();
144                #check_main_flag
145                let join_prefix = #join_prefix;
146                #( doc.push_str(
147                    &format!(" {}",
148                             <#types as auto_args::AutoArgs>::tiny_help_message(&join_prefix(#names))));
149                )*
150                doc
151            }
152        },
153        syn::Fields::Unit => {
154            quote!( _prefix.clone() )
155        },
156        syn::Fields::Unnamed(ref unnamed) if unnamed.unnamed.len() == 1 => {
157            let f = unnamed.unnamed.iter().next().expect("we should have one field");
158            let mytype = f.ty.clone();
159            quote!{
160                <#mytype as auto_args::AutoArgs>::tiny_help_message(&_prefix)
161            }
162        },
163        _ => {
164            panic!("AutoArgs only supports named fields so far!")
165        },
166    }
167}
168
169
170fn help_with_fields(f: syn::Fields,
171                    _name: proc_macro2::TokenStream,
172                    am_enum_variant: bool) -> proc_macro2::TokenStream {
173    let join_prefix = create_join_prefix();
174    match f {
175        syn::Fields::Named(ref fields) => {
176            let f: Vec<_> = fields.named.clone().into_iter().collect();
177            let docs: Vec<_> = f.iter().map(|x| get_doc_comment(&x.attrs)).collect();
178            let names = f.iter().map(|x| snake_case_to_kebab(&x.ident.clone().unwrap().to_string()));
179            let types = f.iter().map(|x| x.ty.clone());
180            let types2 = types.clone();
181            let check_main_flag = if am_enum_variant {
182                quote!{
183                    if #( <#types2 as auto_args::AutoArgs>::REQUIRES_INPUT ||)* false {
184                        // Nothing special to do, something below requires input.
185                    } else {
186                        doc.push_str(&format!("\t{}\t{}\n", _prefix, variant_doc));
187                    }
188                }
189            } else {
190                quote!{}
191            };
192            quote! {
193                let mut doc = String::new();
194                #check_main_flag
195                let join_prefix = #join_prefix;
196                #( doc.push_str(
197                    &<#types as auto_args::AutoArgs>::help_message(&join_prefix(#names),
198                                                                   #docs));
199                   if !doc.ends_with("\n") {
200                       doc.push('\n');
201                   }
202                )*
203                doc
204            }
205        },
206        syn::Fields::Unit => {
207            quote!( format!("\t{}\t{}\n", _prefix, variant_doc) )
208        },
209        syn::Fields::Unnamed(ref unnamed) if unnamed.unnamed.len() == 1 => {
210            let f = unnamed.unnamed.iter().next().expect("we should have one field");
211            let mytype = f.ty.clone();
212            quote!{
213                <#mytype as auto_args::AutoArgs>::help_message(&_prefix, &variant_doc)
214            }
215        },
216        _ => {
217            panic!("AutoArgs only supports named fields so far!")
218        },
219    }
220}
221
222fn create_join_prefix() -> proc_macro2::TokenStream {
223    quote!{
224        move |name: &str| -> String {
225            if name.len() == 0 {
226                let mut x = _prefix.to_string();
227                // x.pop();
228                x
229            } else if _prefix.chars().last() == Some('-') {
230                format!("{}{}", _prefix, name)
231            } else {
232                format!("{}-{}", _prefix, name)
233            }
234        }
235    }
236}
237fn create_find_prefix() -> proc_macro2::TokenStream {
238    quote!{
239        match key.chars().next() {
240            None | Some('_') => "--".to_string(),
241            _ => match key.chars().last() {
242                Some('-') => key.to_string(),
243                _ => format!("{}-", key),
244            }
245        }
246    }
247}
248
249/// Generates the `AutoArgs` impl.
250#[proc_macro_derive(AutoArgs)]
251pub fn auto_args(raw_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
252    use syn::Data::*;
253    let input: DeriveInput = syn::parse(raw_input.clone()).unwrap();
254
255    let name = &input.ident;
256    let generics = &input.generics;
257    let find_prefix = create_find_prefix();
258    let myimpl = match input.data {
259        Struct(DataStruct {
260            fields: syn::Fields::Named(ref fields),
261            ..
262        }) => {
263            let f: Vec<_> = fields.named.clone().into_iter().collect();
264            let types3 = f.iter().rev().map(|x| x.ty.clone());
265            let return_struct = return_with_fields(syn::Fields::Named(fields.clone()),
266                                                   quote!(#name), false);
267            let usage_struct = usage_with_fields(syn::Fields::Named(fields.clone()),
268                                                 quote!(#name), false);
269            let help_struct = help_with_fields(syn::Fields::Named(fields.clone()),
270                                               quote!(#name), false);
271            quote!{
272                const REQUIRES_INPUT: bool = #(
273                    <#types3 as auto_args::AutoArgs>::REQUIRES_INPUT ||)* false;
274                fn parse_internal(key: &str, args: &mut Vec<std::ffi::OsString>)
275                                  -> Result<Self, auto_args::Error> {
276                    let _prefix = #find_prefix;
277                    #return_struct
278                }
279                fn tiny_help_message(key: &str) -> String {
280                    let _prefix = #find_prefix;
281                    #usage_struct
282                }
283                fn help_message(key: &str, _doc: &str) -> String {
284                    let _prefix = #find_prefix;
285                    #help_struct
286                }
287            }
288        },
289        Struct(DataStruct {
290            fields: syn::Fields::Unit,
291            ..
292        }) => {
293            quote!{
294                const REQUIRES_INPUT: bool = false;
295                fn parse_internal(key: &str, args: &mut Vec<std::ffi::OsString>)
296                                  -> Result<Self, auto_args::Error> {
297                    Ok( #name )
298                }
299                fn tiny_help_message(key: &str) -> String {
300                    "".to_string()
301                }
302            }
303        },
304        Struct(DataStruct {
305            fields: syn::Fields::Unnamed(ref unnamed),
306            ..
307        }) => {
308            if unnamed.unnamed.len() != 1 {
309                panic!("AutoArgs does not handle tuple structs with more than one field");
310            }
311            let f = unnamed.unnamed.iter().next().expect("There should be a field here!");
312            let mytype = f.ty.clone();
313            quote!{
314                const REQUIRES_INPUT: bool =
315                    <#mytype as auto_args::AutoArgs>::REQUIRES_INPUT;
316                fn parse_internal(key: &str, args: &mut Vec<std::ffi::OsString>)
317                                  -> Result<Self, auto_args::Error> {
318                    <#mytype as auto_args::AutoArgs>::parse_internal(key, args)
319                        .map(|x| #name(x))
320                }
321                fn tiny_help_message(key: &str) -> String {
322                    "fixme unnamed".to_string()
323                }
324            }
325        },
326        Enum(ref e) => {
327            let v: Vec<_> = e.variants.iter().collect();
328            let vnames: Vec<_> = e.variants.iter().map(|v| camel_case_to_kebab(&v.ident.to_string())).collect();
329            let variant_docs: Vec<_> = e.variants.iter().map(|v| get_doc_comment(&v.attrs)).collect();
330            let vnames = &vnames;
331            // println!("variant names are {:?}", names);
332            let return_enum = v.iter().map(|v| {
333                let variant_name = v.ident.clone();
334                return_with_fields(v.fields.clone(), quote!(#name::#variant_name), true)
335            });
336            let helps = v.iter().map(|v| {
337                let variant_name = v.ident.clone();
338                help_with_fields(v.fields.clone(),
339                                 quote!(#name::#variant_name), true)
340            });
341            let usages = v.iter().map(|v| {
342                let variant_name = v.ident.clone();
343                usage_with_fields(v.fields.clone(),
344                                  quote!(#name::#variant_name), true)
345            });
346            let s = quote! {
347                const REQUIRES_INPUT: bool = true;
348                fn parse_internal(key: &str, args: &mut Vec<std::ffi::OsString>)
349                                  -> Result<Self, auto_args::Error>
350                {
351                    let _prefix = match key.chars().next() {
352                        None | Some('_') => "--".to_string(),
353                        _ => match key.chars().last() {
354                            Some('-') => key.to_string(),
355                            _ => format!("{}-", key),
356                        }
357                    };
358                    let orig_args = args;
359                    let mut most_used = 0;
360                    let mut best_err = Err(auto_args::Error::MissingOption("a missing thingy".to_string()));
361                    #(
362                        {
363                            let mut args = orig_args.clone();
364                            let args = &mut args;
365                            let variant = #vnames;
366                            let _prefix = format!("{}{}", _prefix, variant);
367                            let mut closure = || -> Result<_, auto_args::Error> {
368                                #return_enum
369                                Err(auto_args::Error::MissingOption(_prefix))
370                            };
371                            match closure() {
372                                Ok(v) => {
373                                    *orig_args = args.clone();
374                                    return Ok(v);
375                                }
376                                Err(e) => {
377                                    let args_used = orig_args.len() - args.len();
378                                    if args_used >= most_used {
379                                        most_used = args_used;
380                                        best_err = Err(e);
381                                    }
382                                }
383                            }
384                        }
385
386                    )*
387                    best_err
388                }
389                fn help_message(key: &str, doc: &str) -> String {
390                    let _prefix = match key.chars().next() {
391                        None | Some('_') => "--".to_string(),
392                        _ => match key.chars().last() {
393                            Some('-') => key.to_string(),
394                            _ => format!("{}-", key),
395                        }
396                    };
397                    let mut doc = String::new();
398                    doc.push_str("\tEITHER\t\n");
399                    #(
400                        {
401                            let variant = #vnames;
402                            let _prefix = format!("{}{}", _prefix, variant);
403                            let variant_doc = #variant_docs;
404                            doc.push_str(&{ #helps });
405                            if !doc.ends_with("\n") {
406                                doc.push_str("\n");
407                            }
408                            doc.push_str("\tOR\t\n");
409                        }
410
411                    )*
412                    for _ in 0.."\tOR\t\n".len() {
413                        doc.pop();
414                    }
415                    doc.push_str("\t\t");
416                    doc
417                }
418                fn tiny_help_message(key: &str) -> String {
419                    let _prefix = match key.chars().next() {
420                        None | Some('_') => "--".to_string(),
421                        _ => match key.chars().last() {
422                            Some('-') => key.to_string(),
423                            _ => format!("{}-", key),
424                        }
425                    };
426                    let mut doc = String::new();
427                    doc.push_str("( ");
428                    #(
429                        {
430                            let variant = #vnames;
431                            let _prefix = format!("{}{}", _prefix, variant);
432                            doc.push_str(&{ #usages });
433                            doc.push_str(" | ");
434                        }
435
436                    )*
437                    for _ in 0..3 {
438                        doc.pop();
439                    }
440                    doc.push_str(" )");
441                    doc
442                }
443            };
444            s
445        },
446        _ => panic!("AutoArgs only supports non-tuple structs"),
447    };
448
449    let generic_types = input.generics.type_params();
450    let bounds = quote!{
451        <#(#generic_types: auto_args::AutoArgs),*>
452    };
453
454    let tokens2: proc_macro2::TokenStream = quote!{
455        #[allow(unreachable_code)]
456        impl#bounds auto_args::AutoArgs for #name#generics {
457            #myimpl
458        }
459    };
460    // println!("\n\n{}", tokens2);
461    tokens2.into()
462}
463
464fn camel_case_to_kebab(name: &str) -> String {
465    if name.chars().next() == Some('_') {
466        "".to_string()
467    } else if name.contains('_') {
468        let mut out = name.to_string().replace("_", "-");
469        if out.chars().last() == Some('-') {
470            out.pop();
471        }
472        out
473    } else {
474        let mut out = String::new();
475        let mut am_on_cap = true;
476        for c in name.chars() {
477            if !am_on_cap && c.is_ascii_uppercase() {
478                out.push('-');
479            }
480            am_on_cap = c.is_ascii_uppercase();
481            out.push(c.to_ascii_lowercase());
482        }
483        out
484    }
485}
486
487fn snake_case_to_kebab(name: &str) -> String {
488    if name.chars().next() == Some('_') {
489        "".to_string()
490    } else {
491        name.to_string().replace("_", "-")
492    }
493}