1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
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 use crate::doc_comments::process_doc_comment;
10 use crate::{parse::*, spanned::Sp, ty::Ty};
11 
12 use std::env;
13 
14 use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase};
15 use proc_macro2::{Span, TokenStream};
16 use proc_macro_error::abort;
17 use quote::{quote, quote_spanned, ToTokens};
18 use syn::{
19     self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue, Type,
20 };
21 
22 #[derive(Clone)]
23 pub enum Kind {
24     Arg(Sp<Ty>),
25     Subcommand(Sp<Ty>),
26     ExternalSubcommand,
27     Flatten,
28     Skip(Option<Expr>),
29 }
30 
31 #[derive(Clone)]
32 pub struct Method {
33     name: Ident,
34     args: TokenStream,
35 }
36 
37 #[derive(Clone)]
38 pub struct Parser {
39     pub kind: Sp<ParserKind>,
40     pub func: TokenStream,
41 }
42 
43 #[derive(Debug, PartialEq, Clone)]
44 pub enum ParserKind {
45     FromStr,
46     TryFromStr,
47     FromOsStr,
48     TryFromOsStr,
49     FromOccurrences,
50     FromFlag,
51 }
52 
53 /// Defines the casing for the attributes long representation.
54 #[derive(Copy, Clone, Debug, PartialEq)]
55 pub enum CasingStyle {
56     /// Indicate word boundaries with uppercase letter, excluding the first word.
57     Camel,
58     /// Keep all letters lowercase and indicate word boundaries with hyphens.
59     Kebab,
60     /// Indicate word boundaries with uppercase letter, including the first word.
61     Pascal,
62     /// Keep all letters uppercase and indicate word boundaries with underscores.
63     ScreamingSnake,
64     /// Keep all letters lowercase and indicate word boundaries with underscores.
65     Snake,
66     /// Use the original attribute name defined in the code.
67     Verbatim,
68     /// Keep all letters lowercase and remove word boundaries.
69     Lower,
70     /// Keep all letters uppercase and remove word boundaries.
71     Upper,
72 }
73 
74 #[derive(Clone)]
75 pub enum Name {
76     Derived(Ident),
77     Assigned(TokenStream),
78 }
79 
80 #[derive(Clone)]
81 pub struct Attrs {
82     name: Name,
83     casing: Sp<CasingStyle>,
84     env_casing: Sp<CasingStyle>,
85     ty: Option<Type>,
86     doc_comment: Vec<Method>,
87     methods: Vec<Method>,
88     parser: Sp<Parser>,
89     author: Option<Method>,
90     about: Option<Method>,
91     version: Option<Method>,
92     no_version: Option<Ident>,
93     verbatim_doc_comment: Option<Ident>,
94     has_custom_parser: bool,
95     kind: Sp<Kind>,
96 }
97 
98 impl Method {
new(name: Ident, args: TokenStream) -> Self99     pub fn new(name: Ident, args: TokenStream) -> Self {
100         Method { name, args }
101     }
102 
from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Self103     fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Self {
104         let mut lit = match lit {
105             Some(lit) => lit,
106 
107             None => match env::var(env_var) {
108                 Ok(val) => LitStr::new(&val, ident.span()),
109                 Err(_) => {
110                     abort!(ident,
111                         "cannot derive `{}` from Cargo.toml", ident;
112                         note = "`{}` environment variable is not set", env_var;
113                         help = "use `{} = \"...\"` to set {} manually", ident, ident;
114                     );
115                 }
116             },
117         };
118 
119         if ident == "author" {
120             let edited = process_author_str(&lit.value());
121             lit = LitStr::new(&edited, lit.span());
122         }
123 
124         Method::new(ident, quote!(#lit))
125     }
126 }
127 
128 impl ToTokens for Method {
to_tokens(&self, ts: &mut TokenStream)129     fn to_tokens(&self, ts: &mut TokenStream) {
130         let Method { ref name, ref args } = self;
131         quote!(.#name(#args)).to_tokens(ts);
132     }
133 }
134 
135 impl Parser {
default_spanned(span: Span) -> Sp<Self>136     fn default_spanned(span: Span) -> Sp<Self> {
137         let kind = Sp::new(ParserKind::TryFromStr, span);
138         let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
139         Sp::new(Parser { kind, func }, span)
140     }
141 
from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self>142     fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
143         use ParserKind::*;
144 
145         let kind = match &*spec.kind.to_string() {
146             "from_str" => FromStr,
147             "try_from_str" => TryFromStr,
148             "from_os_str" => FromOsStr,
149             "try_from_os_str" => TryFromOsStr,
150             "from_occurrences" => FromOccurrences,
151             "from_flag" => FromFlag,
152             s => abort!(spec.kind, "unsupported parser `{}`", s),
153         };
154 
155         let func = match spec.parse_func {
156             None => match kind {
157                 FromStr | FromOsStr => {
158                     quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
159                 }
160                 TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
161                 TryFromOsStr => abort!(
162                     spec.kind,
163                     "you must set parser for `try_from_os_str` explicitly"
164                 ),
165                 FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
166                 FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
167             },
168 
169             Some(func) => match func {
170                 syn::Expr::Path(_) => quote!(#func),
171                 _ => abort!(func, "`parse` argument must be a function path"),
172             },
173         };
174 
175         let kind = Sp::new(kind, spec.kind.span());
176         let parser = Parser { kind, func };
177         Sp::new(parser, parse_ident.span())
178     }
179 }
180 
181 impl CasingStyle {
from_lit(name: LitStr) -> Sp<Self>182     fn from_lit(name: LitStr) -> Sp<Self> {
183         use CasingStyle::*;
184 
185         let normalized = name.value().to_camel_case().to_lowercase();
186         let cs = |kind| Sp::new(kind, name.span());
187 
188         match normalized.as_ref() {
189             "camel" | "camelcase" => cs(Camel),
190             "kebab" | "kebabcase" => cs(Kebab),
191             "pascal" | "pascalcase" => cs(Pascal),
192             "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
193             "snake" | "snakecase" => cs(Snake),
194             "verbatim" | "verbatimcase" => cs(Verbatim),
195             "lower" | "lowercase" => cs(Lower),
196             "upper" | "uppercase" => cs(Upper),
197             s => abort!(name, "unsupported casing: `{}`", s),
198         }
199     }
200 }
201 
202 impl Name {
translate(self, style: CasingStyle) -> TokenStream203     pub fn translate(self, style: CasingStyle) -> TokenStream {
204         use CasingStyle::*;
205 
206         match self {
207             Name::Assigned(tokens) => tokens,
208             Name::Derived(ident) => {
209                 let s = ident.unraw().to_string();
210                 let s = match style {
211                     Pascal => s.to_camel_case(),
212                     Kebab => s.to_kebab_case(),
213                     Camel => s.to_mixed_case(),
214                     ScreamingSnake => s.to_shouty_snake_case(),
215                     Snake => s.to_snake_case(),
216                     Verbatim => s,
217                     Lower => s.to_snake_case().replace("_", ""),
218                     Upper => s.to_shouty_snake_case().replace("_", ""),
219                 };
220                 quote_spanned!(ident.span()=> #s)
221             }
222         }
223     }
224 }
225 
226 impl Attrs {
new( default_span: Span, name: Name, parent_attrs: Option<&Attrs>, ty: Option<Type>, casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self227     fn new(
228         default_span: Span,
229         name: Name,
230         parent_attrs: Option<&Attrs>,
231         ty: Option<Type>,
232         casing: Sp<CasingStyle>,
233         env_casing: Sp<CasingStyle>,
234     ) -> Self {
235         let no_version = parent_attrs
236             .as_ref()
237             .map(|attrs| attrs.no_version.clone())
238             .unwrap_or(None);
239 
240         Self {
241             name,
242             ty,
243             casing,
244             env_casing,
245             doc_comment: vec![],
246             methods: vec![],
247             parser: Parser::default_spanned(default_span),
248             about: None,
249             author: None,
250             version: None,
251             no_version,
252             verbatim_doc_comment: None,
253 
254             has_custom_parser: false,
255             kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
256         }
257     }
258 
push_method(&mut self, name: Ident, arg: impl ToTokens)259     fn push_method(&mut self, name: Ident, arg: impl ToTokens) {
260         if name == "name" {
261             self.name = Name::Assigned(quote!(#arg));
262         } else if name == "version" {
263             self.version = Some(Method::new(name, quote!(#arg)));
264         } else {
265             self.methods.push(Method::new(name, quote!(#arg)))
266         }
267     }
268 
push_attrs(&mut self, attrs: &[Attribute])269     fn push_attrs(&mut self, attrs: &[Attribute]) {
270         use crate::parse::StructOptAttr::*;
271 
272         for attr in parse_structopt_attributes(attrs) {
273             match attr {
274                 Short(ident) | Long(ident) => {
275                     self.push_method(ident, self.name.clone().translate(*self.casing));
276                 }
277 
278                 Env(ident) => {
279                     self.push_method(ident, self.name.clone().translate(*self.env_casing));
280                 }
281 
282                 Subcommand(ident) => {
283                     let ty = Sp::call_site(Ty::Other);
284                     let kind = Sp::new(Kind::Subcommand(ty), ident.span());
285                     self.set_kind(kind);
286                 }
287 
288                 ExternalSubcommand(ident) => {
289                     self.kind = Sp::new(Kind::ExternalSubcommand, ident.span());
290                 }
291 
292                 Flatten(ident) => {
293                     let kind = Sp::new(Kind::Flatten, ident.span());
294                     self.set_kind(kind);
295                 }
296 
297                 Skip(ident, expr) => {
298                     let kind = Sp::new(Kind::Skip(expr), ident.span());
299                     self.set_kind(kind);
300                 }
301 
302                 NoVersion(ident) => self.no_version = Some(ident),
303 
304                 VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
305 
306                 DefaultValue(ident, lit) => {
307                     let val = if let Some(lit) = lit {
308                         quote!(#lit)
309                     } else {
310                         let ty = if let Some(ty) = self.ty.as_ref() {
311                             ty
312                         } else {
313                             abort!(
314                                 ident,
315                                 "#[structopt(default_value)] (without an argument) can be used \
316                                 only on field level";
317 
318                                 note = "see \
319                                     https://docs.rs/structopt/0.3.5/structopt/#magical-methods")
320                         };
321 
322                         quote_spanned!(ident.span()=> {
323                             ::structopt::lazy_static::lazy_static! {
324                                 static ref DEFAULT_VALUE: &'static str = {
325                                     let val = <#ty as ::std::default::Default>::default();
326                                     let s = ::std::string::ToString::to_string(&val);
327                                     ::std::boxed::Box::leak(s.into_boxed_str())
328                                 };
329                             }
330                             *DEFAULT_VALUE
331                         })
332                     };
333 
334                     self.methods.push(Method::new(ident, val));
335                 }
336 
337                 About(ident, about) => {
338                     self.about = Some(Method::from_lit_or_env(
339                         ident,
340                         about,
341                         "CARGO_PKG_DESCRIPTION",
342                     ));
343                 }
344 
345                 Author(ident, author) => {
346                     self.author = Some(Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS"));
347                 }
348 
349                 Version(ident, version) => {
350                     self.push_method(ident, version);
351                 }
352 
353                 NameLitStr(name, lit) => {
354                     self.push_method(name, lit);
355                 }
356 
357                 NameExpr(name, expr) => {
358                     self.push_method(name, expr);
359                 }
360 
361                 MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),
362 
363                 RenameAll(_, casing_lit) => {
364                     self.casing = CasingStyle::from_lit(casing_lit);
365                 }
366 
367                 RenameAllEnv(_, casing_lit) => {
368                     self.env_casing = CasingStyle::from_lit(casing_lit);
369                 }
370 
371                 Parse(ident, spec) => {
372                     self.has_custom_parser = true;
373                     self.parser = Parser::from_spec(ident, spec);
374                 }
375             }
376         }
377     }
378 
push_doc_comment(&mut self, attrs: &[Attribute], name: &str)379     fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
380         use crate::Lit::*;
381         use crate::Meta::*;
382 
383         let comment_parts: Vec<_> = attrs
384             .iter()
385             .filter(|attr| attr.path.is_ident("doc"))
386             .filter_map(|attr| {
387                 if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
388                     Some(s.value())
389                 } else {
390                     // non #[doc = "..."] attributes are not our concern
391                     // we leave them for rustc to handle
392                     None
393                 }
394             })
395             .collect();
396 
397         self.doc_comment =
398             process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none());
399     }
400 
from_struct( span: Span, attrs: &[Attribute], name: Name, parent_attrs: Option<&Attrs>, argument_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self401     pub fn from_struct(
402         span: Span,
403         attrs: &[Attribute],
404         name: Name,
405         parent_attrs: Option<&Attrs>,
406         argument_casing: Sp<CasingStyle>,
407         env_casing: Sp<CasingStyle>,
408     ) -> Self {
409         let mut res = Self::new(span, name, parent_attrs, None, argument_casing, env_casing);
410         res.push_attrs(attrs);
411         res.push_doc_comment(attrs, "about");
412 
413         if res.has_custom_parser {
414             abort!(
415                 res.parser.span(),
416                 "`parse` attribute is only allowed on fields"
417             );
418         }
419         match &*res.kind {
420             Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
421             Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"),
422             Kind::Arg(_) | Kind::ExternalSubcommand | Kind::Flatten => res,
423         }
424     }
425 
from_field( field: &syn::Field, parent_attrs: Option<&Attrs>, struct_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self426     pub fn from_field(
427         field: &syn::Field,
428         parent_attrs: Option<&Attrs>,
429         struct_casing: Sp<CasingStyle>,
430         env_casing: Sp<CasingStyle>,
431     ) -> Self {
432         let name = field.ident.clone().unwrap();
433         let mut res = Self::new(
434             field.span(),
435             Name::Derived(name),
436             parent_attrs,
437             Some(field.ty.clone()),
438             struct_casing,
439             env_casing,
440         );
441         res.push_attrs(&field.attrs);
442         res.push_doc_comment(&field.attrs, "help");
443 
444         match &*res.kind {
445             Kind::Flatten => {
446                 if res.has_custom_parser {
447                     abort!(
448                         res.parser.span(),
449                         "parse attribute is not allowed for flattened entry"
450                     );
451                 }
452                 if res.has_explicit_methods() {
453                     abort!(
454                         res.kind.span(),
455                         "methods are not allowed for flattened entry"
456                     );
457                 }
458 
459                 if res.has_doc_methods() {
460                     res.doc_comment = vec![];
461                 }
462             }
463 
464             Kind::ExternalSubcommand => {}
465 
466             Kind::Subcommand(_) => {
467                 if res.has_custom_parser {
468                     abort!(
469                         res.parser.span(),
470                         "parse attribute is not allowed for subcommand"
471                     );
472                 }
473                 if res.has_explicit_methods() {
474                     abort!(
475                         res.kind.span(),
476                         "methods in attributes are not allowed for subcommand"
477                     );
478                 }
479 
480                 let ty = Ty::from_syn_ty(&field.ty);
481                 match *ty {
482                     Ty::OptionOption => {
483                         abort!(
484                             field.ty,
485                             "Option<Option<T>> type is not allowed for subcommand"
486                         );
487                     }
488                     Ty::OptionVec => {
489                         abort!(
490                             field.ty,
491                             "Option<Vec<T>> type is not allowed for subcommand"
492                         );
493                     }
494                     _ => (),
495                 }
496 
497                 res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
498             }
499             Kind::Skip(_) => {
500                 if res.has_explicit_methods() {
501                     abort!(
502                         res.kind.span(),
503                         "methods are not allowed for skipped fields"
504                     );
505                 }
506             }
507             Kind::Arg(orig_ty) => {
508                 let mut ty = Ty::from_syn_ty(&field.ty);
509                 if res.has_custom_parser {
510                     match *ty {
511                         Ty::Option | Ty::Vec | Ty::OptionVec => (),
512                         _ => ty = Sp::new(Ty::Other, ty.span()),
513                     }
514                 }
515 
516                 match *ty {
517                     Ty::Bool => {
518                         if res.is_positional() && !res.has_custom_parser {
519                             abort!(field.ty,
520                                 "`bool` cannot be used as positional parameter with default parser";
521                                 help = "if you want to create a flag add `long` or `short`";
522                                 help = "If you really want a boolean parameter \
523                                     add an explicit parser, for example `parse(try_from_str)`";
524                                 note = "see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs";
525                             )
526                         }
527                         if let Some(m) = res.find_method("default_value") {
528                             abort!(m.name, "default_value is meaningless for bool")
529                         }
530                         if let Some(m) = res.find_method("required") {
531                             abort!(m.name, "required is meaningless for bool")
532                         }
533                     }
534                     Ty::Option => {
535                         if let Some(m) = res.find_method("default_value") {
536                             abort!(m.name, "default_value is meaningless for Option")
537                         }
538                         if let Some(m) = res.find_method("required") {
539                             abort!(m.name, "required is meaningless for Option")
540                         }
541                     }
542                     Ty::OptionOption => {
543                         if res.is_positional() {
544                             abort!(
545                                 field.ty,
546                                 "Option<Option<T>> type is meaningless for positional argument"
547                             )
548                         }
549                     }
550                     Ty::OptionVec => {
551                         if res.is_positional() {
552                             abort!(
553                                 field.ty,
554                                 "Option<Vec<T>> type is meaningless for positional argument"
555                             )
556                         }
557                     }
558 
559                     _ => (),
560                 }
561                 res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
562             }
563         }
564 
565         res
566     }
567 
set_kind(&mut self, kind: Sp<Kind>)568     fn set_kind(&mut self, kind: Sp<Kind>) {
569         if let Kind::Arg(_) = *self.kind {
570             self.kind = kind;
571         } else {
572             abort!(
573                 kind.span(),
574                 "subcommand, flatten and skip cannot be used together"
575             );
576         }
577     }
578 
has_method(&self, name: &str) -> bool579     pub fn has_method(&self, name: &str) -> bool {
580         self.find_method(name).is_some()
581     }
582 
find_method(&self, name: &str) -> Option<&Method>583     pub fn find_method(&self, name: &str) -> Option<&Method> {
584         self.methods.iter().find(|m| m.name == name)
585     }
586 
587     /// generate methods from attributes on top of struct or enum
top_level_methods(&self) -> TokenStream588     pub fn top_level_methods(&self) -> TokenStream {
589         let author = &self.author;
590         let about = &self.about;
591         let methods = &self.methods;
592         let doc_comment = &self.doc_comment;
593 
594         quote!( #(#doc_comment)* #author #about #(#methods)*  )
595     }
596 
597     /// generate methods on top of a field
field_methods(&self) -> TokenStream598     pub fn field_methods(&self) -> TokenStream {
599         let methods = &self.methods;
600         let doc_comment = &self.doc_comment;
601         quote!( #(#doc_comment)* #(#methods)* )
602     }
603 
version(&self) -> TokenStream604     pub fn version(&self) -> TokenStream {
605         match (&self.no_version, &self.version) {
606             (None, Some(m)) => m.to_token_stream(),
607 
608             (None, None) => std::env::var("CARGO_PKG_VERSION")
609                 .map(|version| quote!( .version(#version) ))
610                 .unwrap_or_default(),
611 
612             _ => quote!(),
613         }
614     }
615 
cased_name(&self) -> TokenStream616     pub fn cased_name(&self) -> TokenStream {
617         self.name.clone().translate(*self.casing)
618     }
619 
parser(&self) -> &Sp<Parser>620     pub fn parser(&self) -> &Sp<Parser> {
621         &self.parser
622     }
623 
kind(&self) -> Sp<Kind>624     pub fn kind(&self) -> Sp<Kind> {
625         self.kind.clone()
626     }
627 
casing(&self) -> Sp<CasingStyle>628     pub fn casing(&self) -> Sp<CasingStyle> {
629         self.casing.clone()
630     }
631 
env_casing(&self) -> Sp<CasingStyle>632     pub fn env_casing(&self) -> Sp<CasingStyle> {
633         self.env_casing.clone()
634     }
635 
is_positional(&self) -> bool636     pub fn is_positional(&self) -> bool {
637         self.methods
638             .iter()
639             .all(|m| m.name != "long" && m.name != "short")
640     }
641 
has_explicit_methods(&self) -> bool642     pub fn has_explicit_methods(&self) -> bool {
643         self.methods
644             .iter()
645             .any(|m| m.name != "help" && m.name != "long_help")
646     }
647 
has_doc_methods(&self) -> bool648     pub fn has_doc_methods(&self) -> bool {
649         !self.doc_comment.is_empty()
650             || self.methods.iter().any(|m| {
651                 m.name == "help"
652                     || m.name == "long_help"
653                     || m.name == "about"
654                     || m.name == "long_about"
655             })
656     }
657 }
658 
659 /// replace all `:` with `, ` when not inside the `<>`
660 ///
661 /// `"author1:author2:author3" => "author1, author2, author3"`
662 /// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
process_author_str(author: &str) -> String663 fn process_author_str(author: &str) -> String {
664     let mut res = String::with_capacity(author.len());
665     let mut inside_angle_braces = 0usize;
666 
667     for ch in author.chars() {
668         if inside_angle_braces > 0 && ch == '>' {
669             inside_angle_braces -= 1;
670             res.push(ch);
671         } else if ch == '<' {
672             inside_angle_braces += 1;
673             res.push(ch);
674         } else if inside_angle_braces == 0 && ch == ':' {
675             res.push_str(", ");
676         } else {
677             res.push(ch);
678         }
679     }
680 
681     res
682 }
683