]> git.proxmox.com Git - rustc.git/blob - vendor/structopt-derive/src/attrs.rs
New upstream version 1.62.1+dfsg1
[rustc.git] / vendor / structopt-derive / src / attrs.rs
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 {
99 pub fn new(name: Ident, args: TokenStream) -> Self {
100 Method { name, args }
101 }
102
103 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 {
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 {
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
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 {
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 {
203 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 {
227 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
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
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
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
401 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 allow_skip: bool,
409 ) -> Self {
410 let mut res = Self::new(span, name, parent_attrs, None, argument_casing, env_casing);
411 res.push_attrs(attrs);
412 res.push_doc_comment(attrs, "about");
413
414 if res.has_custom_parser {
415 abort!(
416 res.parser.span(),
417 "`parse` attribute is only allowed on fields"
418 );
419 }
420 match &*res.kind {
421 Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
422 Kind::Skip(_) if !allow_skip => {
423 abort!(res.kind.span(), "skip is only allowed on fields")
424 }
425 Kind::Arg(_) | Kind::ExternalSubcommand | Kind::Flatten | Kind::Skip(_) => res,
426 }
427 }
428
429 pub fn from_field(
430 field: &syn::Field,
431 parent_attrs: Option<&Attrs>,
432 struct_casing: Sp<CasingStyle>,
433 env_casing: Sp<CasingStyle>,
434 ) -> Self {
435 let name = field.ident.clone().unwrap();
436 let mut res = Self::new(
437 field.span(),
438 Name::Derived(name),
439 parent_attrs,
440 Some(field.ty.clone()),
441 struct_casing,
442 env_casing,
443 );
444 res.push_attrs(&field.attrs);
445 res.push_doc_comment(&field.attrs, "help");
446
447 match &*res.kind {
448 Kind::Flatten => {
449 if res.has_custom_parser {
450 abort!(
451 res.parser.span(),
452 "parse attribute is not allowed for flattened entry"
453 );
454 }
455 if res.has_explicit_methods() {
456 abort!(
457 res.kind.span(),
458 "methods are not allowed for flattened entry"
459 );
460 }
461
462 if res.has_doc_methods() {
463 res.doc_comment = vec![];
464 }
465 }
466
467 Kind::ExternalSubcommand => {}
468
469 Kind::Subcommand(_) => {
470 if res.has_custom_parser {
471 abort!(
472 res.parser.span(),
473 "parse attribute is not allowed for subcommand"
474 );
475 }
476 if res.has_explicit_methods() {
477 abort!(
478 res.kind.span(),
479 "methods in attributes are not allowed for subcommand"
480 );
481 }
482
483 let ty = Ty::from_syn_ty(&field.ty);
484 match *ty {
485 Ty::OptionOption => {
486 abort!(
487 field.ty,
488 "Option<Option<T>> type is not allowed for subcommand"
489 );
490 }
491 Ty::OptionVec => {
492 abort!(
493 field.ty,
494 "Option<Vec<T>> type is not allowed for subcommand"
495 );
496 }
497 _ => (),
498 }
499
500 res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
501 }
502 Kind::Skip(_) => {
503 if res.has_explicit_methods() {
504 abort!(
505 res.kind.span(),
506 "methods are not allowed for skipped fields"
507 );
508 }
509 }
510 Kind::Arg(orig_ty) => {
511 let mut ty = Ty::from_syn_ty(&field.ty);
512 if res.has_custom_parser {
513 match *ty {
514 Ty::Option | Ty::Vec | Ty::OptionVec => (),
515 _ => ty = Sp::new(Ty::Other, ty.span()),
516 }
517 }
518
519 match *ty {
520 Ty::Bool => {
521 if res.is_positional() && !res.has_custom_parser {
522 abort!(field.ty,
523 "`bool` cannot be used as positional parameter with default parser";
524 help = "if you want to create a flag add `long` or `short`";
525 help = "If you really want a boolean parameter \
526 add an explicit parser, for example `parse(try_from_str)`";
527 note = "see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs";
528 )
529 }
530 if let Some(m) = res.find_method("default_value") {
531 abort!(m.name, "default_value is meaningless for bool")
532 }
533 if let Some(m) = res.find_method("required") {
534 abort!(m.name, "required is meaningless for bool")
535 }
536 }
537 Ty::Option => {
538 if let Some(m) = res.find_method("default_value") {
539 abort!(m.name, "default_value is meaningless for Option")
540 }
541 if let Some(m) = res.find_method("required") {
542 abort!(m.name, "required is meaningless for Option")
543 }
544 }
545 Ty::OptionOption => {
546 if res.is_positional() {
547 abort!(
548 field.ty,
549 "Option<Option<T>> type is meaningless for positional argument"
550 )
551 }
552 }
553 Ty::OptionVec => {
554 if res.is_positional() {
555 abort!(
556 field.ty,
557 "Option<Vec<T>> type is meaningless for positional argument"
558 )
559 }
560 }
561
562 _ => (),
563 }
564 res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
565 }
566 }
567
568 res
569 }
570
571 fn set_kind(&mut self, kind: Sp<Kind>) {
572 if let Kind::Arg(_) = *self.kind {
573 self.kind = kind;
574 } else {
575 abort!(
576 kind.span(),
577 "subcommand, flatten and skip cannot be used together"
578 );
579 }
580 }
581
582 pub fn has_method(&self, name: &str) -> bool {
583 self.find_method(name).is_some()
584 }
585
586 pub fn find_method(&self, name: &str) -> Option<&Method> {
587 self.methods.iter().find(|m| m.name == name)
588 }
589
590 /// generate methods from attributes on top of struct or enum
591 pub fn top_level_methods(&self) -> TokenStream {
592 let author = &self.author;
593 let about = &self.about;
594 let methods = &self.methods;
595 let doc_comment = &self.doc_comment;
596
597 quote!( #(#doc_comment)* #author #about #(#methods)* )
598 }
599
600 /// generate methods on top of a field
601 pub fn field_methods(&self) -> TokenStream {
602 let methods = &self.methods;
603 let doc_comment = &self.doc_comment;
604 quote!( #(#doc_comment)* #(#methods)* )
605 }
606
607 pub fn version(&self) -> TokenStream {
608 match (&self.no_version, &self.version) {
609 (None, Some(m)) => m.to_token_stream(),
610
611 (None, None) => std::env::var("CARGO_PKG_VERSION")
612 .map(|version| quote!( .version(#version) ))
613 .unwrap_or_default(),
614
615 _ => quote!(),
616 }
617 }
618
619 pub fn cased_name(&self) -> TokenStream {
620 self.name.clone().translate(*self.casing)
621 }
622
623 pub fn parser(&self) -> &Sp<Parser> {
624 &self.parser
625 }
626
627 pub fn kind(&self) -> Sp<Kind> {
628 self.kind.clone()
629 }
630
631 pub fn casing(&self) -> Sp<CasingStyle> {
632 self.casing.clone()
633 }
634
635 pub fn env_casing(&self) -> Sp<CasingStyle> {
636 self.env_casing.clone()
637 }
638
639 pub fn is_positional(&self) -> bool {
640 self.methods
641 .iter()
642 .all(|m| m.name != "long" && m.name != "short")
643 }
644
645 pub fn has_explicit_methods(&self) -> bool {
646 self.methods
647 .iter()
648 .any(|m| m.name != "help" && m.name != "long_help")
649 }
650
651 pub fn has_doc_methods(&self) -> bool {
652 !self.doc_comment.is_empty()
653 || self.methods.iter().any(|m| {
654 m.name == "help"
655 || m.name == "long_help"
656 || m.name == "about"
657 || m.name == "long_about"
658 })
659 }
660 }
661
662 /// replace all `:` with `, ` when not inside the `<>`
663 ///
664 /// `"author1:author2:author3" => "author1, author2, author3"`
665 /// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
666 fn process_author_str(author: &str) -> String {
667 let mut res = String::with_capacity(author.len());
668 let mut inside_angle_braces = 0usize;
669
670 for ch in author.chars() {
671 if inside_angle_braces > 0 && ch == '>' {
672 inside_angle_braces -= 1;
673 res.push(ch);
674 } else if ch == '<' {
675 inside_angle_braces += 1;
676 res.push(ch);
677 } else if inside_angle_braces == 0 && ch == ':' {
678 res.push_str(", ");
679 } else {
680 res.push(ch);
681 }
682 }
683
684 res
685 }