1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2 // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3 // Ana Hobden (@hoverbear) <operator@hoverbear.org>
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10 //
11 // This work was derived from Structopt (https://github.com/TeXitoi/structopt)
12 // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
13 // MIT/Apache 2.0 license.
14
15 use crate::{
16 parse::*,
17 utils::{process_doc_comment, Sp, Ty},
18 };
19
20 use std::env;
21
22 use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
23 use proc_macro2::{self, Span, TokenStream};
24 use proc_macro_error::abort;
25 use quote::{quote, quote_spanned, ToTokens};
26 use syn::{
27 self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue,
28 Type, Variant,
29 };
30
31 /// Default casing style for generated arguments.
32 pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
33
34 /// Default casing style for environment variables
35 pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
36
37 #[derive(Clone)]
38 pub struct Attrs {
39 name: Name,
40 casing: Sp<CasingStyle>,
41 env_casing: Sp<CasingStyle>,
42 ty: Option<Type>,
43 doc_comment: Vec<Method>,
44 methods: Vec<Method>,
45 parser: Sp<Parser>,
46 author: Option<Method>,
47 version: Option<Method>,
48 verbatim_doc_comment: Option<Ident>,
49 help_heading: Option<Method>,
50 is_enum: bool,
51 has_custom_parser: bool,
52 kind: Sp<Kind>,
53 }
54
55 impl Attrs {
from_struct( span: Span, attrs: &[Attribute], name: Name, argument_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self56 pub fn from_struct(
57 span: Span,
58 attrs: &[Attribute],
59 name: Name,
60 argument_casing: Sp<CasingStyle>,
61 env_casing: Sp<CasingStyle>,
62 ) -> Self {
63 let mut res = Self::new(span, name, None, argument_casing, env_casing);
64 res.push_attrs(attrs);
65 res.push_doc_comment(attrs, "about");
66
67 if res.has_custom_parser {
68 abort!(
69 res.parser.span(),
70 "`parse` attribute is only allowed on fields"
71 );
72 }
73 match &*res.kind {
74 Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
75 Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"),
76 Kind::Arg(_) => res,
77 Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"),
78 Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"),
79 Kind::ExternalSubcommand => abort!(
80 res.kind.span(),
81 "external_subcommand is only allowed on fields"
82 ),
83 }
84 }
85
from_variant( variant: &Variant, struct_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self86 pub fn from_variant(
87 variant: &Variant,
88 struct_casing: Sp<CasingStyle>,
89 env_casing: Sp<CasingStyle>,
90 ) -> Self {
91 let name = variant.ident.clone();
92 let mut res = Self::new(
93 variant.span(),
94 Name::Derived(name),
95 None,
96 struct_casing,
97 env_casing,
98 );
99 res.push_attrs(&variant.attrs);
100 res.push_doc_comment(&variant.attrs, "about");
101
102 match &*res.kind {
103 Kind::Flatten => {
104 if res.has_custom_parser {
105 abort!(
106 res.parser.span(),
107 "parse attribute is not allowed for flattened entry"
108 );
109 }
110 if res.has_explicit_methods() {
111 abort!(
112 res.kind.span(),
113 "methods are not allowed for flattened entry"
114 );
115 }
116
117 // ignore doc comments
118 res.doc_comment = vec![];
119 }
120
121 Kind::ExternalSubcommand => (),
122
123 Kind::Subcommand(_) => {
124 if res.has_custom_parser {
125 abort!(
126 res.parser.span(),
127 "parse attribute is not allowed for subcommand"
128 );
129 }
130 if res.has_explicit_methods() {
131 abort!(
132 res.kind.span(),
133 "methods in attributes are not allowed for subcommand"
134 );
135 }
136 use syn::Fields::*;
137 use syn::FieldsUnnamed;
138
139 let field_ty = match variant.fields {
140 Named(_) => {
141 abort!(variant.span(), "structs are not allowed for subcommand");
142 }
143 Unit => abort!(variant.span(), "unit-type is not allowed for subcommand"),
144 Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
145 &unnamed[0].ty
146 }
147 Unnamed(..) => {
148 abort!(
149 variant,
150 "non single-typed tuple is not allowed for subcommand"
151 )
152 }
153 };
154 let ty = Ty::from_syn_ty(field_ty);
155 match *ty {
156 Ty::OptionOption => {
157 abort!(
158 field_ty,
159 "Option<Option<T>> type is not allowed for subcommand"
160 );
161 }
162 Ty::OptionVec => {
163 abort!(
164 field_ty,
165 "Option<Vec<T>> type is not allowed for subcommand"
166 );
167 }
168 _ => (),
169 }
170
171 res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
172 }
173 Kind::Skip(_) => (),
174 Kind::FromGlobal(_) => {
175 abort!(res.kind.span(), "from_global is not supported on variants");
176 }
177 Kind::Arg(_) => (),
178 }
179
180 res
181 }
182
from_arg_enum_variant( variant: &Variant, argument_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self183 pub fn from_arg_enum_variant(
184 variant: &Variant,
185 argument_casing: Sp<CasingStyle>,
186 env_casing: Sp<CasingStyle>,
187 ) -> Self {
188 let mut res = Self::new(
189 variant.span(),
190 Name::Derived(variant.ident.clone()),
191 None,
192 argument_casing,
193 env_casing,
194 );
195 res.push_attrs(&variant.attrs);
196 res.push_doc_comment(&variant.attrs, "help");
197
198 if res.has_custom_parser {
199 abort!(
200 res.parser.span(),
201 "`parse` attribute is only allowed on fields"
202 );
203 }
204 match &*res.kind {
205 Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
206 Kind::Skip(_) => res,
207 Kind::Arg(_) => res,
208 Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"),
209 Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"),
210 Kind::ExternalSubcommand => abort!(
211 res.kind.span(),
212 "external_subcommand is only allowed on fields"
213 ),
214 }
215 }
216
from_field( field: &Field, struct_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self217 pub fn from_field(
218 field: &Field,
219 struct_casing: Sp<CasingStyle>,
220 env_casing: Sp<CasingStyle>,
221 ) -> Self {
222 let name = field.ident.clone().unwrap();
223 let mut res = Self::new(
224 field.span(),
225 Name::Derived(name),
226 Some(field.ty.clone()),
227 struct_casing,
228 env_casing,
229 );
230 res.push_attrs(&field.attrs);
231 res.push_doc_comment(&field.attrs, "help");
232
233 match &*res.kind {
234 Kind::Flatten => {
235 if res.has_custom_parser {
236 abort!(
237 res.parser.span(),
238 "parse attribute is not allowed for flattened entry"
239 );
240 }
241 if res.has_explicit_methods() {
242 abort!(
243 res.kind.span(),
244 "methods are not allowed for flattened entry"
245 );
246 }
247
248 // ignore doc comments
249 res.doc_comment = vec![];
250 }
251
252 Kind::ExternalSubcommand => {
253 abort! { res.kind.span(),
254 "`external_subcommand` can be used only on enum variants"
255 }
256 }
257
258 Kind::Subcommand(_) => {
259 if res.has_custom_parser {
260 abort!(
261 res.parser.span(),
262 "parse attribute is not allowed for subcommand"
263 );
264 }
265 if res.has_explicit_methods() {
266 abort!(
267 res.kind.span(),
268 "methods in attributes are not allowed for subcommand"
269 );
270 }
271
272 let ty = Ty::from_syn_ty(&field.ty);
273 match *ty {
274 Ty::OptionOption => {
275 abort!(
276 field.ty,
277 "Option<Option<T>> type is not allowed for subcommand"
278 );
279 }
280 Ty::OptionVec => {
281 abort!(
282 field.ty,
283 "Option<Vec<T>> type is not allowed for subcommand"
284 );
285 }
286 _ => (),
287 }
288
289 res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
290 }
291 Kind::Skip(_) => {
292 if res.has_explicit_methods() {
293 abort!(
294 res.kind.span(),
295 "methods are not allowed for skipped fields"
296 );
297 }
298 }
299 Kind::FromGlobal(orig_ty) => {
300 let ty = Ty::from_syn_ty(&field.ty);
301 res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span());
302 }
303 Kind::Arg(orig_ty) => {
304 let mut ty = Ty::from_syn_ty(&field.ty);
305 if res.has_custom_parser {
306 match *ty {
307 Ty::Option | Ty::Vec | Ty::OptionVec => (),
308 _ => ty = Sp::new(Ty::Other, ty.span()),
309 }
310 }
311
312 match *ty {
313 Ty::Bool => {
314 if res.is_positional() && !res.has_custom_parser {
315 abort!(field.ty,
316 "`bool` cannot be used as positional parameter with default parser";
317 help = "if you want to create a flag add `long` or `short`";
318 help = "If you really want a boolean parameter \
319 add an explicit parser, for example `parse(try_from_str)`";
320 note = "see also https://github.com/clap-rs/clap/blob/master/examples/derive_ref/custom-bool.md";
321 )
322 }
323 if res.is_enum {
324 abort!(field.ty, "`arg_enum` is meaningless for bool")
325 }
326 if let Some(m) = res.find_default_method() {
327 abort!(m.name, "default_value is meaningless for bool")
328 }
329 if let Some(m) = res.find_method("required") {
330 abort!(m.name, "required is meaningless for bool")
331 }
332 }
333 Ty::Option => {
334 if let Some(m) = res.find_default_method() {
335 abort!(m.name, "default_value is meaningless for Option")
336 }
337 }
338 Ty::OptionOption => {
339 if res.is_positional() {
340 abort!(
341 field.ty,
342 "Option<Option<T>> type is meaningless for positional argument"
343 )
344 }
345 }
346 Ty::OptionVec => {
347 if res.is_positional() {
348 abort!(
349 field.ty,
350 "Option<Vec<T>> type is meaningless for positional argument"
351 )
352 }
353 }
354
355 _ => (),
356 }
357 res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
358 }
359 }
360
361 res
362 }
363
new( default_span: Span, name: Name, ty: Option<Type>, casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self364 fn new(
365 default_span: Span,
366 name: Name,
367 ty: Option<Type>,
368 casing: Sp<CasingStyle>,
369 env_casing: Sp<CasingStyle>,
370 ) -> Self {
371 Self {
372 name,
373 ty,
374 casing,
375 env_casing,
376 doc_comment: vec![],
377 methods: vec![],
378 parser: Parser::default_spanned(default_span),
379 author: None,
380 version: None,
381 verbatim_doc_comment: None,
382 help_heading: None,
383 is_enum: false,
384 has_custom_parser: false,
385 kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
386 }
387 }
388
push_method(&mut self, name: Ident, arg: impl ToTokens)389 fn push_method(&mut self, name: Ident, arg: impl ToTokens) {
390 if name == "name" {
391 self.name = Name::Assigned(quote!(#arg));
392 } else if name == "version" {
393 self.version = Some(Method::new(name, quote!(#arg)));
394 } else {
395 self.methods.push(Method::new(name, quote!(#arg)))
396 }
397 }
398
push_attrs(&mut self, attrs: &[Attribute])399 fn push_attrs(&mut self, attrs: &[Attribute]) {
400 use ClapAttr::*;
401
402 let parsed = parse_clap_attributes(attrs);
403 for attr in &parsed {
404 let attr = attr.clone();
405 match attr {
406 Short(ident) => {
407 self.push_method(ident, self.name.clone().translate_char(*self.casing));
408 }
409
410 Long(ident) => {
411 self.push_method(ident, self.name.clone().translate(*self.casing));
412 }
413
414 Env(ident) => {
415 self.push_method(ident, self.name.clone().translate(*self.env_casing));
416 }
417
418 ArgEnum(_) => self.is_enum = true,
419
420 FromGlobal(ident) => {
421 let ty = Sp::call_site(Ty::Other);
422 let kind = Sp::new(Kind::FromGlobal(ty), ident.span());
423 self.set_kind(kind);
424 }
425
426 Subcommand(ident) => {
427 let ty = Sp::call_site(Ty::Other);
428 let kind = Sp::new(Kind::Subcommand(ty), ident.span());
429 self.set_kind(kind);
430 }
431
432 ExternalSubcommand(ident) => {
433 let kind = Sp::new(Kind::ExternalSubcommand, ident.span());
434 self.set_kind(kind);
435 }
436
437 Flatten(ident) => {
438 let kind = Sp::new(Kind::Flatten, ident.span());
439 self.set_kind(kind);
440 }
441
442 Skip(ident, expr) => {
443 let kind = Sp::new(Kind::Skip(expr), ident.span());
444 self.set_kind(kind);
445 }
446
447 VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
448
449 DefaultValueT(ident, expr) => {
450 let ty = if let Some(ty) = self.ty.as_ref() {
451 ty
452 } else {
453 abort!(
454 ident,
455 "#[clap(default_value_t)] (without an argument) can be used \
456 only on field level";
457
458 note = "see \
459 https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
460 };
461
462 let val = if let Some(expr) = expr {
463 quote!(#expr)
464 } else {
465 quote!(<#ty as ::std::default::Default>::default())
466 };
467
468 let val = if parsed.iter().any(|a| matches!(a, ArgEnum(_))) {
469 quote_spanned!(ident.span()=> {
470 {
471 let val: #ty = #val;
472 clap::ArgEnum::to_possible_value(&val).unwrap().get_name()
473 }
474 })
475 } else {
476 quote_spanned!(ident.span()=> {
477 clap::lazy_static::lazy_static! {
478 static ref DEFAULT_VALUE: &'static str = {
479 let val: #ty = #val;
480 let s = ::std::string::ToString::to_string(&val);
481 ::std::boxed::Box::leak(s.into_boxed_str())
482 };
483 }
484 *DEFAULT_VALUE
485 })
486 };
487
488 let raw_ident = Ident::new("default_value", ident.span());
489 self.methods.push(Method::new(raw_ident, val));
490 }
491
492 DefaultValueOsT(ident, expr) => {
493 let ty = if let Some(ty) = self.ty.as_ref() {
494 ty
495 } else {
496 abort!(
497 ident,
498 "#[clap(default_value_os_t)] (without an argument) can be used \
499 only on field level";
500
501 note = "see \
502 https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
503 };
504
505 let val = if let Some(expr) = expr {
506 quote!(#expr)
507 } else {
508 quote!(<#ty as ::std::default::Default>::default())
509 };
510
511 let val = if parsed.iter().any(|a| matches!(a, ArgEnum(_))) {
512 quote_spanned!(ident.span()=> {
513 {
514 let val: #ty = #val;
515 clap::ArgEnum::to_possible_value(&val).unwrap().get_name()
516 }
517 })
518 } else {
519 quote_spanned!(ident.span()=> {
520 clap::lazy_static::lazy_static! {
521 static ref DEFAULT_VALUE: &'static ::std::ffi::OsStr = {
522 let val: #ty = #val;
523 let s: ::std::ffi::OsString = val.into();
524 ::std::boxed::Box::leak(s.into_boxed_os_str())
525 };
526 }
527 *DEFAULT_VALUE
528 })
529 };
530
531 let raw_ident = Ident::new("default_value_os", ident.span());
532 self.methods.push(Method::new(raw_ident, val));
533 }
534
535 HelpHeading(ident, expr) => {
536 self.help_heading = Some(Method::new(ident, quote!(#expr)));
537 }
538
539 About(ident, about) => {
540 if let Some(method) =
541 Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION")
542 {
543 self.methods.push(method);
544 }
545 }
546
547 Author(ident, author) => {
548 self.author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS");
549 }
550
551 Version(ident, version) => {
552 self.version = Method::from_lit_or_env(ident, version, "CARGO_PKG_VERSION");
553 }
554
555 NameLitStr(name, lit) => {
556 self.push_method(name, lit);
557 }
558
559 NameExpr(name, expr) => {
560 self.push_method(name, expr);
561 }
562
563 MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),
564
565 RenameAll(_, casing_lit) => {
566 self.casing = CasingStyle::from_lit(casing_lit);
567 }
568
569 RenameAllEnv(_, casing_lit) => {
570 self.env_casing = CasingStyle::from_lit(casing_lit);
571 }
572
573 Parse(ident, spec) => {
574 self.has_custom_parser = true;
575 self.parser = Parser::from_spec(ident, spec);
576 }
577 }
578 }
579 }
580
push_doc_comment(&mut self, attrs: &[Attribute], name: &str)581 fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
582 use syn::Lit::*;
583 use syn::Meta::*;
584
585 let comment_parts: Vec<_> = attrs
586 .iter()
587 .filter(|attr| attr.path.is_ident("doc"))
588 .filter_map(|attr| {
589 if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
590 Some(s.value())
591 } else {
592 // non #[doc = "..."] attributes are not our concern
593 // we leave them for rustc to handle
594 None
595 }
596 })
597 .collect();
598
599 self.doc_comment =
600 process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none());
601 }
602
set_kind(&mut self, kind: Sp<Kind>)603 fn set_kind(&mut self, kind: Sp<Kind>) {
604 if let Kind::Arg(_) = *self.kind {
605 self.kind = kind;
606 } else {
607 abort!(
608 kind.span(),
609 "`subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together"
610 );
611 }
612 }
613
find_method(&self, name: &str) -> Option<&Method>614 pub fn find_method(&self, name: &str) -> Option<&Method> {
615 self.methods.iter().find(|m| m.name == name)
616 }
617
find_default_method(&self) -> Option<&Method>618 pub fn find_default_method(&self) -> Option<&Method> {
619 self.methods
620 .iter()
621 .find(|m| m.name == "default_value" || m.name == "default_value_os")
622 }
623
624 /// generate methods from attributes on top of struct or enum
initial_top_level_methods(&self) -> TokenStream625 pub fn initial_top_level_methods(&self) -> TokenStream {
626 let help_heading = self.help_heading.as_ref().into_iter();
627 quote!( #(#help_heading)* )
628 }
629
final_top_level_methods(&self) -> TokenStream630 pub fn final_top_level_methods(&self) -> TokenStream {
631 let version = &self.version;
632 let author = &self.author;
633 let methods = &self.methods;
634 let doc_comment = &self.doc_comment;
635
636 quote!( #(#doc_comment)* #author #version #(#methods)*)
637 }
638
639 /// generate methods on top of a field
field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream640 pub fn field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream {
641 let methods = &self.methods;
642 let help_heading = self.help_heading.as_ref().into_iter();
643 match supports_long_help {
644 true => {
645 let doc_comment = &self.doc_comment;
646 quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* )
647 }
648 false => {
649 let doc_comment = self
650 .doc_comment
651 .iter()
652 .filter(|mth| mth.name != "long_help");
653 quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* )
654 }
655 }
656 }
657
help_heading(&self) -> TokenStream658 pub fn help_heading(&self) -> TokenStream {
659 let help_heading = self.help_heading.as_ref().into_iter();
660 quote!( #(#help_heading)* )
661 }
662
cased_name(&self) -> TokenStream663 pub fn cased_name(&self) -> TokenStream {
664 self.name.clone().translate(*self.casing)
665 }
666
value_name(&self) -> TokenStream667 pub fn value_name(&self) -> TokenStream {
668 self.name.clone().translate(CasingStyle::ScreamingSnake)
669 }
670
parser(&self) -> &Sp<Parser>671 pub fn parser(&self) -> &Sp<Parser> {
672 &self.parser
673 }
674
kind(&self) -> Sp<Kind>675 pub fn kind(&self) -> Sp<Kind> {
676 self.kind.clone()
677 }
678
is_enum(&self) -> bool679 pub fn is_enum(&self) -> bool {
680 self.is_enum
681 }
682
ignore_case(&self) -> TokenStream683 pub fn ignore_case(&self) -> TokenStream {
684 let method = self.find_method("ignore_case");
685
686 if let Some(method) = method {
687 method.args.clone()
688 } else {
689 quote! { false }
690 }
691 }
692
casing(&self) -> Sp<CasingStyle>693 pub fn casing(&self) -> Sp<CasingStyle> {
694 self.casing.clone()
695 }
696
env_casing(&self) -> Sp<CasingStyle>697 pub fn env_casing(&self) -> Sp<CasingStyle> {
698 self.env_casing.clone()
699 }
700
is_positional(&self) -> bool701 pub fn is_positional(&self) -> bool {
702 self.methods
703 .iter()
704 .all(|m| m.name != "long" && m.name != "short")
705 }
706
has_explicit_methods(&self) -> bool707 pub fn has_explicit_methods(&self) -> bool {
708 self.methods
709 .iter()
710 .any(|m| m.name != "help" && m.name != "long_help")
711 }
712 }
713
714 #[allow(clippy::large_enum_variant)]
715 #[derive(Clone)]
716 pub enum Kind {
717 Arg(Sp<Ty>),
718 FromGlobal(Sp<Ty>),
719 Subcommand(Sp<Ty>),
720 Flatten,
721 Skip(Option<Expr>),
722 ExternalSubcommand,
723 }
724
725 #[derive(Clone)]
726 pub struct Method {
727 name: Ident,
728 args: TokenStream,
729 }
730
731 impl Method {
new(name: Ident, args: TokenStream) -> Self732 pub fn new(name: Ident, args: TokenStream) -> Self {
733 Method { name, args }
734 }
735
from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Option<Self>736 fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Option<Self> {
737 let mut lit = match lit {
738 Some(lit) => lit,
739
740 None => match env::var(env_var) {
741 Ok(val) => {
742 if val.is_empty() {
743 return None;
744 }
745 LitStr::new(&val, ident.span())
746 }
747 Err(_) => {
748 abort!(ident,
749 "cannot derive `{}` from Cargo.toml", ident;
750 note = "`{}` environment variable is not set", env_var;
751 help = "use `{} = \"...\"` to set {} manually", ident, ident;
752 );
753 }
754 },
755 };
756
757 if ident == "author" {
758 let edited = process_author_str(&lit.value());
759 lit = LitStr::new(&edited, lit.span());
760 }
761
762 Some(Method::new(ident, quote!(#lit)))
763 }
764 }
765
766 impl ToTokens for Method {
to_tokens(&self, ts: &mut proc_macro2::TokenStream)767 fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
768 let Method { ref name, ref args } = self;
769
770 let tokens = quote!( .#name(#args) );
771
772 tokens.to_tokens(ts);
773 }
774 }
775
776 /// replace all `:` with `, ` when not inside the `<>`
777 ///
778 /// `"author1:author2:author3" => "author1, author2, author3"`
779 /// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
process_author_str(author: &str) -> String780 fn process_author_str(author: &str) -> String {
781 let mut res = String::with_capacity(author.len());
782 let mut inside_angle_braces = 0usize;
783
784 for ch in author.chars() {
785 if inside_angle_braces > 0 && ch == '>' {
786 inside_angle_braces -= 1;
787 res.push(ch);
788 } else if ch == '<' {
789 inside_angle_braces += 1;
790 res.push(ch);
791 } else if inside_angle_braces == 0 && ch == ':' {
792 res.push_str(", ");
793 } else {
794 res.push(ch);
795 }
796 }
797
798 res
799 }
800
801 #[derive(Clone)]
802 pub struct Parser {
803 pub kind: Sp<ParserKind>,
804 pub func: TokenStream,
805 }
806
807 impl Parser {
default_spanned(span: Span) -> Sp<Self>808 fn default_spanned(span: Span) -> Sp<Self> {
809 let kind = Sp::new(ParserKind::TryFromStr, span);
810 let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
811 Sp::new(Parser { kind, func }, span)
812 }
813
from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self>814 fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
815 use self::ParserKind::*;
816
817 let kind = match &*spec.kind.to_string() {
818 "from_str" => FromStr,
819 "try_from_str" => TryFromStr,
820 "from_os_str" => FromOsStr,
821 "try_from_os_str" => TryFromOsStr,
822 "from_occurrences" => FromOccurrences,
823 "from_flag" => FromFlag,
824 s => abort!(spec.kind.span(), "unsupported parser `{}`", s),
825 };
826
827 let func = match spec.parse_func {
828 None => match kind {
829 FromStr | FromOsStr => {
830 quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
831 }
832 TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
833 TryFromOsStr => abort!(
834 spec.kind.span(),
835 "you must set parser for `try_from_os_str` explicitly"
836 ),
837 FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
838 FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
839 },
840
841 Some(func) => match func {
842 Expr::Path(_) => quote!(#func),
843 _ => abort!(func, "`parse` argument must be a function path"),
844 },
845 };
846
847 let kind = Sp::new(kind, spec.kind.span());
848 let parser = Parser { kind, func };
849 Sp::new(parser, parse_ident.span())
850 }
851 }
852
853 #[derive(Debug, PartialEq, Clone)]
854 pub enum ParserKind {
855 FromStr,
856 TryFromStr,
857 FromOsStr,
858 TryFromOsStr,
859 FromOccurrences,
860 FromFlag,
861 }
862
863 /// Defines the casing for the attributes long representation.
864 #[derive(Copy, Clone, Debug, PartialEq)]
865 pub enum CasingStyle {
866 /// Indicate word boundaries with uppercase letter, excluding the first word.
867 Camel,
868 /// Keep all letters lowercase and indicate word boundaries with hyphens.
869 Kebab,
870 /// Indicate word boundaries with uppercase letter, including the first word.
871 Pascal,
872 /// Keep all letters uppercase and indicate word boundaries with underscores.
873 ScreamingSnake,
874 /// Keep all letters lowercase and indicate word boundaries with underscores.
875 Snake,
876 /// Keep all letters lowercase and remove word boundaries.
877 Lower,
878 /// Keep all letters uppercase and remove word boundaries.
879 Upper,
880 /// Use the original attribute name defined in the code.
881 Verbatim,
882 }
883
884 impl CasingStyle {
from_lit(name: LitStr) -> Sp<Self>885 fn from_lit(name: LitStr) -> Sp<Self> {
886 use self::CasingStyle::*;
887
888 let normalized = name.value().to_upper_camel_case().to_lowercase();
889 let cs = |kind| Sp::new(kind, name.span());
890
891 match normalized.as_ref() {
892 "camel" | "camelcase" => cs(Camel),
893 "kebab" | "kebabcase" => cs(Kebab),
894 "pascal" | "pascalcase" => cs(Pascal),
895 "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
896 "snake" | "snakecase" => cs(Snake),
897 "lower" | "lowercase" => cs(Lower),
898 "upper" | "uppercase" => cs(Upper),
899 "verbatim" | "verbatimcase" => cs(Verbatim),
900 s => abort!(name, "unsupported casing: `{}`", s),
901 }
902 }
903 }
904
905 #[derive(Clone)]
906 pub enum Name {
907 Derived(Ident),
908 Assigned(TokenStream),
909 }
910
911 impl Name {
translate(self, style: CasingStyle) -> TokenStream912 pub fn translate(self, style: CasingStyle) -> TokenStream {
913 use CasingStyle::*;
914
915 match self {
916 Name::Assigned(tokens) => tokens,
917 Name::Derived(ident) => {
918 let s = ident.unraw().to_string();
919 let s = match style {
920 Pascal => s.to_upper_camel_case(),
921 Kebab => s.to_kebab_case(),
922 Camel => s.to_lower_camel_case(),
923 ScreamingSnake => s.to_shouty_snake_case(),
924 Snake => s.to_snake_case(),
925 Lower => s.to_snake_case().replace('_', ""),
926 Upper => s.to_shouty_snake_case().replace('_', ""),
927 Verbatim => s,
928 };
929 quote_spanned!(ident.span()=> #s)
930 }
931 }
932 }
933
translate_char(self, style: CasingStyle) -> TokenStream934 pub fn translate_char(self, style: CasingStyle) -> TokenStream {
935 use CasingStyle::*;
936
937 match self {
938 Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
939 Name::Derived(ident) => {
940 let s = ident.unraw().to_string();
941 let s = match style {
942 Pascal => s.to_upper_camel_case(),
943 Kebab => s.to_kebab_case(),
944 Camel => s.to_lower_camel_case(),
945 ScreamingSnake => s.to_shouty_snake_case(),
946 Snake => s.to_snake_case(),
947 Lower => s.to_snake_case(),
948 Upper => s.to_shouty_snake_case(),
949 Verbatim => s,
950 };
951
952 let s = s.chars().next().unwrap();
953 quote_spanned!(ident.span()=> #s)
954 }
955 }
956 }
957 }
958