1#![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 } 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 } 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 } 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
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#[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 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 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}