1use proc_macro::TokenStream;
53use proc_macro2::{Span, TokenStream as TokenStream2};
54use quote::{format_ident, quote};
55use syn::{
56 Data, DeriveInput, Fields, GenericArgument, Ident, LitByteStr, LitChar, LitStr, PathArguments,
57 Type, ext::IdentExt as _, parse_macro_input, spanned::Spanned as _,
58};
59
60mod spec;
61
62#[proc_macro_derive(Args, attributes(arg, winnow_args))]
256pub fn derive_args(input: TokenStream) -> TokenStream {
257 let input = parse_macro_input!(input as DeriveInput);
258 finish(expand(&input))
259}
260
261fn finish(expanded: syn::Result<TokenStream2>) -> TokenStream {
264 let expanded = expanded.unwrap_or_else(syn::Error::into_compile_error);
265 quote! {
266 #expanded
267 const _: ::core::option::Option<&str> = ::core::option_env!("WINNOW_ARGS_SPEC");
268 }
269 .into()
270}
271
272#[proc_macro_derive(Subcommand, attributes(arg, winnow_args))]
351pub fn derive_subcommand(input: TokenStream) -> TokenStream {
352 let input = parse_macro_input!(input as DeriveInput);
353 finish(expand_subcommand(&input))
354}
355
356fn expand_subcommand(input: &DeriveInput) -> syn::Result<TokenStream2> {
358 let Data::Enum(data) = &input.data else {
359 return Err(syn::Error::new(
360 input.span(),
361 "`Subcommand` can only be derived for enums",
362 ));
363 };
364 let mut runs = None;
365 let case = rename_all(input, Some(&mut runs))?;
366 let mut names: Vec<(String, &Ident)> = Vec::new();
367 let mut arms = Vec::new();
368 let mut patterns = Vec::new();
369 let mut subs = Vec::new();
370 let mut held: Vec<(&Ident, Type, bool)> = Vec::new();
372 let spec_on = spec::enabled();
373 let filed = filed(input)?;
374 let identity = spec::identity(input.ident.span(), &filed);
375 let mut spec_variants = Vec::new();
376 let mut spec_checks = Vec::new();
377 let mut structs = Vec::new();
379 for variant in &data.variants {
380 let ident = &variant.ident;
381 let info = variant_names(variant, &mut names, case)?;
382 let pattern = byte_patterns(&info.names);
383 let primary = LitStr::new(&info.names[0], Span::call_site());
384 let fields_of = match &variant.fields {
385 Fields::Named(_) => Some(variant_struct(input, variant)?),
386 _ => None,
387 };
388 let inner = match (&variant.fields, &fields_of) {
389 (Fields::Unnamed(fields), _) if fields.unnamed.len() == 1 => {
390 let ty = &fields.unnamed[0].ty;
391 Some(boxed(ty).unwrap_or(ty).clone())
392 }
393 (_, Some((name, ..))) => Some(syn::parse_quote!(#name)),
394 _ => None,
395 };
396 let mut filed_as = None;
399 if let Some((_, tokens, stated)) = fields_of {
400 structs.push(tokens);
401 filed_as = stated;
402 if runs.is_some() {
403 return Err(syn::Error::new(
404 ident.span(),
405 "`run` calls the type a variant holds, and this variant has fields of its own",
406 ));
407 }
408 }
409 match &inner {
410 Some(_) if matches!(variant.fields, Fields::Named(_)) => {}
411 Some(ty) => {
412 let is_boxed = matches!(&variant.fields, Fields::Unnamed(f) if boxed(&f.unnamed[0].ty).is_some());
413 held.push((ident, ty.clone(), is_boxed));
414 }
415 None if runs.is_some() => {
416 return Err(syn::Error::new(
417 ident.span(),
418 "`run` calls the type a variant holds, and this variant holds none",
419 ));
420 }
421 None => {}
422 }
423 let unit_long_about = inner.is_none().then(|| {
425 let (doc, long_doc) = docs(&variant.attrs);
426 if doc == info.about {
427 long_doc
428 } else {
429 info.about.clone()
430 }
431 });
432 let command = match (&inner, &unit_long_about) {
433 (Some(ty), _) => quote!(<#ty as ::winnow_args::Args>::HELP),
434 (None, long_about) => {
435 let long_about = text(long_about.as_deref().unwrap_or_default());
436 let about = text(&info.about);
437 quote!(&::winnow_args::help::Command {
438 about: #about,
439 long_about: #long_about,
440 help_flag: true,
441 help_short: true,
442 ..::winnow_args::help::Command::EMPTY
443 })
444 }
445 };
446 let about = match (&inner, info.about.is_empty()) {
447 (Some(ty), true) => quote!(<#ty as ::winnow_args::Args>::HELP.about),
448 _ => text(&info.about),
449 };
450 let shown = &info.names[1..=info.shown];
451 let all = &info.names;
452 let hide = info.hide;
453 if spec_on {
454 let ty = match &inner {
455 Some(ty) => {
456 let stated = spec_name(&variant.attrs)?.or(filed_as);
457 let name = spec_type(stated, ty)?;
458 spec_checks.extend(spec_filed(&input.generics, quote!(Args), ty, &name));
459 Some(name)
460 }
461 None => None,
462 };
463 spec_variants.push(spec::VariantDoc {
464 name: info.names[0].clone(),
465 aliases: shown.to_vec(),
466 hidden_aliases: info.names[info.shown + 1..].to_vec(),
467 about: info.about.clone(),
468 long_about: unit_long_about.unwrap_or_default(),
469 hide: info.hide,
470 ty,
471 });
472 }
473 subs.push(quote! {
474 ::winnow_args::help::Sub {
475 name: #primary,
476 aliases: &[#(#shown),*],
477 names: &[#(#all),*],
478 command: #command,
479 about: #about,
480 hide: #hide,
481 }
482 });
483 let read_payload = quote! {
485 |__item| match __item {
486 Self::#ident(__read) => {
487 let _ = __read;
488 }
489 #[allow(unreachable_patterns)]
490 _ => {}
491 }
492 };
493 let parse = match &variant.fields {
494 Fields::Unit => {
495 let index = subs.len() - 1;
496 quote! {
497 __wa::finish_with(
498 __input,
499 __globals,
500 <Self as ::winnow_args::Args>::HELP.subcommands[#index].command,
501 )
502 .map(|()| Self::#ident)
503 }
504 }
505 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
506 let ty = &fields.unnamed[0].ty;
507 match boxed(ty) {
508 Some(inner) => quote! {
509 <#inner as ::winnow_args::Args>::parse_argv_with(__input, __globals)
510 .map(|v| Self::#ident(::std::boxed::Box::new(v)))
511 .inspect(#read_payload)
512 },
513 None => quote! {
514 <#ty as ::winnow_args::Args>::parse_argv_with(__input, __globals)
515 .map(Self::#ident)
516 .inspect(#read_payload)
517 },
518 }
519 }
520 Fields::Named(fields) => {
521 let ty = inner.as_ref().expect("a struct for the fields");
522 let names = fields.named.iter().map(|field| &field.ident);
523 let names: Vec<_> = names.collect();
524 quote! {
525 <#ty as ::winnow_args::Args>::parse_argv_with(__input, __globals)
526 .map(|#ty { #(#names),* }| Self::#ident { #(#names),* })
527 }
528 }
529 _ => {
530 return Err(syn::Error::new(
531 variant.span(),
532 "a subcommand variant is a unit, holds one `Args` type or has named fields",
533 ));
534 }
535 };
536 arms.push(quote!(#pattern => (#parse).map_err(|e| e.within(#primary)),));
537 patterns.push(pattern);
538 }
539 let (about_text, long_about_text) = docs(&input.attrs);
540 let (about, long_about) = (text(&about_text), text(&long_about_text));
541 if spec_on {
542 spec::write_subcommands(
543 input.ident.span(),
544 &filed,
545 &about_text,
546 &long_about_text,
547 &spec_variants,
548 )?;
549 }
550
551 let name = &input.ident;
552 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
553 let run_impl = runs.map(|runs| run_impl(input, runs, &held));
554 Ok(quote! {
555 #(#structs)*
556 #(#spec_checks)*
557 #run_impl
558 impl #impl_generics ::winnow_args::Subcommand for #name #ty_generics #where_clause {
559 const SPEC: &'static str = #identity;
560
561 #[inline]
562 fn has(__name: &[u8]) -> bool {
563 matches!(__name, #(#patterns)|*)
564 }
565
566 fn parse_subcommand(
567 __name: &[u8],
568 __input: &mut ::winnow_args::Argv<'_>,
569 __globals: &mut dyn ::winnow_args::Globals,
570 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
571 use ::winnow_args::__private as __wa;
572 match __name {
573 #(#arms)*
574 _ => ::core::result::Result::Err(__wa::Error::unexpected_arg(
575 __input.offset(),
576 ::std::string::String::from_utf8_lossy(__name),
577 )),
578 }
579 }
580 }
581
582 impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
583 const SPEC: &'static str = #identity;
584
585 #[inline(never)]
588 fn help_data() -> &'static ::winnow_args::help::Command {
589 <Self as ::winnow_args::Args>::HELP
590 }
591
592 const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
593 about: #about,
594 long_about: #long_about,
595 subcommands: &[#(#subs),*],
596 subcommand_required: true,
597 help_flag: true,
598 help_short: true,
599 ..::winnow_args::help::Command::EMPTY
600 };
601
602 fn parse_argv_with(
603 __input: &mut ::winnow_args::Argv<'_>,
604 __globals: &mut dyn ::winnow_args::Globals,
605 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
606 use ::winnow_args::__private as __wa;
607 let __help = <Self as ::winnow_args::Args>::HELP;
608 loop {
610 if __input.is_empty() {
611 return ::core::result::Result::Err(
612 __wa::Error::missing_subcommand(__input.offset()),
613 );
614 }
615 let __arg = __wa::arg(__input)?;
616 match __arg {
617 __wa::Arg::Word(__word) => {
618 if <Self as __wa::Subcommand>::has(&**__word.value) {
619 return <Self as __wa::Subcommand>::parse_subcommand(
620 &**__word.value, __input, __globals,
621 );
622 }
623 if &**__word.value == b"help" {
624 return ::core::result::Result::Err(__wa::help_word(__help, __input));
625 }
626 }
627 __wa::Arg::Long(_) | __wa::Arg::Short(_)
628 if __globals.bind(&__arg, __input)? =>
629 {
630 continue;
631 }
632 __wa::Arg::Long(__flag) if __flag.name == b"help" => {
633 return ::core::result::Result::Err(__wa::Error::help(__help, true));
634 }
635 __wa::Arg::Short(__flag) if __flag.letter == 'h' => {
636 return ::core::result::Result::Err(__wa::Error::help(__help, false));
637 }
638 _ => {}
639 }
640 return ::core::result::Result::Err(__arg.unexpected());
641 }
642 }
643 }
644 })
645}
646
647fn variant_struct(
651 input: &DeriveInput,
652 variant: &syn::Variant,
653) -> syn::Result<(Ident, TokenStream2, Option<String>)> {
654 let Fields::Named(fields) = &variant.fields else {
655 unreachable!("called for a variant with named fields");
656 };
657 if !input.generics.params.is_empty() {
658 return Err(syn::Error::new(
659 variant.ident.span(),
660 "a variant with fields in a generic enum is not supported: hold a struct that derives `Args`",
661 ));
662 }
663 let name = quote::format_ident!("__{}_{}", input.ident, variant.ident);
664 let mut options: Vec<syn::Meta> = Vec::new();
665 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
666 let metas = attr.parse_args_with(
667 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
668 )?;
669 for meta in metas {
670 let key = meta.path().get_ident().map(Ident::to_string);
671 match (key.as_deref(), &meta) {
672 (Some("name" | "alias" | "alias_hidden" | "hide"), _) => {}
674 (Some("help"), syn::Meta::NameValue(pair)) => {
675 let text = &pair.value;
676 options.push(syn::parse_quote!(about = #text));
677 }
678 (Some("long_help"), syn::Meta::NameValue(pair)) => {
679 let text = &pair.value;
680 options.push(syn::parse_quote!(long_about = #text));
681 }
682 _ => options.push(meta),
683 }
684 }
685 }
686 let enum_name = filed(input)?;
690 let stated = (spec_name(&variant.attrs)?.is_none() && input.ident != enum_name.as_str())
691 .then(|| format!("__{enum_name}_{}", variant.ident));
692 if let Some(stated) = &stated {
693 options.push(syn::parse_quote!(spec = #stated));
694 }
695 let docs = variant.attrs.iter().filter(|a| a.path().is_ident("doc"));
696 let docs: Vec<_> = docs.collect();
697 let named = &fields.named;
698 let derived: DeriveInput = syn::parse_quote! {
699 #(#docs)*
700 #[arg(#(#options),*)]
701 struct #name { #named }
702 };
703 let args = expand(&derived)?;
704 let plain = named.iter().map(|field| {
707 let attrs = field.attrs.iter().filter(|a| a.path().is_ident("cfg"));
708 let (ident, ty) = (&field.ident, &field.ty);
709 quote!(#(#attrs)* #ident: #ty)
710 });
711 let tokens = quote! {
712 #[doc(hidden)]
713 #[allow(non_camel_case_types)]
714 struct #name { #(#plain),* }
715 #args
716 };
717 Ok((name, tokens, stated))
718}
719
720fn run_impl(input: &DeriveInput, runs: Runs, held: &[(&Ident, Type, bool)]) -> TokenStream2 {
723 if held.is_empty() {
724 return quote!();
726 }
727 let name = &input.ident;
728 let (_, ty_generics, where_clause) = input.generics.split_for_impl();
729 let mut generics = input.generics.clone();
731 for param in &mut generics.params {
732 match param {
733 syn::GenericParam::Type(param) => (param.eq_token, param.default) = (None, None),
734 syn::GenericParam::Const(param) => (param.eq_token, param.default) = (None, None),
735 syn::GenericParam::Lifetime(_) => {}
736 }
737 }
738 let params = generics.params.iter();
739 let (the_trait, method) = match runs {
740 Runs::Sync => (quote!(::winnow_args::run::Run), quote!(run)),
741 Runs::Async => (quote!(::winnow_args::run::RunAsync), quote!(run_async)),
742 };
743 let bounds = held
745 .iter()
746 .map(|(_, ty, _)| quote!(#ty: #the_trait<__C, Output = __O>));
747 let own_bounds = where_clause
748 .iter()
749 .flat_map(|clause| clause.predicates.iter());
750 let arms = held.iter().map(|(ident, ty, is_boxed)| {
751 let command = match is_boxed {
752 true => quote!(*__command),
753 false => quote!(__command),
754 };
755 let call = quote!(<#ty as #the_trait<__C>>::#method(#command, __context));
756 match runs {
757 Runs::Sync => quote!(Self::#ident(__command) => #call,),
758 Runs::Async => quote!(Self::#ident(__command) => #call.await,),
759 }
760 });
761 let function = match runs {
762 Runs::Sync => quote!(fn run(self, __context: __C) -> Self::Output),
763 Runs::Async => quote!(async fn run_async(self, __context: __C) -> Self::Output),
764 };
765 quote! {
766 #[automatically_derived]
767 #[allow(clippy::future_not_send)]
769 impl<#(#params,)* __C, __O> #the_trait<__C> for #name #ty_generics
770 where
771 #(#own_bounds,)*
772 #(#bounds,)*
773 {
774 type Output = __O;
775
776 #function {
777 match self {
778 #(#arms)*
779 }
780 }
781 }
782 }
783}
784
785#[proc_macro_derive(Occurrence, attributes(arg, winnow_args))]
851pub fn derive_occurrence(input: TokenStream) -> TokenStream {
852 let input = parse_macro_input!(input as DeriveInput);
853 finish(expand_occurrence(&input))
854}
855
856#[proc_macro_derive(ValueEnum, attributes(arg, winnow_args))]
891pub fn derive_value_enum(input: TokenStream) -> TokenStream {
892 let input = parse_macro_input!(input as DeriveInput);
893 finish(expand_value_enum(&input))
894}
895
896fn expand_occurrence(input: &DeriveInput) -> syn::Result<TokenStream2> {
899 let Data::Enum(data) = &input.data else {
900 return Err(syn::Error::new(
901 input.span(),
902 "`Occurrence` can only be derived for enums",
903 ));
904 };
905 let (mut long_arms, mut short_arms) = (Vec::new(), Vec::new());
906 let (mut single_dash, mut prefixes) = (Vec::new(), Vec::new());
907 let mut letters = Vec::new();
908 let (mut word, mut unknown, mut bundle) = (None, None, None);
909 let mut seen = std::collections::HashMap::new();
910 let mut help_items = Vec::new();
912 let spec_on = spec::enabled();
913 let filed = filed(input)?;
914 let identity = spec::identity(input.ident.span(), &filed);
915 let mut spec_rows = Vec::new();
916 let mut spec_checks = Vec::new();
917 let (mut enum_hyphen_values, mut enum_keep_equals) = (false, false);
920 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
921 attr.parse_nested_meta(|meta| {
922 if meta.path.is_ident("allow_hyphen_values") {
923 enum_hyphen_values = true;
924 Ok(())
925 } else if meta.path.is_ident("keep_equals") {
926 enum_keep_equals = true;
927 Ok(())
928 } else if skip_spec(&meta)? {
929 Ok(())
930 } else {
931 Err(meta.error("expected `allow_hyphen_values`, `keep_equals` or `spec`"))
932 }
933 })?;
934 }
935 for variant in &data.variants {
936 let ident = &variant.ident;
937 let (mut short, mut longs, mut positional) = (None, Vec::new(), false);
938 let (mut two_dashes, mut prefix, mut value_name) = (false, false, None);
939 let (mut hyphen_values, mut negative_numbers) = (enum_hyphen_values, false);
940 let (mut require_equals, mut default_missing) = (false, None);
941 let mut keep_equals = enum_keep_equals;
942 let (mut skip, mut is_unknown, mut is_bundle) = (false, false, false);
943 let stated = spec_name(&variant.attrs)?;
944 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
945 attr.parse_nested_meta(|meta| {
946 if meta.path.is_ident("allow_hyphen_values") {
947 hyphen_values = true;
948 } else if meta.path.is_ident("allow_negative_numbers") {
949 negative_numbers = true;
950 } else if meta.path.is_ident("require_equals") {
951 require_equals = true;
952 } else if meta.path.is_ident("keep_equals") {
953 keep_equals = true;
954 } else if meta.path.is_ident("default_missing") {
955 default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
956 } else if meta.path.is_ident("skip") {
957 skip = true;
958 } else if meta.path.is_ident("bundle") {
959 is_bundle = true;
960 } else if meta.path.is_ident("unknown") {
961 is_unknown = true;
962 } else if meta.path.is_ident("short") {
963 if short.is_some() {
964 return Err(meta.error("an `Occurrence` variant has one `short`"));
965 }
966 short = Some(meta.value()?.parse::<LitChar>()?.value());
967 } else if meta.path.is_ident("long") {
968 longs.push(if meta.input.peek(syn::Token![=]) {
969 meta.value()?.parse::<LitStr>()?.value()
970 } else {
971 kebab_case(&ident.to_string())
972 });
973 } else if meta.path.is_ident("alias") {
974 longs.extend(aliases(&meta)?);
975 } else if meta.path.is_ident("positional") {
976 positional = true;
977 } else if meta.path.is_ident("two_dashes") {
978 two_dashes = true;
979 } else if meta.path.is_ident("prefix") {
980 prefix = true;
981 } else if meta.path.is_ident("value_name") {
982 value_name = Some(meta.value()?.parse::<LitStr>()?.value());
983 } else if skip_spec(&meta)? {
984 } else {
985 return Err(meta.error(
986 "expected `short`, `long`, `alias`, `positional`, `two_dashes`, `prefix`, \
987 `value_name`, `allow_hyphen_values`, `allow_negative_numbers`, \
988 `require_equals`, `keep_equals`, `default_missing`, `skip`, `unknown` or `bundle`",
989 ));
990 }
991 Ok(())
992 })?;
993 }
994 let takes = match &variant.fields {
995 Fields::Unit => None,
996 Fields::Unnamed(f) if f.unnamed.len() == 1 => Some(&f.unnamed[0].ty),
997 _ => {
998 return Err(syn::Error::new(
999 variant.span(),
1000 "an `Occurrence` variant is a unit (a switch) or holds one value",
1001 ));
1002 }
1003 };
1004 if skip {
1005 continue;
1007 }
1008 let spanned = takes.and_then(|ty| {
1010 last_segment(ty)
1011 .filter(|s| s.ident == "Spanned")
1012 .and_then(inner)
1013 });
1014 let optional = takes.and_then(|ty| {
1017 last_segment(ty)
1018 .filter(|s| s.ident == "Option")
1019 .and_then(inner)
1020 });
1021 let wrap = |value: TokenStream2, offset: TokenStream2, attached: TokenStream2| {
1022 if spanned.is_some() {
1023 quote! {
1024 ::winnow_args::value::Spanned {
1025 value: #value,
1026 offset: #offset,
1027 attached: #attached,
1028 }
1029 }
1030 } else {
1031 value
1032 }
1033 };
1034 if positional || is_unknown || is_bundle {
1035 let Some(ty) = takes else {
1036 return Err(syn::Error::new(
1037 variant.span(),
1038 "a positional, `unknown` or `bundle` variant holds its value",
1039 ));
1040 };
1041 let ty = spanned.unwrap_or(ty);
1042 let slot = if positional {
1043 &mut word
1044 } else if is_unknown {
1045 &mut unknown
1046 } else {
1047 &mut bundle
1048 };
1049 if slot.is_some() {
1050 return Err(syn::Error::new(
1051 variant.span(),
1052 "at most one positional, one `unknown` and one `bundle` variant",
1053 ));
1054 }
1055 let name = value_name.unwrap_or_else(|| ident.to_string().to_uppercase());
1056 if positional {
1057 help_items.push(occurrence_item(
1058 variant,
1059 None,
1060 &[],
1061 Some(&name),
1062 Some(ty),
1063 false,
1064 false,
1065 ));
1066 if spec_on {
1067 spec_checks.extend(spec_check(&input.generics, Some(ty), stated.as_ref()));
1068 spec_rows.push(occurrence_spec(
1069 variant,
1070 spec::ItemDoc {
1071 value_name: Some(name.clone()),
1072 choices_ty: choices_name(stated.as_ref(), ty),
1073 ..spec::ItemDoc::default()
1074 },
1075 ));
1076 }
1077 }
1078 let value = wrap(
1079 quote!(__word.convert::<#ty>(#name)?),
1080 quote!(__word.offset),
1081 quote!(false),
1082 );
1083 *slot = Some(quote!({
1084 let __item = Self::#ident(#value);
1085 match &__item {
1087 Self::#ident(__read) => {
1088 let _ = __read;
1089 }
1090 #[allow(unreachable_patterns)]
1091 _ => {}
1092 }
1093 ::core::result::Result::Ok(::core::option::Option::Some(__item))
1094 }));
1095 continue;
1096 }
1097 if short.is_none() && longs.is_empty() {
1098 longs.push(kebab_case(&ident.to_string()));
1099 }
1100 let spelled = short
1101 .map(|c| format!("-{c}"))
1102 .into_iter()
1103 .chain(longs.iter().map(|l| format!("--{l}")));
1104 for name in spelled {
1105 if let Some(other) = seen.insert(name.clone(), ident.clone()) {
1106 return Err(syn::Error::new(
1107 variant.span(),
1108 format!("`{name}` is already used by `{other}`"),
1109 ));
1110 }
1111 }
1112 let value = takes.map(|ty| spanned.or(optional).unwrap_or(ty));
1113 help_items.push(occurrence_item(
1114 variant,
1115 short,
1116 &longs,
1117 takes
1118 .is_some()
1119 .then(|| value_name.as_deref().unwrap_or("VALUE")),
1120 value,
1121 require_equals,
1122 default_missing.is_some(),
1123 ));
1124 if spec_on {
1125 let value_ty = takes.map(|ty| spanned.or(optional).unwrap_or(ty));
1126 spec_checks.extend(spec_check(&input.generics, value_ty, stated.as_ref()));
1127 spec_rows.push(occurrence_spec(
1128 variant,
1129 spec::ItemDoc {
1130 short,
1131 long: longs.first().cloned(),
1132 aliases: longs.iter().skip(1).cloned().collect(),
1133 value_name: value_ty
1134 .map(|_| value_name.clone().unwrap_or_else(|| "VALUE".to_owned())),
1135 choices_ty: value_ty.and_then(|ty| choices_name(stated.as_ref(), ty)),
1136 require_equals,
1137 optional_value: default_missing.is_some(),
1138 two_dashes,
1139 prefix,
1140 ..spec::ItemDoc::default()
1141 },
1142 ));
1143 }
1144 let body = match takes {
1145 None => quote! {
1146 __arg.check_switch()?;
1147 ::core::result::Result::Ok(::core::option::Option::Some(Self::#ident))
1148 },
1149 Some(ty) => {
1150 let ty = spanned.or(optional).unwrap_or(ty);
1151 let options = quote! {
1152 ::winnow_args::token::ValueOptions {
1153 negative_numbers: #negative_numbers,
1154 hyphen_values: #hyphen_values,
1155 require_equals: #require_equals,
1156 keep_equals: #keep_equals,
1157 }
1158 };
1159 let attached = spanned
1160 .is_some()
1161 .then(|| quote!(let __attached = __arg.has_attached_value(__input);));
1162 let value = wrap(
1163 quote!(__arg.convert::<#ty>(__value)?),
1164 quote!(__arg.offset()),
1165 quote!(__attached),
1166 );
1167 let item = if optional.is_some() && require_equals && default_missing.is_none() {
1168 quote! {
1169 if let ::core::option::Option::Some(__value) =
1170 __arg.read_value_opt_with(__input, #options)
1171 {
1172 Self::#ident(::core::option::Option::Some(#value))
1173 } else {
1174 Self::#ident(::core::option::Option::None)
1175 }
1176 }
1177 } else {
1178 let read = match &default_missing {
1179 Some(missing) => {
1180 let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
1181 quote! {
1182 __arg.read_value_or_with(
1183 __input,
1184 #options,
1185 ::winnow_args::__private::BStr::new(#bytes),
1186 )
1187 }
1188 }
1189 None => quote!(__arg.read_value_with(__input, #options)?),
1190 };
1191 quote! {{
1192 let __value = #read;
1193 Self::#ident(#value)
1194 }}
1195 };
1196 quote! {
1197 #attached
1198 let __item = #item;
1199 match &__item {
1201 Self::#ident(__read) => {
1202 let _ = __read;
1203 }
1204 #[allow(unreachable_patterns)]
1205 _ => {}
1206 }
1207 ::core::result::Result::Ok(::core::option::Option::Some(__item))
1208 }
1209 }
1210 };
1211 if !longs.is_empty() {
1212 let pattern = byte_patterns(&longs);
1213 long_arms.push(quote!(#pattern => { #body }));
1214 if !two_dashes {
1215 single_dash.extend(longs.iter().cloned());
1216 }
1217 }
1218 if let Some(c) = short {
1219 if prefix {
1220 if !c.is_ascii() || takes.is_none() {
1221 return Err(syn::Error::new(
1222 variant.span(),
1223 "`prefix` is for a value-taking variant with an ASCII `short` letter",
1224 ));
1225 }
1226 prefixes.push(c as u8);
1227 }
1228 let letter = LitChar::new(c, Span::call_site());
1229 let takes_value = takes.is_some();
1230 letters.push(quote!(#letter => ::core::option::Option::Some(#takes_value),));
1231 short_arms.push(quote!(#letter => { #body }));
1232 }
1233 }
1234 let has_positional = word.is_some();
1235 let word =
1236 word.unwrap_or_else(|| quote!(::core::result::Result::Ok(::core::option::Option::None)));
1237 let bundle = bundle.map(|body| {
1238 quote! {
1239 const BUNDLES: bool = true;
1240
1241 fn from_bundle(
1242 __word: &::winnow_args::token::Word<'_>,
1243 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1244 #body
1245 }
1246 }
1247 });
1248 let unknown = unknown.map(|body| {
1249 quote! {
1250 fn from_unknown(
1251 __word: &::winnow_args::token::Word<'_>,
1252 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1253 #body
1254 }
1255 }
1256 });
1257 let is_long = matches_name(&single_dash);
1258 let name = &input.ident;
1259 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1260 if spec_on {
1261 spec::write_occurrence(input.ident.span(), &filed, &spec_rows)?;
1262 }
1263 Ok(quote! {
1264 #(#spec_checks)*
1265 impl #impl_generics ::winnow_args::Occurrence for #name #ty_generics #where_clause {
1266 const SPEC: &'static str = #identity;
1267
1268 const PREFIXES: &'static [u8] = &[#(#prefixes),*];
1269
1270 const POSITIONAL: bool = #has_positional;
1271
1272 const ITEMS: &'static [::winnow_args::help::Item] = &[#(#help_items),*];
1273
1274 fn from_arg<'__i>(
1275 __arg: &::winnow_args::Arg<'__i>,
1276 __input: &mut ::winnow_args::Argv<'__i>,
1277 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1278 let _ = &__input;
1279 match __arg {
1280 ::winnow_args::Arg::Long(__flag) => match __flag.name {
1281 #(#long_arms)*
1282 _ => ::core::result::Result::Ok(::core::option::Option::None),
1283 },
1284 ::winnow_args::Arg::Short(__flag) if !__flag.plus => match __flag.letter {
1285 #(#short_arms)*
1286 _ => ::core::result::Result::Ok(::core::option::Option::None),
1287 },
1288 _ => ::core::result::Result::Ok(::core::option::Option::None),
1289 }
1290 }
1291
1292 fn from_word(
1293 __word: &::winnow_args::token::Word<'_>,
1294 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1295 let _ = __word;
1296 #word
1297 }
1298
1299 fn is_long(__name: &[u8]) -> bool {
1300 #is_long
1301 }
1302
1303 #unknown
1304
1305 #bundle
1306
1307 fn short(__letter: char) -> ::core::option::Option<bool> {
1308 match __letter {
1309 #(#letters)*
1310 _ => ::core::option::Option::None,
1311 }
1312 }
1313 }
1314 })
1315}
1316
1317fn occurrence_item(
1320 variant: &syn::Variant,
1321 short: Option<char>,
1322 longs: &[String],
1323 value_name: Option<&str>,
1324 value: Option<&Type>,
1325 require_equals: bool,
1326 optional_value: bool,
1327) -> TokenStream2 {
1328 let (doc, long_doc) = docs(&variant.attrs);
1329 let (help, long_help) = (text(&doc), text(&long_doc));
1330 let (choices, hint) = match value {
1332 Some(ty) => (
1333 quote!(<#ty as ::winnow_args::FromArg>::CHOICES),
1334 quote!(<#ty as ::winnow_args::FromArg>::HINT),
1335 ),
1336 None => (quote!(&[]), quote!(::winnow_args::complete::Hint::Nothing)),
1337 };
1338 let positional = short.is_none() && longs.is_empty();
1339 let short = opt_char(short);
1340 let long = opt_str(longs.first().map(String::as_str));
1341 let aliases = longs.iter().skip(1);
1342 let value_name = opt_str(value_name);
1343 quote!(::winnow_args::help::Item {
1344 short: #short,
1345 long: #long,
1346 aliases: &[#(#aliases),*],
1347 negate: ::core::option::Option::None,
1348 value_name: #value_name,
1349 help: #help,
1350 long_help: #long_help,
1351 heading: ::core::option::Option::None,
1352 hide: false,
1353 positional: #positional,
1354 required: false,
1355 multiple: #positional,
1356 trailing: false,
1357 default: ::core::option::Option::None,
1358 env: ::core::option::Option::None,
1359 choices: #choices,
1360 require_equals: #require_equals,
1361 global: false,
1362 more_shorts: &[],
1363 plus: ::core::option::Option::None,
1364 optional_value: #optional_value,
1365 values: 1,
1366 more_values: false,
1367 hint: #hint,
1368 complete: ::core::option::Option::None,
1369 warning: "",
1370 })
1371}
1372
1373fn spec_name(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
1376 let mut name = None;
1377 for attr in attrs.iter().filter(|a| is_ours(a)) {
1378 let _ = attr.parse_nested_meta(|meta| {
1380 if meta.path.is_ident("spec") {
1381 name = Some(meta.value()?.parse::<LitStr>()?);
1382 } else if meta.input.peek(syn::Token![=]) {
1383 meta.value()?.parse::<syn::Expr>()?;
1384 } else if meta.input.peek(syn::token::Paren) {
1385 let _content;
1386 syn::parenthesized!(_content in meta.input);
1387 _content.parse::<TokenStream2>()?;
1388 }
1389 Ok(())
1390 });
1391 }
1392 let Some(name) = name else {
1393 return Ok(None);
1394 };
1395 let text = name.value();
1396 if text.is_empty() || text.contains(['/', '\\', ':', '.']) {
1397 return Err(syn::Error::new(
1398 name.span(),
1399 "`spec` is a name as a type's is: a file is named after it",
1400 ));
1401 }
1402 Ok(Some(text))
1403}
1404
1405fn skip_spec(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<bool> {
1407 if !meta.path.is_ident("spec") {
1408 return Ok(false);
1409 }
1410 meta.value()?.parse::<LitStr>()?;
1411 Ok(true)
1412}
1413
1414fn filed(input: &DeriveInput) -> syn::Result<String> {
1416 Ok(spec_name(&input.attrs)?.unwrap_or_else(|| input.ident.unraw().to_string()))
1417}
1418
1419fn type_path(ty: &Type) -> Option<String> {
1428 let Type::Path(path) = boxed(ty).unwrap_or(ty) else {
1429 return None;
1430 };
1431 if path.qself.is_some() {
1432 return None;
1433 }
1434 let mut names: Vec<String> = path
1435 .path
1436 .segments
1437 .iter()
1438 .map(|segment| segment.ident.unraw().to_string())
1439 .collect();
1440 if let [name] = &names[..] {
1441 match spec::origin(ty.span(), name) {
1442 spec::Origin::Here => {}
1443 spec::Origin::Imported(from) => names = from,
1444 spec::Origin::Unknown => names.insert(0, "?".to_owned()),
1445 }
1446 }
1447 let rooted = names
1448 .iter()
1449 .take_while(|name| matches!(name.as_str(), "crate" | "self" | "super"))
1450 .count();
1451 if rooted > 0 {
1452 let supers = names[..rooted]
1453 .iter()
1454 .filter(|name| *name == "super")
1455 .count();
1456 let relative = names[0] != "crate";
1457 let modules = relative.then(|| spec::above(supers)).flatten();
1458 let root = ["crate".to_owned()].into_iter();
1459 names.splice(..rooted, root.chain(modules.unwrap_or_default()));
1460 }
1461 (names.len() > usize::from(rooted > 0)).then(|| names.join("::"))
1462}
1463
1464fn spec_type(stated: Option<String>, ty: &Type) -> syn::Result<String> {
1467 stated.or_else(|| type_path(ty)).ok_or_else(|| {
1468 syn::Error::new(
1469 ty.span(),
1470 "this type needs a name when `WINNOW_ARGS_SPEC` is set: state it, as `spec = \"Name\"`",
1471 )
1472 })
1473}
1474
1475fn spec_filed(
1479 generics: &syn::Generics,
1480 kind: TokenStream2,
1481 ty: &Type,
1482 name: &str,
1483) -> Option<TokenStream2> {
1484 if !generics.params.is_empty() {
1485 return None;
1486 }
1487 let ty = boxed(ty).unwrap_or(ty);
1488 let name = name.strip_prefix("?::").unwrap_or(name);
1489 Some(quote::quote_spanned! {ty.span()=>
1490 const _: () = ::core::assert!(
1491 ::winnow_args::__private::filed_as(<#ty as ::winnow_args::#kind>::SPEC, #name),
1492 "the documentation does not find this type by this path: it is named through an alias or a re-export, its module goes by another name here, or it states a `spec`. State the name it is filed under, as `spec = \"Name\"`",
1493 );
1494 })
1495}
1496
1497fn value_type(field: &Field) -> Option<&Type> {
1499 if !field.takes_value()
1500 || field.keywords
1501 || field.from_str
1502 || matches!(field.role, Role::Subcommand)
1503 {
1504 return None;
1505 }
1506 match &field.kind {
1507 Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty) => Some(ty),
1508 Kind::Switch | Kind::Count(_) => None,
1509 }
1510}
1511
1512fn choices_type(field: &Field) -> Option<&Type> {
1515 value_type(field).filter(|_| field.choices.is_none())
1516}
1517
1518fn spec_check_stated(generics: &syn::Generics, field: &Field) -> Option<TokenStream2> {
1521 let stated = field.choices.as_ref()?;
1522 let ty = value_type(field).filter(|_| generics.params.is_empty())?;
1523 Some(quote::quote_spanned! {ty.span()=>
1524 const _: () = ::core::assert!(
1525 ::winnow_args::__private::stated_choices::<#ty>(&[#(#stated),*]),
1526 "the choices stated on this field are not those of its type, in number, spelling or order",
1527 );
1528 })
1529}
1530
1531fn choices_link(field: &Field) -> Option<String> {
1534 choices_name(field.spec.as_ref(), choices_type(field)?)
1535}
1536
1537fn choices_name(stated: Option<&String>, ty: &Type) -> Option<String> {
1538 stated.cloned().or_else(|| type_path(ty))
1539}
1540
1541fn spec_check(
1545 generics: &syn::Generics,
1546 ty: Option<&Type>,
1547 stated: Option<&String>,
1548) -> Option<TokenStream2> {
1549 let ty = ty.filter(|_| generics.params.is_empty())?;
1550 let name = choices_name(stated, ty)?;
1551 let name = name.strip_prefix("?::").unwrap_or(&name);
1552 Some(quote::quote_spanned! {ty.span()=>
1553 const _: () = ::core::assert!(
1554 ::winnow_args::__private::documented_choices::<#ty>(#name),
1555 "the documentation cannot find the choices of this value: its type is named through an alias or a re-export, states a `spec`, or implements `FromArg` by hand. State the name they are filed under, as `spec = \"Name\"`, or the choices, as `choices(\"a\", \"b\")`",
1556 );
1557 })
1558}
1559
1560fn package_version(version: &Option<TokenStream2>, enabled: bool) -> Option<String> {
1563 if !enabled {
1564 return None;
1565 }
1566 let tokens = version.as_ref()?;
1567 if let Ok(lit) = syn::parse2::<LitStr>(tokens.clone()) {
1568 let text = lit.value();
1569 return (!text.is_empty()).then_some(text);
1570 }
1571 std::env::var("CARGO_PKG_VERSION")
1572 .ok()
1573 .filter(|text| !text.is_empty())
1574}
1575
1576fn spec_item(field: &Field) -> syn::Result<spec::ItemDoc> {
1577 let longs = field.longs();
1578 let keywords = if field.keywords {
1579 match &field.kind {
1580 Kind::Required(ty) => Some(spec_type(field.spec.clone(), ty)?),
1581 _ => {
1582 return Err(syn::Error::new(
1583 field.ident.span(),
1584 "`keywords` needs a named type",
1585 ));
1586 }
1587 }
1588 } else {
1589 None
1590 };
1591 let required = (matches!(field.kind, Kind::Required(_))
1592 && field.default.is_none()
1593 && field.default_fn.is_none()
1594 && field.env.is_none()
1595 && !field.keywords)
1596 || field.required;
1597 Ok(spec::ItemDoc {
1598 short: field.short(),
1599 more_shorts: field.short_aliases.clone(),
1600 plus: field.plus,
1601 long: longs.first().map(|name| (*name).to_owned()),
1602 aliases: longs
1603 .iter()
1604 .skip(1)
1605 .map(|name| (*name).to_owned())
1606 .collect(),
1607 negate: field.negate.clone(),
1608 value_name: if field.is_positional() {
1609 Some(field.display())
1610 } else {
1611 field.value_name.clone()
1612 },
1613 help: field.help.clone(),
1614 long_help: field.long_help.clone(),
1615 heading: field.heading.clone(),
1616 hide: field.hide,
1617 positional: field.is_positional(),
1618 required,
1619 multiple: matches!(field.kind, Kind::Many(_) | Kind::Count(_)),
1620 trailing: field.double_dash() == DoubleDash::Required,
1621 default: field.default.clone().or_else(|| field.default_note.clone()),
1622 env: field.env.clone(),
1623 choices: field.choices.clone().unwrap_or_default(),
1624 choices_ty: choices_link(field),
1625 require_equals: field.require_equals,
1626 global: field.is_global(),
1627 optional_value: field.default_missing.is_some()
1628 || field.require_equals && matches!(field.kind, Kind::Optional(_)),
1629 values: field.values,
1630 more_values: field.values_max.is_some(),
1631 two_dashes: field.two_dashes,
1632 prefix: field.prefix,
1633 stop_flags: field.stop_flags,
1634 keywords,
1635 warning: field.warning.clone(),
1636 effect: field.effect.clone(),
1637 })
1638}
1639
1640fn occurrence_spec(variant: &syn::Variant, mut item: spec::ItemDoc) -> spec::ItemDoc {
1641 let (help, long_help) = docs(&variant.attrs);
1642 item.help = help;
1643 item.long_help = long_help;
1644 item.positional = item.short.is_none() && item.long.is_none();
1645 item.multiple = item.positional;
1646 item
1647}
1648
1649fn expand_value_enum(input: &DeriveInput) -> syn::Result<TokenStream2> {
1651 let Data::Enum(data) = &input.data else {
1652 return Err(syn::Error::new(
1653 input.span(),
1654 "`ValueEnum` can only be derived for enums",
1655 ));
1656 };
1657 let case = rename_all(input, None)?;
1658 let mut names: Vec<(String, &Ident)> = Vec::new();
1659 let mut arms = Vec::new();
1660 let mut visible = Vec::new();
1661 let spec_on = spec::enabled();
1662 let filed = filed(input)?;
1663 let identity = spec::identity(input.ident.span(), &filed);
1664 let mut spec_choices = Vec::new();
1665 for variant in &data.variants {
1666 if !matches!(variant.fields, Fields::Unit) {
1667 return Err(syn::Error::new(
1668 variant.span(),
1669 "a `ValueEnum` variant holds no data",
1670 ));
1671 }
1672 let Variant {
1673 names: spellings,
1674 hide,
1675 ..
1676 } = variant_names(variant, &mut names, case)?;
1677 let name = LitStr::new(&spellings[0], Span::call_site());
1678 if !hide {
1679 if spec_on {
1680 spec_choices.push(spellings[0].clone());
1681 }
1682 visible.push(name);
1683 }
1684 let pattern = byte_patterns(&spellings);
1685 let ident = &variant.ident;
1686 arms.push(quote!(#pattern => ::core::result::Result::Ok(Self::#ident),));
1687 }
1688 let name = &input.ident;
1689 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1690 if spec_on {
1691 spec::write_choices(input.ident.span(), &filed, &spec_choices)?;
1692 }
1693 Ok(quote! {
1694 impl #impl_generics ::winnow_args::FromArg for #name #ty_generics #where_clause {
1695 const CHOICES: &'static [&'static str] = &[#(#visible),*];
1696 const SPEC: &'static str = #identity;
1697
1698 fn from_arg(
1699 __value: &::winnow_args::__private::BStr,
1700 ) -> ::core::result::Result<Self, ::winnow_args::__private::BoxError> {
1701 match &**__value {
1702 #(#arms)*
1703 _ => ::core::result::Result::Err(::std::boxed::Box::new(
1704 ::winnow_args::ChoiceError { choices: Self::CHOICES },
1705 )),
1706 }
1707 }
1708 }
1709 })
1710}
1711
1712fn text(prose: &str) -> TokenStream2 {
1714 if prose.is_empty() {
1715 quote!("")
1716 } else {
1717 quote!(::winnow_args::__text!(#prose))
1718 }
1719}
1720
1721struct Variant {
1723 names: Vec<String>,
1725 shown: usize,
1727 hide: bool,
1728 about: String,
1729}
1730
1731#[derive(Clone, Copy)]
1734enum Case {
1735 Kebab,
1737 Lower,
1739 Upper,
1741 Snake,
1743 Verbatim,
1745}
1746
1747impl Case {
1748 fn apply(self, ident: &str) -> String {
1749 let ident = ident.trim_start_matches("r#");
1750 match self {
1751 Case::Kebab => kebab_case(ident),
1752 Case::Lower => ident.to_lowercase(),
1753 Case::Upper => ident.to_uppercase(),
1754 Case::Snake => kebab_case(ident).replace('-', "_"),
1755 Case::Verbatim => ident.to_owned(),
1756 }
1757 }
1758}
1759
1760#[derive(Clone, Copy, PartialEq)]
1763enum Runs {
1764 Sync,
1765 Async,
1766}
1767
1768fn rename_all(input: &DeriveInput, runs: Option<&mut Option<Runs>>) -> syn::Result<Case> {
1771 let mut case = Case::Kebab;
1772 let mut found = None;
1773 let may_run = runs.is_some();
1774 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
1775 attr.parse_nested_meta(|meta| {
1776 if skip_spec(&meta)? {
1777 return Ok(());
1778 }
1779 let run = match () {
1780 () if meta.path.is_ident("run") => Some(Runs::Sync),
1781 () if meta.path.is_ident("run_async") => Some(Runs::Async),
1782 () => None,
1783 };
1784 if let (Some(run), true) = (run, may_run) {
1785 if found.replace(run).is_some() {
1786 return Err(meta.error("`run` or `run_async`, once"));
1787 }
1788 return Ok(());
1789 }
1790 if !meta.path.is_ident("rename_all") {
1791 return Err(meta.error(match may_run {
1792 true => "expected `rename_all`, `run`, `run_async` or `spec`",
1793 false => "expected `rename_all` or `spec`",
1794 }));
1795 }
1796 let lit = meta.value()?.parse::<LitStr>()?;
1797 case = match lit.value().as_str() {
1798 "kebab-case" => Case::Kebab,
1799 "lowercase" => Case::Lower,
1800 "UPPERCASE" => Case::Upper,
1801 "snake_case" => Case::Snake,
1802 "verbatim" => Case::Verbatim,
1803 _ => {
1804 return Err(syn::Error::new(
1805 lit.span(),
1806 "expected \"kebab-case\", \"lowercase\", \"UPPERCASE\", \"snake_case\" or \"verbatim\"",
1807 ));
1808 }
1809 };
1810 Ok(())
1811 })?;
1812 }
1813 if let Some(runs) = runs {
1814 *runs = found;
1815 }
1816 Ok(case)
1817}
1818
1819fn variant_names<'a>(
1821 variant: &'a syn::Variant,
1822 seen: &mut Vec<(String, &'a Ident)>,
1823 case: Case,
1824) -> syn::Result<Variant> {
1825 let holds_type = !matches!(variant.fields, Fields::Unit);
1826 let (mut name, mut alias, mut hidden, mut hide) = (None, Vec::new(), Vec::new(), false);
1827 let mut help: Option<String> = None;
1828 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
1829 attr.parse_nested_meta(|meta| {
1830 if meta.path.is_ident("name") {
1831 name = Some(meta.value()?.parse::<LitStr>()?.value());
1832 } else if meta.path.is_ident("alias") {
1833 alias.extend(aliases(&meta)?);
1834 } else if meta.path.is_ident("alias_hidden") {
1835 hidden.extend(aliases(&meta)?);
1836 } else if meta.path.is_ident("hide") {
1837 hide = true;
1838 } else if meta.path.is_ident("help") {
1839 help = Some(meta.value()?.parse::<LitStr>()?.value());
1840 } else if meta.path.is_ident("long_help") {
1841 meta.value()?.parse::<LitStr>()?;
1843 } else if skip_spec(&meta)? {
1844 if !holds_type {
1845 return Err(meta
1846 .error("`spec` names the type a variant holds, and this one holds none"));
1847 }
1848 } else if matches!(variant.fields, Fields::Named(_)) {
1849 if meta.input.peek(syn::Token![=]) {
1852 meta.value()?.parse::<syn::Expr>()?;
1853 } else if meta.input.peek(syn::token::Paren) {
1854 let content;
1855 syn::parenthesized!(content in meta.input);
1856 content.parse::<TokenStream2>()?;
1857 }
1858 } else {
1859 return Err(meta.error(
1860 "expected `name`, `alias`, `alias_hidden`, `hide`, `help` or `long_help`",
1861 ));
1862 }
1863 Ok(())
1864 })?;
1865 }
1866 let ident = &variant.ident;
1867 let shown = alias.len();
1868 let names: Vec<String> =
1869 std::iter::once(name.unwrap_or_else(|| case.apply(&ident.to_string())))
1870 .chain(alias)
1871 .chain(hidden)
1872 .collect();
1873 for spelling in &names {
1874 if let Some((_, other)) = seen.iter().find(|(n, _)| n == spelling) {
1875 return Err(syn::Error::new(
1876 ident.span(),
1877 format!("`{spelling}` is already used by `{other}`"),
1878 ));
1879 }
1880 seen.push((spelling.clone(), ident));
1881 }
1882 Ok(Variant {
1883 names,
1884 shown,
1885 hide,
1886 about: help
1887 .map(|h| {
1888 h.split("\n\n")
1889 .next()
1890 .unwrap_or_default()
1891 .replace('\n', " ")
1892 })
1893 .unwrap_or_else(|| docs(&variant.attrs).0),
1894 })
1895}
1896
1897fn docs(attrs: &[syn::Attribute]) -> (String, String) {
1899 let lines: Vec<String> = attrs
1900 .iter()
1901 .filter(|a| a.path().is_ident("doc"))
1902 .filter_map(|a| match &a.meta {
1903 syn::Meta::NameValue(nv) => match &nv.value {
1904 syn::Expr::Lit(syn::ExprLit {
1905 lit: syn::Lit::Str(s),
1906 ..
1907 }) => Some(s.value()),
1908 _ => None,
1909 },
1910 _ => None,
1911 })
1912 .map(|line| {
1913 line.strip_prefix(' ')
1914 .unwrap_or(&line)
1915 .trim_end()
1916 .to_owned()
1917 })
1918 .collect();
1919 let long = lines.join("\n").trim().to_owned();
1920 let short = long
1921 .split("\n\n")
1922 .next()
1923 .unwrap_or_default()
1924 .replace('\n', " ");
1925 (short, long)
1926}
1927
1928fn opt_str(s: Option<&str>) -> TokenStream2 {
1930 match s {
1931 Some(s) => quote!(::core::option::Option::Some(#s)),
1932 None => quote!(::core::option::Option::None),
1933 }
1934}
1935
1936fn opt_char(c: Option<char>) -> TokenStream2 {
1938 match c {
1939 Some(c) => quote!(::core::option::Option::Some(#c)),
1940 None => quote!(::core::option::Option::None),
1941 }
1942}
1943
1944fn byte_patterns<S: AsRef<str>>(names: &[S]) -> TokenStream2 {
1946 let literals = names
1947 .iter()
1948 .map(|n| LitByteStr::new(n.as_ref().as_bytes(), Span::call_site()));
1949 quote!(#(#literals)|*)
1950}
1951
1952fn matches_name(names: &[String]) -> TokenStream2 {
1954 if names.is_empty() {
1955 quote!(false)
1956 } else {
1957 let names = byte_patterns(names);
1958 quote!(matches!(__name, #names))
1959 }
1960}
1961
1962fn aliases(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<Vec<String>> {
1964 if meta.input.peek(syn::Token![=]) {
1965 return Ok(vec![meta.value()?.parse::<LitStr>()?.value()]);
1966 }
1967 let content;
1968 syn::parenthesized!(content in meta.input);
1969 let names = syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
1970 Ok(names.iter().map(LitStr::value).collect())
1971}
1972
1973fn kebab_case(ident: &str) -> String {
1975 let mut out = String::new();
1976 for (i, c) in ident.trim_start_matches("r#").chars().enumerate() {
1977 if c.is_uppercase() {
1978 if i > 0 {
1979 out.push('-');
1980 }
1981 out.extend(c.to_lowercase());
1982 } else {
1983 out.push(c);
1984 }
1985 }
1986 out
1987}
1988
1989fn is_ours(attr: &syn::Attribute) -> bool {
1992 attr.path().is_ident("arg") || attr.path().is_ident("winnow_args")
1993}
1994
1995fn is_bool(ty: &Type) -> bool {
1997 last_segment(ty).is_some_and(|s| s.ident == "bool" && s.arguments.is_none())
1998}
1999
2000fn is_sequence(f: &syn::Field) -> bool {
2002 has_flag(f, "sequence")
2003}
2004
2005fn is_flatten(f: &syn::Field) -> bool {
2007 has_flag(f, "flatten")
2008}
2009
2010fn is_unknown(f: &syn::Field) -> bool {
2012 has_flag(f, "unknown") && !is_sequence(f)
2013}
2014
2015fn is_skipped(f: &syn::Field) -> bool {
2017 has_flag(f, "skip")
2018}
2019
2020fn has_flag(f: &syn::Field, name: &str) -> bool {
2022 f.attrs.iter().filter(|a| is_ours(a)).any(|a| {
2023 let mut found = false;
2024 let _ = a.parse_nested_meta(|meta| {
2025 if meta.path.is_ident(name) {
2026 found = true;
2027 } else if meta.input.peek(syn::Token![=]) {
2028 meta.value()?.parse::<syn::Expr>()?;
2029 } else if meta.input.peek(syn::token::Paren) {
2030 let _content;
2031 syn::parenthesized!(_content in meta.input);
2032 }
2033 Ok(())
2034 });
2035 found
2036 })
2037}
2038
2039fn role_alone(f: &syn::Field) -> syn::Result<()> {
2042 let role = ["skip", "flatten", "sequence", "unknown"]
2043 .into_iter()
2044 .find(|role| has_flag(f, role));
2045 let Some(role) = role else {
2046 return Ok(());
2047 };
2048 for attr in f.attrs.iter().filter(|a| is_ours(a)) {
2049 attr.parse_nested_meta(|meta| {
2050 let alone =
2051 meta.path.is_ident(role) || (role == "sequence" && meta.path.is_ident("unknown"));
2052 let named = matches!(role, "flatten" | "sequence") && skip_spec(&meta)?;
2055 if alone || named {
2056 Ok(())
2057 } else {
2058 Err(meta.error(format!("a `{role}` field takes no other `arg` options")))
2059 }
2060 })?;
2061 }
2062 Ok(())
2063}
2064
2065fn boxed(ty: &Type) -> Option<&Type> {
2067 let last = last_segment(ty)?;
2068 if last.ident == "Box" {
2069 inner(last)
2070 } else {
2071 None
2072 }
2073}
2074
2075enum Kind {
2076 Switch,
2078 Count(Type),
2080 Optional(Type),
2082 Required(Type),
2084 Many(Type),
2086}
2087
2088enum Role {
2089 Flag {
2090 short: Option<char>,
2091 long: Option<String>,
2092 aliases: Vec<String>,
2094 global: bool,
2096 },
2097 Positional {
2098 name: String,
2099 double_dash: DoubleDash,
2100 },
2101 Subcommand,
2102}
2103
2104#[derive(Clone, Copy, PartialEq, Eq)]
2106enum DoubleDash {
2107 Optional,
2109 Required,
2111 Automatic,
2113 Preserve,
2115}
2116
2117struct Field {
2118 ident: Ident,
2119 spec: Option<String>,
2122 kind: Kind,
2123 role: Role,
2124 delimiter: Option<u8>,
2126 choices: Option<Vec<String>>,
2128 env: Option<String>,
2130 default: Option<String>,
2132 conflicts: Vec<String>,
2134 overrides: Vec<String>,
2136 requires: Vec<String>,
2138 required: bool,
2140 required_unless: Vec<String>,
2142 group: Option<String>,
2144 default_missing: Option<String>,
2146 help: String,
2148 long_help: String,
2149 heading: Option<String>,
2150 hide: bool,
2151 value_name: Option<String>,
2153 hint: Option<syn::Ident>,
2156 complete: Option<syn::Path>,
2158 warning: String,
2160 from_str: bool,
2162 effect: Option<String>,
2165 negative_numbers: bool,
2167 hyphen_values: bool,
2169 require_equals: bool,
2171 keep_equals: bool,
2173 negate: Option<String>,
2176 two_dashes: bool,
2178 values: usize,
2180 values_max: Option<usize>,
2183 value_terminator: Option<String>,
2185 default_fn: Option<syn::ExprPath>,
2187 default_note: Option<String>,
2189 default_ifs: Vec<(String, Option<String>, String)>,
2191 stop_flags: bool,
2193 plus: Option<char>,
2195 tristate: bool,
2198 keywords: bool,
2201 short_aliases: Vec<char>,
2203 prefix: bool,
2206}
2207
2208impl Field {
2209 fn help_item(&self) -> TokenStream2 {
2211 let short = opt_char(self.short());
2212 let long = opt_str(self.longs().first().copied());
2213 let aliases = self.longs().into_iter().skip(1);
2214 let negate = opt_str(self.negate.as_deref());
2215 let require_equals = self.require_equals;
2216 let global = self.is_global();
2217 let positional = self.is_positional();
2218 let trailing = self.double_dash() == DoubleDash::Required;
2219 let display = self.display();
2220 let value_name = if positional {
2221 opt_str(Some(&display))
2222 } else {
2223 opt_str(self.value_name.as_deref())
2224 };
2225 let (help, long_help) = (text(&self.help), text(&self.long_help));
2226 let heading = opt_str(self.heading.as_deref());
2227 let warning = text(&self.warning);
2228 let hide = self.hide;
2229 let more_shorts = self.short_aliases.iter();
2230 let plus = opt_char(self.plus);
2231 let optional_value = self.default_missing.is_some()
2232 || require_equals && matches!(self.kind, Kind::Optional(_));
2233 let values = self.values;
2234 let more_values = self.values_max.is_some();
2235 let hint = match (&self.hint, &self.kind, &self.role) {
2236 (Some(hint), _, _) => quote!(::winnow_args::complete::Hint::#hint),
2237 (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), role)
2238 if !matches!(role, Role::Subcommand)
2239 && !self.keywords
2240 && !self.tristate
2241 && !self.from_str =>
2242 {
2243 quote!(<#ty as ::winnow_args::FromArg>::HINT)
2244 }
2245 (None, _, _) => quote!(::winnow_args::complete::Hint::Nothing),
2246 };
2247 let complete = match &self.complete {
2248 Some(function) => quote!(::core::option::Option::Some(#function)),
2249 None => quote!(::core::option::Option::None),
2250 };
2251 let required = matches!(self.kind, Kind::Required(_))
2252 && self.default.is_none()
2253 && self.default_fn.is_none()
2254 && self.env.is_none()
2255 && !self.keywords
2256 || self.required;
2257 let multiple = matches!(self.kind, Kind::Many(_) | Kind::Count(_));
2258 let default = opt_str(self.default.as_deref().or(self.default_note.as_deref()));
2259 let env = opt_str(self.env.as_deref());
2260 let choices = match (&self.choices, &self.kind, &self.role) {
2262 (Some(choices), _, _) => quote!(&[#(#choices),*]),
2263 (None, _, Role::Subcommand) | (None, Kind::Switch | Kind::Count(_), _) => quote!(&[]),
2264 (None, _, _) if self.keywords || self.tristate || self.from_str => quote!(&[]),
2265 (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), _) => {
2266 quote!(<#ty as ::winnow_args::FromArg>::CHOICES)
2267 }
2268 };
2269 quote! {
2270 ::winnow_args::help::Item {
2271 short: #short,
2272 long: #long,
2273 aliases: &[#(#aliases),*],
2274 negate: #negate,
2275 value_name: #value_name,
2276 help: #help,
2277 long_help: #long_help,
2278 heading: #heading,
2279 hide: #hide,
2280 positional: #positional,
2281 required: #required,
2282 multiple: #multiple,
2283 trailing: #trailing,
2284 default: #default,
2285 env: #env,
2286 choices: #choices,
2287 require_equals: #require_equals,
2288 global: #global,
2289 more_shorts: &[#(#more_shorts),*],
2290 plus: #plus,
2291 optional_value: #optional_value,
2292 values: #values,
2293 more_values: #more_values,
2294 hint: #hint,
2295 complete: #complete,
2296 warning: #warning,
2297 }
2298 }
2299 }
2300
2301 fn value_options(&self) -> TokenStream2 {
2303 let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
2304 self.negative_numbers,
2305 self.hyphen_values,
2306 self.require_equals,
2307 self.keep_equals,
2308 );
2309 quote! {
2310 __wa::ValueOptions {
2311 negative_numbers: #negative_numbers,
2312 hyphen_values: #hyphen_values,
2313 require_equals: #require_equals,
2314 keep_equals: #keep_equals,
2315 }
2316 }
2317 }
2318
2319 fn read(&self) -> TokenStream2 {
2321 let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
2322 self.negative_numbers,
2323 self.hyphen_values,
2324 self.require_equals,
2325 self.keep_equals,
2326 );
2327 let options = self.value_options();
2328 match &self.default_missing {
2329 None if !negative_numbers && !hyphen_values && !require_equals && !keep_equals => {
2330 quote!(__arg.read_value(__input)?)
2331 }
2332 None => quote!(__arg.read_value_with(__input, #options)?),
2333 Some(missing) => {
2334 let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
2335 quote!(__arg.read_value_or_with(__input, #options, __wa::BStr::new(#bytes)))
2336 }
2337 }
2338 }
2339
2340 fn check(
2342 &self,
2343 value: TokenStream2,
2344 error: impl FnOnce(TokenStream2) -> TokenStream2,
2345 ) -> TokenStream2 {
2346 let Some(choices) = &self.choices else {
2347 return quote!();
2348 };
2349 let pattern = byte_patterns(choices);
2350 let listed = choices.iter().map(|c| LitStr::new(c, Span::call_site()));
2351 let cause = quote!(::winnow_args::ChoiceError { choices: &[#(#listed),*] });
2352 let error = error(cause);
2353 quote! {
2354 if !matches!(&**#value, #pattern) {
2355 return ::core::result::Result::Err(#error);
2356 }
2357 }
2358 }
2359
2360 fn parsed(&self, ty: &Type) -> (TokenStream2, TokenStream2) {
2363 if self.from_str {
2364 (quote!(::winnow_args::value::Parsed<#ty>), quote!(.0))
2365 } else {
2366 (quote!(#ty), quote!())
2367 }
2368 }
2369
2370 fn flag_value(&self, ty: &Type, value: TokenStream2) -> TokenStream2 {
2372 let check = self.check(value.clone(), |cause| {
2373 quote!(__wa::Error::invalid_value(__arg.offset(), __arg.spelling(), #value, #cause))
2374 });
2375 if self.from_str {
2376 return quote!({ #check __arg.convert::<::winnow_args::value::Parsed<#ty>>(#value)?.0 });
2377 }
2378 quote!({ #check __arg.convert::<#ty>(#value)? })
2379 }
2380
2381 fn source_value(&self, ty: &Type, value: TokenStream2, source: &str) -> TokenStream2 {
2384 let source = LitStr::new(source, Span::call_site());
2385 let check = self.check(
2386 value.clone(),
2387 |cause| quote!(__wa::Error::invalid_value(__input.offset(), #source, #value, #cause)),
2388 );
2389 let (ty, unwrap) = self.parsed(ty);
2390 quote!({
2391 #check
2392 <#ty as __wa::FromArg>::from_arg(#value).map_err(|cause| {
2393 __wa::Error::invalid_value(__input.offset(), #source, #value, cause)
2394 })? #unwrap
2395 })
2396 }
2397
2398 fn has(&self) -> TokenStream2 {
2401 let slot = slot(&self.ident);
2402 if self.keywords {
2403 return quote!((!#slot.is_empty()));
2404 }
2405 match &self.kind {
2406 Kind::Switch if self.negate.is_some() => {
2407 quote!((#slot == ::core::option::Option::Some(true)))
2408 }
2409 Kind::Switch => quote!(#slot),
2410 Kind::Count(_) => quote!((#slot != 0)),
2411 Kind::Optional(_) | Kind::Required(_) => quote!(#slot.is_some()),
2412 Kind::Many(_) => quote!((!#slot.is_empty())),
2413 }
2414 }
2415
2416 fn assign(&self, value: TokenStream2, source: &str) -> TokenStream2 {
2419 let slot = slot(&self.ident);
2420 match &self.kind {
2421 Kind::Optional(ty) | Kind::Required(ty) => {
2422 let value = self.source_value(ty, value, source);
2423 quote!(#slot = ::core::option::Option::Some(#value);)
2424 }
2425 Kind::Many(ty) => match self.delimiter {
2426 None => {
2427 let value = self.source_value(ty, value, source);
2428 quote!(#slot.push(#value);)
2429 }
2430 Some(d) => {
2431 let piece = self.source_value(ty, quote!(__piece), source);
2432 quote!(for __piece in __wa::split(#value, #d) { #slot.push(#piece); })
2433 }
2434 },
2435 Kind::Switch | Kind::Count(_) => unreachable!("a switch or a count takes no value"),
2436 }
2437 }
2438
2439 fn unset(&self, displaced: bool) -> TokenStream2 {
2441 let slot = slot(&self.ident);
2442 if self.negate.is_some() {
2443 return quote!(#slot.is_none());
2444 }
2445 let has = self.has();
2446 let not_displaced = displaced.then(|| {
2447 let displaced = displaced_flag(&self.ident);
2448 quote!(&& !#displaced)
2449 });
2450 quote!(!#has #not_displaced)
2451 }
2452
2453 fn fallback(&self, env: bool, displaced: bool) -> TokenStream2 {
2456 let slot = slot(&self.ident);
2457 if self.negate.is_some() {
2458 return self.negatable_fallback(env);
2459 }
2460 let unset = self.unset(displaced);
2461 let assign = |value: TokenStream2, source: &str| self.assign(value, source);
2462 if !env {
2463 if let Some(function) = &self.default_fn {
2464 let set = match &self.kind {
2465 Kind::Many(_) => quote!(#slot = #function();),
2466 _ => quote!(#slot = ::core::option::Option::Some(#function());),
2467 };
2468 return quote!(if #unset { #set });
2469 }
2470 return self
2471 .default
2472 .as_ref()
2473 .map(|value| {
2474 let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
2475 let assign = assign(quote!(__wa::BStr::new(#bytes)), &self.display());
2476 quote!(if #unset { #assign })
2477 })
2478 .unwrap_or_default();
2479 }
2480 self.env
2481 .as_ref()
2482 .map(|var| {
2483 let name = LitStr::new(var, Span::call_site());
2484 let apply = match &self.kind {
2485 Kind::Switch => quote!(#slot = __wa::env::truthy(&__raw);),
2486 Kind::Count(ty) => quote! {
2487 if let ::core::option::Option::Some(::core::result::Result::Ok(__n)) =
2488 __raw.to_str().map(str::parse::<#ty>)
2489 {
2490 #slot = __n;
2491 }
2492 },
2493 _ => {
2494 let assign = assign(quote!(__value), &format!("${var}"));
2495 quote! {
2496 let __value = __wa::BStr::new(__raw.as_encoded_bytes());
2497 #assign
2498 }
2499 }
2500 };
2501 quote! {
2502 if #unset {
2503 if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
2504 #apply
2505 }
2506 }
2507 }
2508 })
2509 .unwrap_or_default()
2510 }
2511
2512 fn negatable_fallback(&self, env: bool) -> TokenStream2 {
2514 let slot = slot(&self.ident);
2515 let value = if env {
2516 let Some(var) = &self.env else {
2517 return quote!();
2518 };
2519 let name = LitStr::new(var, Span::call_site());
2520 quote! {
2521 if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
2522 #slot = ::core::option::Option::Some(__wa::env::truthy(&__raw));
2523 }
2524 }
2525 } else {
2526 match (&self.default_fn, &self.default) {
2527 (Some(function), _) => quote!(#slot = ::core::option::Option::Some(#function());),
2528 (None, Some(default)) => {
2529 let default = default == "true";
2530 quote!(#slot = ::core::option::Option::Some(#default);)
2531 }
2532 (None, None) => return quote!(),
2533 }
2534 };
2535 quote!(if #slot.is_none() { #value })
2536 }
2537
2538 fn word_value(&self, ty: &Type, word: TokenStream2, display: &LitStr) -> TokenStream2 {
2540 let check = self.check(
2541 quote!(#word.value),
2542 |cause| quote!(__wa::Error::invalid_value(#word.offset, #display, #word.value, #cause)),
2543 );
2544 let (ty, unwrap) = self.parsed(ty);
2545 quote!({ #check #word.convert::<#ty>(#display)? #unwrap })
2546 }
2547
2548 fn display(&self) -> String {
2550 match &self.role {
2551 Role::Flag { long: Some(l), .. } => format!("--{l}"),
2552 Role::Flag { short: Some(c), .. } => format!("-{c}"),
2553 Role::Flag { .. } => match self.plus {
2554 Some(c) => format!("+{c}"),
2555 None => unreachable!("every flag has a name"),
2556 },
2557 Role::Positional { name, .. } => name.clone(),
2558 Role::Subcommand => "<COMMAND>".to_owned(),
2559 }
2560 }
2561
2562 fn short_pattern(&self) -> Option<TokenStream2> {
2564 let first = self.short()?;
2565 let letters = std::iter::once(first)
2566 .chain(self.short_aliases.iter().copied())
2567 .map(|c| LitChar::new(c, Span::call_site()));
2568 Some(quote!(#(#letters)|*))
2569 }
2570
2571 fn shorts(&self) -> Vec<char> {
2573 self.short()
2574 .into_iter()
2575 .chain(self.short_aliases.iter().copied())
2576 .collect()
2577 }
2578
2579 fn short(&self) -> Option<char> {
2580 match self.role {
2581 Role::Flag { short, .. } => short,
2582 Role::Positional { .. } | Role::Subcommand => None,
2583 }
2584 }
2585
2586 fn longs(&self) -> Vec<&str> {
2588 match &self.role {
2589 Role::Flag { long, aliases, .. } => {
2590 long.iter().chain(aliases).map(String::as_str).collect()
2591 }
2592 Role::Positional { .. } | Role::Subcommand => Vec::new(),
2593 }
2594 }
2595
2596 fn long_pattern(&self) -> Option<TokenStream2> {
2598 let longs = self.longs();
2599 (!longs.is_empty()).then(|| byte_patterns(&longs))
2600 }
2601
2602 fn takes_value(&self) -> bool {
2604 !self.tristate && !matches!(self.kind, Kind::Switch | Kind::Count(_))
2605 }
2606
2607 fn is_positional(&self) -> bool {
2608 matches!(self.role, Role::Positional { .. })
2609 }
2610
2611 fn is_global(&self) -> bool {
2612 matches!(self.role, Role::Flag { global: true, .. })
2613 }
2614
2615 fn double_dash(&self) -> DoubleDash {
2617 match self.role {
2618 Role::Positional { double_dash, .. } => double_dash,
2619 _ => DoubleDash::Optional,
2620 }
2621 }
2622}
2623
2624fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
2625 let Data::Struct(data) = &input.data else {
2626 return Err(syn::Error::new(
2627 input.span(),
2628 "`Args` can only be derived for structs",
2629 ));
2630 };
2631 let Fields::Named(named) = &data.fields else {
2632 return Err(syn::Error::new(
2633 data.fields.span(),
2634 "`Args` needs a struct with named fields",
2635 ));
2636 };
2637 for f in &named.named {
2638 role_alone(f)?;
2639 }
2640 let StructOptions {
2641 groups,
2642 restart_token,
2643 default_subcommand,
2644 default_subcommand_flags,
2645 default_subcommand_help,
2646 flatten_help,
2647 next_help_heading,
2648 headings,
2649 examples,
2650 effect,
2651 outputs,
2652 exit_codes,
2653 arg_required_else_help,
2654 name: program_name,
2655 version,
2656 about,
2657 long_about,
2658 after_help,
2659 after_long_help,
2660 disable_help_flag,
2661 disable_help_short,
2662 disable_version_flag,
2663 disable_help_subcommand,
2664 unknown_flags_value,
2665 long_only,
2666 plus_options,
2667 } = struct_options(input)?;
2668 let mut fields = named
2669 .named
2670 .iter()
2671 .filter(|f| !is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
2672 .map(field)
2673 .collect::<syn::Result<Vec<_>>>()?;
2674 for field in fields.iter_mut().filter(|field| field.heading.is_none()) {
2675 field.heading.clone_from(&next_help_heading);
2676 }
2677 let fields = fields;
2678 let skipped: Vec<&Ident> = named
2679 .named
2680 .iter()
2681 .filter(|f| is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
2682 .filter_map(|f| f.ident.as_ref())
2683 .collect();
2684 let sequences: Vec<&syn::Field> = named.named.iter().filter(|f| is_sequence(f)).collect();
2687 if let Some(extra) = sequences.get(1) {
2688 return Err(syn::Error::new(
2689 extra.span(),
2690 "a struct has at most one `sequence` field",
2691 ));
2692 }
2693 let sequence_unknown = sequences.first().is_some_and(|f| has_flag(f, "unknown"));
2696 let sequence = vec_field(
2697 sequences.first().copied(),
2698 "a `sequence` field is a `Vec<T>` of an `Occurrence` enum",
2699 )?;
2700 let sequence_ty = sequence.as_ref().map(|(_, ty)| ty);
2701 let unknowns: Vec<&syn::Field> = named.named.iter().filter(|f| is_unknown(f)).collect();
2703 if let Some(extra) = unknowns.get(1) {
2704 return Err(syn::Error::new(
2705 extra.span(),
2706 "a struct has at most one `unknown` field",
2707 ));
2708 }
2709 let unknown = vec_field(
2710 unknowns.first().copied(),
2711 "an `unknown` field is a `Vec<T>`",
2712 )?;
2713 let flattens: Vec<(Ident, Type)> = named
2715 .named
2716 .iter()
2717 .filter(|f| is_flatten(f))
2718 .map(|f| (f.ident.clone().expect("named field"), f.ty.clone()))
2719 .collect();
2720 if !flattens.is_empty() && !input.generics.params.is_empty() {
2721 return Err(syn::Error::new(
2722 input.generics.span(),
2723 "`flatten` in a generic struct is not supported yet",
2724 ));
2725 }
2726 check_duplicates(&fields)?;
2727 let positionals: Vec<&Field> = fields
2730 .iter()
2731 .filter(|f| f.is_positional() && f.double_dash() != DoubleDash::Required)
2732 .collect();
2733 check_positional_order(&positionals)?;
2734 let trailing: Vec<&Field> = fields
2735 .iter()
2736 .filter(|f| f.double_dash() == DoubleDash::Required)
2737 .collect();
2738 if let Some(extra) = trailing.get(1) {
2739 return Err(syn::Error::new(
2740 extra.ident.span(),
2741 "a struct has at most one `double_dash = \"required\"` positional",
2742 ));
2743 }
2744 let trailing = trailing.first().copied();
2745 let preserving: Vec<usize> = positionals
2747 .iter()
2748 .enumerate()
2749 .filter(|(_, f)| f.double_dash() == DoubleDash::Preserve)
2750 .map(|(i, _)| i)
2751 .collect();
2752 if let (Some(t), false) = (trailing, preserving.is_empty()) {
2753 return Err(syn::Error::new(
2754 t.ident.span(),
2755 "a `double_dash = \"preserve\"` positional keeps the `--` this one waits for",
2756 ));
2757 }
2758 let (doc_about, doc_long_about) = docs(&input.attrs);
2759 let about = about.unwrap_or(doc_about);
2760 let long_about = long_about.unwrap_or(doc_long_about);
2761 let (about_text, long_about_text) = (about, long_about);
2762 let (after_text, after_long_text) = (after_help, after_long_help);
2763 let (about, long_about) = (text(&about_text), text(&long_about_text));
2764 let (after_help, after_long_help) = (text(&after_text), text(&after_long_text));
2765 let rules = Rules::new(&fields, &groups)?;
2766
2767 let name = &input.ident;
2768 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2769
2770 let slot_parts: Vec<(Ident, TokenStream2, TokenStream2)> = fields
2772 .iter()
2773 .map(|f| {
2774 let none = quote!(::core::option::Option::None);
2775 let new = quote!(::std::vec::Vec::new());
2776 let (ty, init) = match &f.kind {
2777 _ if f.keywords => (quote!(::std::vec::Vec<&__wa::BStr>), new),
2779 Kind::Switch if f.negate.is_some() => (quote!(::core::option::Option<bool>), none),
2780 Kind::Switch => (quote!(bool), quote!(false)),
2781 Kind::Count(ty) => (quote!(#ty), quote!(0)),
2782 Kind::Optional(ty) | Kind::Required(ty) => {
2783 (quote!(::core::option::Option<#ty>), none)
2784 }
2785 Kind::Many(ty) => (quote!(::std::vec::Vec<#ty>), new),
2786 };
2787 (slot(&f.ident), ty, init)
2788 })
2789 .collect();
2790 let slots = slot_parts
2791 .iter()
2792 .map(|(ident, ty, init)| quote!(let mut #ident: #ty = #init;));
2793
2794 let store = |f: &Field| {
2796 let ident = slot(&f.ident);
2797 let displace = rules.displace(&fields, f);
2798 let stored = match &f.kind {
2799 _ if f.keywords => {
2800 let read = f.read();
2801 quote! {
2802 let __value = #read;
2803 #ident.push(__value);
2804 }
2805 }
2806 _ if f.tristate || f.negate.is_some() => quote! {
2807 __arg.check_switch()?;
2808 #ident = ::core::option::Option::Some(true);
2809 },
2810 Kind::Switch => quote! {
2811 __arg.check_switch()?;
2812 #ident = true;
2813 },
2814 Kind::Count(_) => quote! {
2815 __arg.check_switch()?;
2816 #ident = #ident.saturating_add(1);
2817 },
2818 Kind::Required(ty) => {
2819 let value = f.flag_value(ty, quote!(__value));
2820 let read = f.read();
2821 quote! {
2822 let __value = #read;
2823 #ident = ::core::option::Option::Some(#value);
2824 }
2825 }
2826 Kind::Optional(ty) => {
2827 let value = f.flag_value(ty, quote!(__value));
2828 match &f.default_missing {
2829 Some(_) => {
2833 let read = f.read();
2834 quote! {
2835 let __value = #read;
2836 #ident = ::core::option::Option::Some(#value);
2837 }
2838 }
2839 None if f.require_equals => {
2840 let options = f.value_options();
2841 quote! {
2842 if let ::core::option::Option::Some(__value) =
2843 __arg.read_value_opt_with(__input, #options)
2844 {
2845 #ident = ::core::option::Option::Some(#value);
2846 }
2847 }
2848 }
2849 None => {
2850 let read = f.read();
2851 quote! {
2852 let __value = #read;
2853 #ident = ::core::option::Option::Some(#value);
2854 }
2855 }
2856 }
2857 }
2858 Kind::Many(ty) => match f.delimiter {
2859 None => {
2860 let value = f.flag_value(ty, quote!(__value));
2861 let read = f.read();
2862 let more = match f.values_max {
2863 None => (f.values > 1).then(|| {
2865 let more = f.values - 1;
2866 quote! {
2867 for _ in 0..#more {
2868 let __value = __arg.read_next(__input)?;
2869 #ident.push(#value);
2870 }
2871 }
2872 }),
2873 Some(max) => {
2875 let (min, options) = (f.values, f.value_options());
2876 let terminator = match &f.value_terminator {
2877 Some(word) => {
2878 let word = LitByteStr::new(word.as_bytes(), Span::call_site());
2879 quote!(::core::option::Option::Some(__wa::BStr::new(#word)))
2880 }
2881 None => quote!(::core::option::Option::None),
2882 };
2883 Some(quote! {
2884 let mut __taken = 1usize;
2885 while __taken < #max {
2886 let ::core::option::Option::Some(__value) =
2887 __arg.read_more(__input, #options, #terminator)
2888 else {
2889 break;
2890 };
2891 #ident.push(#value);
2892 __taken += 1;
2893 }
2894 __arg.check_values(__taken, #min)?;
2895 })
2896 }
2897 };
2898 quote! {
2899 let __value = #read;
2900 #ident.push(#value);
2901 #more
2902 }
2903 }
2904 Some(d) => {
2905 let value = f.flag_value(ty, quote!(__piece));
2906 let read = f.read();
2907 quote! {
2908 for __piece in __wa::split(#read, #d) {
2909 #ident.push(#value);
2910 }
2911 }
2912 }
2913 },
2914 };
2915 quote!(#stored #displace)
2916 };
2917
2918 let negated = |f: &Field| {
2919 let no = LitByteStr::new(f.negate.as_ref()?.as_bytes(), Span::call_site());
2920 let ident = slot(&f.ident);
2921 let displace = rules.displace(&fields, f);
2922 Some((
2923 quote!(#no),
2924 quote! {
2925 __arg.check_switch()?;
2926 #ident = ::core::option::Option::Some(false);
2927 #displace
2928 },
2929 ))
2930 };
2931 let plus = |f: &Field| {
2933 let letter = LitChar::new(f.plus?, Span::call_site());
2934 let body = if f.tristate {
2935 let ident = slot(&f.ident);
2936 let displace = rules.displace(&fields, f);
2937 quote!(#ident = ::core::option::Option::Some(false); #displace)
2938 } else {
2939 store(f)
2940 };
2941 Some((quote!(#letter), body))
2942 };
2943 type Part<'a> = &'a dyn Fn(&Field) -> Option<(TokenStream2, TokenStream2)>;
2946 let arms = |keep: &dyn Fn(&Field) -> bool, tail: TokenStream2| {
2947 let of = |part: Part<'_>| -> Vec<TokenStream2> {
2948 fields
2949 .iter()
2950 .filter(|f| keep(f))
2951 .filter_map(part)
2952 .map(|(pattern, body)| quote!(#pattern => { #body #tail }))
2953 .collect()
2954 };
2955 let mut long = of(&|f| Some((f.long_pattern()?, store(f))));
2956 long.extend(of(&negated));
2957 let short = of(&|f| Some((f.short_pattern()?, store(f))));
2958 (long, short, of(&plus))
2959 };
2960 let (long_arms, short_arms, plus_arms) = arms(&|_| true, quote!());
2961
2962 let unexpected = quote!(return ::core::result::Result::Err(__arg.unexpected()));
2963 let subcommands: Vec<&Field> = fields
2964 .iter()
2965 .filter(|f| matches!(f.role, Role::Subcommand))
2966 .collect();
2967 if let Some(extra) = subcommands.get(1) {
2968 return Err(syn::Error::new(
2969 extra.ident.span(),
2970 "a struct has at most one subcommand field",
2971 ));
2972 }
2973 let subcommand = subcommands.first();
2974 let track_filled = subcommand.is_some() && !positionals.is_empty();
2976 let filled = track_filled.then(|| quote!(__filled = true;));
2977
2978 let overflow = match trailing {
2981 Some(t) => {
2982 let display = LitStr::new(&t.display(), Span::call_site());
2983 quote! {
2984 return ::core::result::Result::Err(
2985 __wa::Error::requires_double_dash(__word.offset, #display),
2986 )
2987 }
2988 }
2989 None => unexpected.clone(),
2990 };
2991 let positional_match = if positionals.is_empty() {
2992 quote!({ #overflow; })
2993 } else {
2994 let arms = positionals.iter().enumerate().map(|(i, f)| {
2995 let ident = slot(&f.ident);
2996 let display = LitStr::new(&f.display(), Span::call_site());
2997 let stop = (f.double_dash() == DoubleDash::Automatic || f.stop_flags)
2998 .then(|| quote!(__input.stop_flags();));
2999 let empty = match &f.kind {
3002 Kind::Many(_) => quote!(#ident.is_empty()),
3003 _ => quote!(true),
3004 };
3005 let separator = (f.double_dash() == DoubleDash::Automatic).then(|| {
3006 quote! {
3007 #i if !__word.after_separator && &**__word.value == b"--" && #empty => {
3008 __input.separate();
3009 }
3010 }
3011 });
3012 let arm = match &f.kind {
3013 Kind::Optional(ty) | Kind::Required(ty) => {
3014 let value = f.word_value(ty, quote!(__word), &display);
3015 quote! {
3016 #i => {
3017 #ident = ::core::option::Option::Some(#value);
3018 __position += 1;
3019 #filled
3020 #stop
3021 }
3022 }
3023 }
3024 Kind::Many(ty) => {
3025 let push = match f.delimiter {
3026 None => {
3027 let value = f.word_value(ty, quote!(__word), &display);
3028 quote! {
3031 if #ident.capacity() == 0 {
3032 #ident.reserve_exact(__input.words_left() + 1);
3033 }
3034 #ident.push(#value);
3035 }
3036 }
3037 Some(d) => {
3038 let value = f.word_value(ty, quote!(__piece), &display);
3039 quote! {
3040 for __piece in __word.split(#d) {
3041 #ident.push(#value);
3042 }
3043 }
3044 }
3045 };
3046 quote! {
3047 #i => {
3048 #push
3049 #filled
3050 #stop
3051 }
3052 }
3053 }
3054 Kind::Switch | Kind::Count(_) => {
3055 unreachable!("rejected for positionals in `field`")
3056 }
3057 };
3058 quote!(#separator #arm)
3059 });
3060 quote! {
3061 match __position {
3062 #(#arms)*
3063 _ => { #overflow; }
3064 }
3065 }
3066 };
3067 let positional_match = match trailing {
3068 None => positional_match,
3069 Some(t) => {
3070 let ident = slot(&t.ident);
3071 let display = LitStr::new(&t.display(), Span::call_site());
3072 let store = match &t.kind {
3073 Kind::Many(ty) => match t.delimiter {
3074 None => {
3075 let value = t.word_value(ty, quote!(__word), &display);
3076 quote!(#ident.push(#value);)
3077 }
3078 Some(d) => {
3079 let value = t.word_value(ty, quote!(__piece), &display);
3080 quote!(for __piece in __word.split(#d) { #ident.push(#value); })
3081 }
3082 },
3083 Kind::Optional(ty) | Kind::Required(ty) => {
3084 let value = t.word_value(ty, quote!(__word), &display);
3085 quote! {
3086 if #ident.is_some() { #unexpected; }
3087 #ident = ::core::option::Option::Some(#value);
3088 }
3089 }
3090 Kind::Switch | Kind::Count(_) => {
3091 unreachable!("rejected for positionals in `field`")
3092 }
3093 };
3094 quote! {
3095 if __word.after_separator {
3096 #store
3097 } else {
3098 #positional_match
3099 }
3100 }
3101 }
3102 };
3103 let routes = subcommand.map(|f| {
3104 let ident = slot(&f.ident);
3105 let ty = subcommand_type(f);
3106 let not_filled = track_filled.then(|| quote!(!__filled &&));
3107 let (global_longs, global_shorts, global_plus) = arms(
3108 &Field::is_global,
3109 quote!(return ::core::result::Result::Ok(true);),
3110 );
3111 let flat_globals = flattens.iter().map(|(ident, ty)| {
3114 let slot = format_ident!("__flat_{}", ident);
3115 quote! {
3116 if <#ty as __wa::Flatten>::bind_global(&mut #slot, __arg, __input)? {
3117 return ::core::result::Result::Ok(true);
3118 }
3119 }
3120 });
3121 let flat_shorts = flattens
3122 .iter()
3123 .map(|(_, ty)| quote!(.or_else(|| <#ty as __wa::Flatten>::global_short(__c))));
3124 let (inherit, handler) = if fields.iter().any(Field::is_global) || !flattens.is_empty() {
3125 let shorts = fields.iter().filter(|f| f.is_global()).flat_map(|f| {
3126 let takes_value = f.takes_value();
3127 f.shorts().into_iter().map(move |c| {
3128 let c = LitChar::new(c, Span::call_site());
3129 quote!((#c, #takes_value))
3130 })
3131 });
3132 let setup = quote! {
3133 let mut __inherit = __wa::inherit(
3134 &[#(#shorts),*],
3135 |__c| ::core::option::Option::<bool>::None #(#flat_shorts)*,
3136 __globals,
3137 |__arg, __input, __parent| {
3138 match __arg {
3139 __wa::Arg::Long(__flag) => match __flag.name {
3140 #(#global_longs)*
3141 _ => {}
3142 },
3143 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3144 #(#global_plus)*
3145 _ => {}
3146 },
3147 __wa::Arg::Short(__flag) => match __flag.letter {
3148 #(#global_shorts)*
3149 _ => {}
3150 },
3151 _ => {}
3152 }
3153 #(#flat_globals)*
3154 __parent.bind(__arg, __input)
3155 });
3156 };
3157 (setup, quote!(&mut __inherit))
3158 } else {
3159 (quote!(), quote!(&mut *__globals))
3160 };
3161 let help_word = (!disable_help_subcommand).then(|| {
3163 quote! {
3164 if &**__word.value == b"help" {
3165 return ::core::result::Result::Err(
3166 __wa::help_word(<Self as ::winnow_args::Args>::HELP, __input),
3167 );
3168 }
3169 }
3170 });
3171 let fallback = default_subcommand.as_ref().map(|name| {
3173 let name = LitByteStr::new(name.as_bytes(), Span::call_site());
3174 quote! {
3175 let __sub = {
3176 #inherit
3177 *__input = __start;
3178 <#ty as __wa::Subcommand>::parse_subcommand(#name, __input, #handler)?
3179 };
3180 #ident = ::core::option::Option::Some(__sub);
3181 break;
3182 }
3183 });
3184 let flag_fallback = fallback.as_ref().filter(|_| default_subcommand_flags).map(
3186 |fallback| match track_filled {
3187 true => quote!(if __filled { #unexpected; } #fallback),
3188 false => fallback.clone(),
3189 },
3190 );
3191 let route = quote! {
3192 if #not_filled !__word.after_separator {
3193 if <#ty as __wa::Subcommand>::has(&**__word.value) {
3194 let __sub = {
3195 #inherit
3196 <#ty as __wa::Subcommand>::parse_subcommand(
3197 &**__word.value, __input, #handler,
3198 )?
3199 };
3200 #ident = ::core::option::Option::Some(__sub);
3201 break;
3202 }
3203 #help_word
3204 #fallback
3205 }
3206 };
3207 (route, flag_fallback)
3208 });
3209 let (route, flag_fallback) = match routes {
3210 Some((route, flag_fallback)) => (Some(route), flag_fallback),
3211 None => (None, None),
3212 };
3213 let restart = restart_token.map(|token| {
3215 let token = LitByteStr::new(token.as_bytes(), Span::call_site());
3216 let clears = fields.iter().filter(|f| f.is_positional()).map(|f| {
3217 let slot = slot(&f.ident);
3218 match f.kind {
3219 Kind::Many(_) => quote!(#slot.clear();),
3220 _ => quote!(#slot = ::core::option::Option::None;),
3221 }
3222 });
3223 let position = (!positionals.is_empty()).then(|| quote!(__position = 0;));
3224 let filled = track_filled.then(|| quote!(__filled = false;));
3225 quote! {
3226 if &**__word.value == #token {
3227 #(#clears)*
3228 #position
3229 #filled
3230 __input.resume_flags();
3231 continue;
3232 }
3233 }
3234 });
3235 let sequence_word = sequence_ty.map(|ty| {
3236 quote! {
3237 if let ::core::option::Option::Some(__item) =
3238 <#ty as ::winnow_args::Occurrence>::from_word(&__word)?
3239 {
3240 __sequence.push(__item);
3241 continue;
3242 }
3243 }
3244 });
3245 let word_arm = quote! {
3246 __wa::Arg::Word(__word) => {
3247 #restart
3248 #route
3249 #sequence_word
3250 #positional_match
3251 }
3252 };
3253 let separator_arm = if preserving.is_empty() {
3254 quote!(__wa::Arg::Separator { .. } => {})
3255 } else {
3256 quote! {
3257 __wa::Arg::Separator { offset: __offset } => {
3258 if matches!(__position, #(#preserving)|*) {
3259 __input.resume_flags();
3260 let __word = __wa::Word {
3261 value: __wa::BStr::new(b"--"),
3262 offset: __offset,
3263 after_separator: false,
3264 };
3265 #positional_match
3266 }
3267 }
3268 }
3269 };
3270 let position = (!positionals.is_empty()).then(|| quote!(let mut __position: usize = 0;));
3271 let filled_slot = track_filled.then(|| quote!(let mut __filled = false;));
3272
3273 let opted: Vec<usize> = positionals
3276 .iter()
3277 .enumerate()
3278 .filter(|(_, f)| f.negative_numbers)
3279 .map(|(i, _)| i)
3280 .collect();
3281 let (unknown_slot, unknown_build) = match &unknown {
3282 Some((ident, ty)) => (
3283 quote!(let mut __unknown: ::std::vec::Vec<#ty> = ::std::vec::Vec::new();),
3284 quote!(#ident: __unknown,),
3285 ),
3286 None => (quote!(), quote!()),
3287 };
3288 let (sequence_slot, sequence_arg, sequence_build) = match &sequence {
3289 Some((ident, ty)) => (
3290 quote! {
3292 let mut __sequence: ::std::vec::Vec<#ty> =
3293 ::std::vec::Vec::with_capacity(__input.words_left());
3294 },
3295 quote! {
3296 if let ::core::option::Option::Some(__item) =
3297 <#ty as ::winnow_args::Occurrence>::from_arg(&__arg, __input)?
3298 {
3299 __sequence.push(__item);
3300 } else
3301 },
3302 quote!(#ident: __sequence,),
3303 ),
3304 None => (quote!(), quote!(), quote!()),
3305 };
3306 let bundle_start = sequence_ty.map(|_| {
3310 quote! {
3311 let __bundle_word = __input.front();
3312 let __bundle_start = __input.mode() == ::winnow_args::stream::Mode::Word;
3313 let __bundle_at = __sequence.len();
3314 }
3315 });
3316 let bundle_end = sequence_ty.map(|ty| {
3317 quote! {
3318 if <#ty as ::winnow_args::Occurrence>::BUNDLES
3319 && __bundle_start
3320 && matches!(__arg, __wa::Arg::Short(__flag) if !__flag.plus)
3321 && __input.mode() == ::winnow_args::stream::Mode::Bundle
3322 {
3323 let __word = __wa::Word {
3324 value: __wa::BStr::new(__bundle_word),
3325 offset: __arg.offset(),
3326 after_separator: false,
3327 };
3328 if let ::core::option::Option::Some(__bundle) =
3329 <#ty as ::winnow_args::Occurrence>::from_bundle(&__word)?
3330 {
3331 __sequence.insert(__bundle_at, __bundle);
3332 }
3333 }
3334 }
3335 });
3336 let flat_slot = |ident: &Ident| format_ident!("__flat_{}", ident);
3339 let flatten_slots = flattens.iter().map(|(ident, ty)| {
3340 let slot = flat_slot(ident);
3341 quote! {
3342 let mut #slot = <<#ty as __wa::Flatten>::Slots as ::core::default::Default>::default();
3343 }
3344 });
3345 let flatten_arg: TokenStream2 = flattens
3346 .iter()
3347 .map(|(ident, ty)| {
3348 let slot = flat_slot(ident);
3349 quote!(if <#ty as __wa::Flatten>::bind(&mut #slot, &__arg, __input)? {} else)
3350 })
3351 .collect();
3352 let flatten_build = flattens.iter().map(|(ident, ty)| {
3353 let slot = flat_slot(ident);
3354 quote!(#ident: <#ty as __wa::Flatten>::finish(#slot, __input)?,)
3355 });
3356 let flatten_shorts: Vec<TokenStream2> = flattens
3357 .iter()
3358 .map(|(_, ty)| {
3359 quote! {
3360 __c if <#ty as __wa::Flatten>::short(__c).is_some() => {
3361 <#ty as __wa::Flatten>::short(__c)
3362 }
3363 }
3364 })
3365 .collect();
3366 let flatten_longs: Vec<TokenStream2> = flattens
3367 .iter()
3368 .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_long(__name)))
3369 .collect();
3370 let own_shorts: Vec<TokenStream2> = fields
3372 .iter()
3373 .filter_map(|f| {
3374 let letters = f.short_pattern()?;
3375 let takes_value = f.takes_value();
3376 Some(quote!(#letters => ::core::option::Option::Some(#takes_value),))
3377 })
3378 .collect();
3379 let own_longs: Vec<String> = fields
3382 .iter()
3383 .filter(|f| !f.two_dashes)
3384 .flat_map(|f| spellings(f).into_iter().map(str::to_owned))
3385 .collect();
3386 let prefixes: Vec<u8> = fields
3387 .iter()
3388 .filter(|f| f.prefix)
3389 .filter_map(|f| f.short().map(|c| c as u8))
3390 .collect();
3391 let flatten_prefixes = flattens.iter().map(|(_, ty)| {
3392 quote!(&& !__name.first().is_some_and(|__c| <#ty as __wa::Flatten>::is_prefix(*__c)))
3393 });
3394 let lexer = if plus_options {
3395 quote!(__wa::arg_plus)
3396 } else {
3397 quote!(__wa::arg)
3398 };
3399 if !plus_options && let Some(f) = fields.iter().find(|f| f.plus.is_some()) {
3400 return Err(syn::Error::new(
3401 f.ident.span(),
3402 "`plus` needs `#[arg(plus_options)]` on the struct",
3403 ));
3404 }
3405 let plus_unknown = match &flag_fallback {
3407 Some(fallback) => quote!(#fallback),
3408 None => quote!(#unexpected;),
3409 };
3410 let plus_match = plus_options.then(|| {
3411 quote! {
3412 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3413 #(#plus_arms)*
3414 _ => #flatten_arg if !__globals.bind(&__arg, __input)? { #plus_unknown }
3415 },
3416 }
3417 });
3418 let lex = if opted.is_empty() {
3419 quote!(#lexer(__input)?)
3420 } else {
3421 let digits: Vec<LitByteStr> = fields
3422 .iter()
3423 .filter_map(|f| f.short().filter(char::is_ascii_digit))
3424 .map(|c| LitByteStr::new(format!("-{c}").as_bytes(), Span::call_site()))
3425 .collect();
3426 let not_a_short =
3427 (!digits.is_empty()).then(|| quote!(&&!matches!(__input.front(), #(#digits)|*)));
3428 quote! {
3429 match matches!(__position, #(#opted)|*) #not_a_short {
3430 true => match __wa::number(__input) {
3431 ::core::result::Result::Ok(__word) => __wa::Arg::Word(__word),
3432 ::core::result::Result::Err(_) => #lexer(__input)?,
3433 },
3434 false => #lexer(__input)?,
3435 }
3436 }
3437 };
3438 let start = default_subcommand
3439 .is_some()
3440 .then(|| quote!(let __start = *__input;));
3441 let help_on_empty = arg_required_else_help.then(|| {
3442 quote! {
3443 if __input.is_empty() {
3444 return ::core::result::Result::Err(
3445 __wa::Error::bare_help(<Self as ::winnow_args::Args>::HELP),
3446 );
3447 }
3448 }
3449 });
3450 if default_subcommand.is_some() && subcommand.is_none() {
3451 return Err(syn::Error::new(
3452 input.ident.span(),
3453 "`default_subcommand` needs an `#[arg(subcommand)]` field",
3454 ));
3455 }
3456 if default_subcommand_flags && default_subcommand.is_none() {
3457 return Err(syn::Error::new(
3458 input.ident.span(),
3459 "`default_subcommand_flags` needs `default_subcommand`",
3460 ));
3461 }
3462 if default_subcommand_help && default_subcommand.is_none() {
3463 return Err(syn::Error::new(
3464 input.ident.span(),
3465 "`default_subcommand_help` needs `default_subcommand`",
3466 ));
3467 }
3468 if flatten_help && subcommand.is_none() {
3469 return Err(syn::Error::new(
3470 input.ident.span(),
3471 "`flatten_help` needs an `#[arg(subcommand)]` field",
3472 ));
3473 }
3474 if default_subcommand_flags && (unknown_flags_value || unknown.is_some() || sequence_unknown) {
3475 return Err(syn::Error::new(
3476 input.ident.span(),
3477 "`default_subcommand_flags` hands an unknown flag to the default subcommand: \
3478 this struct cannot keep unknown flags too",
3479 ));
3480 }
3481 let declares_long = |name: &str| fields.iter().any(|f| f.longs().contains(&name));
3484 let declares_short = |c: char| fields.iter().any(|f| f.shorts().contains(&c));
3485 let help = quote!(<Self as ::winnow_args::Args>::HELP);
3486 let help_long = (!disable_help_flag && !declares_long("help")).then(
3487 || quote!(b"help" => return ::core::result::Result::Err(__wa::Error::help(#help, true)),),
3488 );
3489 let help_short = (!disable_help_flag && !disable_help_short && !declares_short('h')).then(
3490 || quote!('h' => return ::core::result::Result::Err(__wa::Error::help(#help, false)),),
3491 );
3492 let with_version = version.is_some() && !disable_version_flag;
3493 let version_long = (with_version && !declares_long("version")).then(
3494 || quote!(b"version" => return ::core::result::Result::Err(__wa::Error::version(#help)),),
3495 );
3496 let version_short = (with_version && !declares_short('V'))
3497 .then(|| quote!('V' => return ::core::result::Result::Err(__wa::Error::version(#help)),));
3498 let help_flag = help_long.is_some();
3500 let help_short_flag = help_short.is_some();
3501 let version_short_flag = version_short.is_some();
3502 let late = !flattens.is_empty() || sequence.is_some();
3506 let late_if = |on: bool, test: TokenStream2, error: TokenStream2| {
3507 (late && on).then(|| quote!(if #test { return ::core::result::Result::Err(#error); } else))
3508 };
3509 let late_long: TokenStream2 = [
3510 late_if(
3511 help_long.is_some(),
3512 quote!(__flag.name == b"help"),
3513 quote!(__wa::Error::help(#help, true)),
3514 ),
3515 late_if(
3516 version_long.is_some(),
3517 quote!(__flag.name == b"version"),
3518 quote!(__wa::Error::version(#help)),
3519 ),
3520 ]
3521 .into_iter()
3522 .flatten()
3523 .collect();
3524 let late_short: TokenStream2 = [
3525 late_if(
3526 help_short.is_some(),
3527 quote!(__flag.letter == 'h'),
3528 quote!(__wa::Error::help(#help, false)),
3529 ),
3530 late_if(
3531 version_short.is_some(),
3532 quote!(__flag.letter == 'V'),
3533 quote!(__wa::Error::version(#help)),
3534 ),
3535 ]
3536 .into_iter()
3537 .flatten()
3538 .collect();
3539 let keep = |arm: Option<TokenStream2>| arm.filter(|_| !late);
3540 let (help_long, help_short) = (keep(help_long), keep(help_short));
3541 let (version_long, version_short) = (keep(version_long), keep(version_short));
3542
3543 let (lenient_bundle, save_token, unknown_flag) = if unknown_flags_value
3553 || unknown.is_some()
3554 || sequence_unknown
3555 {
3556 let next = (!positionals.is_empty() || trailing.is_some()).then(|| quote!(continue;));
3558 let (sink, sink_next) = match (&unknown, sequence_ty.filter(|_| sequence_unknown)) {
3559 (_, Some(ty)) => (
3560 quote! {
3561 match <#ty as ::winnow_args::Occurrence>::from_unknown(&__word)? {
3562 ::core::option::Option::Some(__item) => __sequence.push(__item),
3563 ::core::option::Option::None => {
3564 return ::core::result::Result::Err(__wa::Error::unknown_flag(
3565 __word.offset,
3566 ::std::string::String::from_utf8_lossy(&__word.value),
3567 ));
3568 }
3569 }
3570 },
3571 quote!(continue;),
3572 ),
3573 (Some((_, ty)), None) => (
3574 quote!(__unknown.push(__word.convert::<#ty>("<UNKNOWN>")?);),
3575 quote!(continue;),
3576 ),
3577 (None, None) => (
3578 quote! {
3579 let __arg = __wa::Arg::Word(__word);
3580 #positional_match
3581 },
3582 quote!(#next),
3583 ),
3584 };
3585 let theirs = match sequence_ty {
3586 Some(ty) => quote! {
3587 __c => match <#ty as ::winnow_args::Occurrence>::short(__c) {
3588 ::core::option::Option::None => __globals.short(__c),
3589 __known => __known,
3590 },
3591 },
3592 None => quote!(_ => __globals.short(__c),),
3593 };
3594 let builtin = help_short_flag
3596 .then(|| quote!('h' => ::core::option::Option::Some(false),))
3597 .into_iter()
3598 .chain(version_short_flag.then(|| quote!('V' => ::core::option::Option::Some(false),)));
3599 (
3600 quote! {
3601 if let ::core::option::Option::Some(__word) = __wa::unknown_bundle(__input, |__c| match __c {
3602 #(#own_shorts)*
3603 #(#builtin)*
3604 #(#flatten_shorts)*
3605 #theirs
3606 }) {
3607 #sink
3608 #sink_next
3609 }
3610 },
3611 quote!(let __token = __input.front();),
3612 quote! {
3613 let __word = __wa::Word {
3614 value: __wa::BStr::new(__token),
3615 offset: __flag.offset,
3616 after_separator: false,
3617 };
3618 #sink
3619 },
3620 )
3621 } else {
3622 let unknown_flag = match &flag_fallback {
3623 Some(fallback) => quote!(#fallback),
3624 None => quote!(#unexpected;),
3625 };
3626 (quote!(), quote!(), unknown_flag)
3627 };
3628 let mut names = Vec::new();
3629 if long_only {
3630 names.clone_from(&own_longs);
3631 names.extend(help_long.is_some().then(|| "help".to_owned()));
3632 names.extend(version_long.is_some().then(|| "version".to_owned()));
3633 }
3634 let long_only_lex =
3635 long_only && (!names.is_empty() || sequence_ty.is_some() || !flattens.is_empty());
3636 let lex = if long_only_lex {
3637 let own = matches_name(&names);
3638 let (their_names, their_prefixes) = match sequence_ty {
3639 Some(ty) => (
3640 quote!(|| <#ty as ::winnow_args::Occurrence>::is_long(__name)),
3641 quote! {
3642 && !__name.first().is_some_and(|__c| {
3643 <#ty as ::winnow_args::Occurrence>::PREFIXES.contains(__c)
3644 })
3645 },
3646 ),
3647 None => (quote!(), quote!()),
3648 };
3649 let not_prefixed = (!prefixes.is_empty()).then(
3650 || quote!(!matches!(__name.first(), ::core::option::Option::Some(#(#prefixes)|*)) &&),
3651 );
3652 quote! {
3653 match __wa::long_only(__input, |__name| {
3654 #not_prefixed (#own #their_names #(#flatten_longs)*) #their_prefixes #(#flatten_prefixes)*
3655 }) {
3656 ::core::option::Option::Some(__arg) => __arg,
3657 ::core::option::Option::None => {
3658 #lenient_bundle
3659 #lex
3660 }
3661 }
3662 }
3663 } else {
3664 lex
3665 };
3666 let lenient_bundle = if long_only_lex {
3667 quote!()
3668 } else {
3669 lenient_bundle
3670 };
3671
3672 let help_version = match &version {
3673 Some(v) if with_version => quote!(::core::option::Option::Some(#v)),
3674 _ => quote!(::core::option::Option::None),
3675 };
3676 let help_items = fields
3677 .iter()
3678 .filter(|f| !matches!(f.role, Role::Subcommand))
3679 .map(|f| f.help_item());
3680 let (help_subcommands, subcommand_required) = match subcommand {
3681 Some(f) => {
3682 let ty = subcommand_type(f);
3683 (
3684 quote!(<#ty as ::winnow_args::Args>::HELP.subcommands),
3685 matches!(f.kind, Kind::Required(_)),
3686 )
3687 }
3688 None => (quote!(&[]), false),
3689 };
3690 let displaced = rules.displaced_slots(&fields);
3691 let env_fallbacks: Vec<TokenStream2> = fields
3692 .iter()
3693 .map(|f| f.fallback(true, rules.is_displaced(&fields, f)))
3694 .collect();
3695 let default_fallbacks: Vec<TokenStream2> = fields
3696 .iter()
3697 .map(|f| f.fallback(false, rules.is_displaced(&fields, f)))
3698 .collect();
3699 let exclusive = rules.exclusive(&fields, &groups);
3700 let supplied = rules.supplied(&fields);
3701 let default_ifs = rules.default_ifs(&fields);
3702 let required = rules.required(&fields, &groups);
3703 let build = fields.iter().map(|f| {
3704 let ident = &f.ident;
3705 let slot = slot(ident);
3706 match &f.kind {
3707 Kind::Required(ty) if f.keywords => {
3708 let display = LitStr::new(&f.display(), Span::call_site());
3709 quote!(#ident: __wa::keywords::<#ty>(&#slot, #display, __input.offset())?)
3710 }
3711 Kind::Switch if f.negate.is_some() => quote!(#ident: #slot.unwrap_or(false)),
3712 Kind::Switch | Kind::Count(_) | Kind::Optional(_) | Kind::Many(_) => {
3713 quote!(#ident: #slot)
3714 }
3715 Kind::Required(_) => {
3716 let display = LitStr::new(&f.display(), Span::call_site());
3717 let error = match f.role {
3718 Role::Positional { .. } => {
3719 quote!(__wa::Error::missing_argument(__input.offset(), #display))
3720 }
3721 Role::Flag { .. } => {
3722 quote!(__wa::Error::missing_required(__input.offset(), #display))
3723 }
3724 Role::Subcommand => quote!(__wa::Error::missing_subcommand(__input.offset())),
3725 };
3726 quote! {
3727 #ident: match #slot {
3728 ::core::option::Option::Some(v) => v,
3729 ::core::option::Option::None => {
3730 return ::core::result::Result::Err(#error);
3731 }
3732 }
3733 }
3734 }
3735 }
3736 });
3737 let build: Vec<TokenStream2> = build.collect();
3738 let field_reads: Vec<TokenStream2> = fields
3742 .iter()
3743 .map(|f| &f.ident)
3744 .chain(skipped.iter().copied())
3745 .chain(sequence.iter().chain(&unknown).map(|(ident, _)| ident))
3746 .chain(flattens.iter().map(|(ident, _)| ident))
3747 .map(|ident| quote!(let _ = &__built.#ident;))
3748 .collect();
3749 let flatten_build: Vec<TokenStream2> = flatten_build.collect();
3750 let flattenable = positionals.is_empty()
3753 && trailing.is_none()
3754 && subcommand.is_none()
3755 && sequence.is_none()
3756 && unknown.is_none()
3757 && !unknown_flags_value
3758 && input.generics.params.is_empty()
3759 && !fields.iter().any(|f| f.keywords);
3760 let flatten_impl = flattenable.then(|| {
3761 let vis = &input.vis;
3762 let slots_name = format_ident!("__{}WinnowArgsSlots", name);
3763 let displaced_idents: Vec<Ident> = fields
3764 .iter()
3765 .filter(|f| rules.is_displaced(&fields, f))
3766 .map(|f| displaced_flag(&f.ident))
3767 .collect();
3768 let nested: Vec<(Ident, &Type)> = flattens
3769 .iter()
3770 .map(|(ident, ty)| (flat_slot(ident), ty))
3771 .collect();
3772 let decls = slot_parts.iter().map(|(ident, ty, _)| quote!(#ident: #ty,));
3773 let inits = slot_parts
3774 .iter()
3775 .map(|(ident, _, init)| quote!(#ident: #init,));
3776 let all: Vec<&Ident> = slot_parts
3777 .iter()
3778 .map(|(ident, _, _)| ident)
3779 .chain(&displaced_idents)
3780 .chain(nested.iter().map(|(n, _)| n))
3781 .collect();
3782 let names: Vec<TokenStream2> = all.iter().map(|ident| quote!(#ident,)).collect();
3783 let mutable: Vec<TokenStream2> = all.iter().map(|ident| quote!(mut #ident,)).collect();
3784 let displaced_decls = displaced_idents.iter().map(|d| quote!(#d: bool,));
3785 let displaced_inits = displaced_idents.iter().map(|d| quote!(#d: false,));
3786 let nested_decls = nested
3787 .iter()
3788 .map(|(n, ty)| quote!(#n: <#ty as ::winnow_args::__private::Flatten>::Slots,));
3789 let nested_inits = nested
3790 .iter()
3791 .map(|(n, _)| quote!(#n: ::core::default::Default::default(),));
3792 let (bind_long, bind_short, bind_plus) = arms(&|_| true, quote!(true));
3793 let (global_long, global_short, global_plus) = arms(&Field::is_global, quote!(true));
3796 let global_nested: TokenStream2 = nested
3797 .iter()
3798 .map(|(n, ty)| {
3799 quote!(if <#ty as __wa::Flatten>::bind_global(&mut #n, &__arg, __input)? { true } else)
3800 })
3801 .collect();
3802 let global_letters = fields.iter().filter(|f| f.is_global()).filter_map(|f| {
3803 let letters = f.short_pattern()?;
3804 let takes_value = f.takes_value();
3805 Some(quote!(#letters => ::core::option::Option::Some(#takes_value),))
3806 });
3807 let nested_global_letters = nested
3808 .iter()
3809 .map(|(_, ty)| quote!(.or_else(|| <#ty as __wa::Flatten>::global_short(__c))));
3810 let bind_nested: TokenStream2 = nested
3811 .iter()
3812 .map(|(n, ty)| {
3813 quote!(if <#ty as __wa::Flatten>::bind(&mut #n, &__arg, __input)? { true } else)
3814 })
3815 .collect();
3816 let plus = if plus_options {
3818 quote! {
3819 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3820 #(#bind_plus)*
3821 _ => #bind_nested { false }
3822 },
3823 }
3824 } else {
3825 quote!(__wa::Arg::Short(__flag) if __flag.plus => #bind_nested { false },)
3826 };
3827 let own_longs = matches_name(&own_longs);
3828 let own_prefixes = if prefixes.is_empty() {
3829 quote!(false)
3830 } else {
3831 quote!(matches!(__c, #(#prefixes)|*))
3832 };
3833 let nested_prefixes = nested
3834 .iter()
3835 .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_prefix(__c)));
3836 quote! {
3837 #[doc(hidden)]
3838 #[allow(non_camel_case_types)]
3839 #[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction)]
3840 #vis struct #slots_name {
3841 #(#decls)*
3842 #(#displaced_decls)*
3843 #(#nested_decls)*
3844 }
3845
3846 #[automatically_derived]
3847 impl ::core::default::Default for #slots_name {
3848 fn default() -> Self {
3849 Self {
3850 #(#inits)*
3851 #(#displaced_inits)*
3852 #(#nested_inits)*
3853 }
3854 }
3855 }
3856
3857 #[automatically_derived]
3858 #[allow(
3859 unused_mut,
3860 unused_variables,
3861 unreachable_code,
3862 clippy::all,
3863 clippy::pedantic,
3864 clippy::nursery,
3865 clippy::restriction
3866 )]
3867 impl ::winnow_args::__private::Flatten for #name {
3868 type Slots = #slots_name;
3869
3870 fn bind<'__i>(
3871 __slots: &mut #slots_name,
3872 __arg: &::winnow_args::Arg<'__i>,
3873 __input: &mut ::winnow_args::Argv<'__i>,
3874 ) -> ::core::result::Result<bool, ::winnow_args::Error> {
3875 use ::winnow_args::__private as __wa;
3876 let __arg = *__arg;
3877 let #slots_name { #(#mutable)* } = ::core::mem::take(__slots);
3878 let __bound = match __arg {
3879 __wa::Arg::Long(__flag) => match __flag.name {
3880 #(#bind_long)*
3881 _ => #bind_nested { false }
3882 },
3883 #plus
3884 __wa::Arg::Short(__flag) => match __flag.letter {
3885 #(#bind_short)*
3886 _ => #bind_nested { false }
3887 },
3888 _ => false,
3889 };
3890 *__slots = #slots_name { #(#names)* };
3891 ::core::result::Result::Ok(__bound)
3892 }
3893
3894 fn short(__c: char) -> ::core::option::Option<bool> {
3895 use ::winnow_args::__private as __wa;
3896 match __c {
3897 #(#own_shorts)*
3898 #(#flatten_shorts)*
3899 _ => ::core::option::Option::None,
3900 }
3901 }
3902
3903 fn bind_global<'__i>(
3904 __slots: &mut #slots_name,
3905 __arg: &::winnow_args::Arg<'__i>,
3906 __input: &mut ::winnow_args::Argv<'__i>,
3907 ) -> ::core::result::Result<bool, ::winnow_args::Error> {
3908 use ::winnow_args::__private as __wa;
3909 let __arg = *__arg;
3910 let #slots_name { #(#mutable)* } = ::core::mem::take(__slots);
3911 let __bound = match __arg {
3912 __wa::Arg::Long(__flag) => match __flag.name {
3913 #(#global_long)*
3914 _ => #global_nested { false }
3915 },
3916 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3917 #(#global_plus)*
3918 _ => #global_nested { false }
3919 },
3920 __wa::Arg::Short(__flag) => match __flag.letter {
3921 #(#global_short)*
3922 _ => #global_nested { false }
3923 },
3924 _ => false,
3925 };
3926 *__slots = #slots_name { #(#names)* };
3927 ::core::result::Result::Ok(__bound)
3928 }
3929
3930 fn global_short(__c: char) -> ::core::option::Option<bool> {
3931 use ::winnow_args::__private as __wa;
3932 let __own = match __c {
3933 #(#global_letters)*
3934 _ => ::core::option::Option::None,
3935 };
3936 __own #(#nested_global_letters)*
3937 }
3938
3939 fn is_prefix(__c: u8) -> bool {
3940 use ::winnow_args::__private as __wa;
3941 #own_prefixes #(#nested_prefixes)*
3942 }
3943
3944 fn is_long(__name: &[u8]) -> bool {
3945 use ::winnow_args::__private as __wa;
3946 #own_longs #(#flatten_longs)*
3947 }
3948
3949 fn finish(
3950 __slots: #slots_name,
3951 __input: &::winnow_args::Argv<'_>,
3952 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
3953 use ::winnow_args::__private as __wa;
3954 let #slots_name { #(#mutable)* } = __slots;
3955 #(#env_fallbacks)*
3956 #exclusive
3957 #supplied
3958 #default_ifs
3959 #(#default_fallbacks)*
3960 #required
3961 let __built = Self {
3962 #(#build,)*
3963 #(#skipped: ::core::default::Default::default(),)*
3964 #(#flatten_build)*
3965 };
3966 #(#field_reads)*
3967 ::core::result::Result::Ok(__built)
3968 }
3969 }
3970 }
3971 });
3972 let own_items = quote!(&[#(#help_items),*]);
3973 let positional_clash = sequence_ty
3975 .filter(|_| !positionals.is_empty() || trailing.is_some())
3976 .map(|ty| {
3977 quote! {
3978 const _: () = assert!(
3979 !<#ty as ::winnow_args::Occurrence>::POSITIONAL,
3980 "the sequence enum has a positional variant, which takes every word: this struct's positionals would never be filled",
3981 );
3982 }
3983 });
3984 let more: Vec<TokenStream2> = flattens
3986 .iter()
3987 .map(|(_, ty)| quote!(<#ty as ::winnow_args::Args>::HELP.items))
3988 .chain(
3989 sequence_ty
3990 .iter()
3991 .map(|ty| quote!(<#ty as ::winnow_args::Occurrence>::ITEMS)),
3992 )
3993 .collect();
3994 let items = if more.is_empty() {
3995 own_items
3996 } else {
3997 quote!({
3998 const __OWN: &[::winnow_args::help::Item] = #own_items;
3999 const _: () = assert!(
4000 !::winnow_args::help::items_clash(&[__OWN, #(#more),*]),
4001 "a flattened struct or the sequence enum declares a flag that this struct, or another of them, also declares",
4002 );
4003 #positional_clash
4004 const __N: usize = __OWN.len() #(+ #more.len())*;
4005 const __ALL: [::winnow_args::help::Item; __N] = ::winnow_args::__private::concat_items::<__N>(
4006 &[__OWN, #(#more),*],
4007 );
4008 &__ALL
4009 })
4010 };
4011 let help_default = opt_str(default_subcommand.as_deref());
4012 let help_examples = examples.iter().map(|[code, header, help]| {
4013 let (header, help) = (text(header), text(help));
4014 quote!(::winnow_args::help::Example { code: #code, header: #header, help: #help })
4015 });
4016 let help_examples: Vec<TokenStream2> = help_examples.collect();
4017 let own_headings = headings.iter().map(|(title, help)| {
4019 let help = text(help);
4020 quote!(::winnow_args::help::Heading { title: #title, help: #help })
4021 });
4022 let own_headings = quote!(&[#(#own_headings),*]);
4023 let help_headings = if flattens.is_empty() {
4024 own_headings
4025 } else {
4026 let more = flattens
4027 .iter()
4028 .map(|(_, ty)| quote!(<#ty as ::winnow_args::Args>::HELP.headings));
4029 let more: Vec<TokenStream2> = more.collect();
4030 quote!({
4031 const __OWN: &[::winnow_args::help::Heading] = #own_headings;
4032 const __N: usize = __OWN.len() #(+ #more.len())*;
4033 const __ALL: [::winnow_args::help::Heading; __N] =
4034 ::winnow_args::__private::concat_headings::<__N>(&[__OWN, #(#more),*]);
4035 &__ALL
4036 })
4037 };
4038 let mut spec_checks = Vec::new();
4039 let filed = filed(input)?;
4040 let identity = spec::identity(input.ident.span(), &filed);
4041 if spec::enabled() {
4042 spec_checks.extend(fields.iter().filter_map(|field| {
4043 spec_check(&input.generics, choices_type(field), field.spec.as_ref())
4044 .or_else(|| spec_check_stated(&input.generics, field))
4045 }));
4046 let stated = |ident: &Ident| {
4049 let field = named.named.iter().find(|f| f.ident.as_ref() == Some(ident));
4050 field.map_or(Ok(None), |f| spec_name(&f.attrs))
4051 };
4052 let mut link = |kind: TokenStream2, stated: Option<String>, ty: &Type| {
4053 let name = spec_type(stated, ty)?;
4054 spec_checks.extend(spec_filed(&input.generics, kind, ty, &name));
4055 syn::Result::Ok(name)
4056 };
4057 for field in fields.iter().filter(|field| field.keywords) {
4058 if let Kind::Required(ty) = &field.kind {
4059 link(quote!(Args), field.spec.clone(), ty)?;
4060 }
4061 }
4062 let items = fields
4063 .iter()
4064 .filter(|field| !matches!(field.role, Role::Subcommand))
4065 .map(spec_item)
4066 .collect::<syn::Result<Vec<_>>>()?;
4067 let flatten = flattens
4068 .iter()
4069 .map(|(ident, ty)| link(quote!(Args), stated(ident)?, ty))
4070 .collect::<syn::Result<Vec<_>>>()?;
4071 let sequence = sequence
4072 .as_ref()
4073 .map(|(ident, ty)| link(quote!(Occurrence), stated(ident)?, ty))
4074 .transpose()?;
4075 let subcommand_ty = subcommand
4076 .map(|field| {
4077 link(
4078 quote!(Subcommand),
4079 field.spec.clone(),
4080 subcommand_type(field),
4081 )
4082 })
4083 .transpose()?;
4084 spec::write_args(
4085 input.ident.span(),
4086 &spec::ArgsDoc {
4087 ident: filed.clone(),
4088 name: program_name.clone(),
4089 about: about_text,
4090 long_about: long_about_text,
4091 after_help: after_text,
4092 after_long_help: after_long_text,
4093 package_version: package_version(&version, with_version),
4094 items,
4095 flatten,
4096 sequence,
4097 subcommand: subcommand_ty,
4098 subcommand_required,
4099 help_flag,
4100 help_short: help_short_flag,
4101 long_only,
4102 unknown_flags_value,
4103 default_subcommand,
4104 examples,
4105 headings,
4106 effect,
4107 outputs,
4108 exit_codes,
4109 },
4110 )?;
4111 }
4112
4113 Ok(quote! {
4114 #(#spec_checks)*
4115 impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
4116 const SPEC: &'static str = #identity;
4117
4118 #[inline(never)]
4121 fn help_data() -> &'static ::winnow_args::help::Command {
4122 <Self as ::winnow_args::Args>::HELP
4123 }
4124
4125 const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
4126 name: #program_name,
4127 about: #about,
4128 long_about: #long_about,
4129 after_help: #after_help,
4130 after_long_help: #after_long_help,
4131 items: #items,
4132 subcommands: #help_subcommands,
4133 subcommand_required: #subcommand_required,
4134 help_flag: #help_flag,
4135 help_short: #help_short_flag,
4136 version: #help_version,
4137 default_subcommand: #help_default,
4138 default_subcommand_flags: #default_subcommand_flags,
4139 default_subcommand_help: #default_subcommand_help,
4140 flatten_help: #flatten_help,
4141 examples: &[#(#help_examples),*],
4142 headings: #help_headings,
4143 };
4144
4145 fn parse_argv_with(
4146 __input: &mut ::winnow_args::Argv<'_>,
4147 __globals: &mut dyn ::winnow_args::Globals,
4148 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
4149 use ::winnow_args::__private as __wa;
4150 #(#slots)*
4151 #sequence_slot
4152 #unknown_slot
4153 #(#flatten_slots)*
4154 #(#displaced)*
4155 #position
4156 #filled_slot
4157 #help_on_empty
4158 while !__input.is_empty() {
4159 #start
4160 #lenient_bundle
4161 #save_token
4162 #bundle_start
4163 let __arg = #lex;
4164 match __arg {
4165 __wa::Arg::Long(__flag) => match __flag.name {
4166 #(#long_arms)*
4167 #help_long
4168 #version_long
4169 _ => #sequence_arg #flatten_arg #late_long if !__globals.bind(&__arg, __input)? { #unknown_flag },
4170 },
4171 #plus_match
4172 __wa::Arg::Short(__flag) => match __flag.letter {
4173 #(#short_arms)*
4174 #help_short
4175 #version_short
4176 _ => #sequence_arg #flatten_arg #late_short if !__globals.bind(&__arg, __input)? { #unknown_flag },
4177 },
4178 #separator_arm
4179 #word_arm
4180 }
4181 #bundle_end
4182 }
4183 #(#env_fallbacks)*
4184 #exclusive
4185 #supplied
4186 #default_ifs
4187 #(#default_fallbacks)*
4188 #required
4189 let __built = Self {
4190 #(#build,)*
4191 #(#skipped: ::core::default::Default::default(),)*
4192 #sequence_build
4193 #unknown_build
4194 #(#flatten_build)*
4195 };
4196 #(#field_reads)*
4197 ::core::result::Result::Ok(__built)
4198 }
4199 }
4200
4201 #flatten_impl
4202 })
4203}
4204
4205struct Group {
4207 name: String,
4208 required: bool,
4209 multiple: bool,
4210}
4211
4212#[derive(Default)]
4214struct StructOptions {
4215 groups: Vec<Group>,
4216 restart_token: Option<String>,
4218 default_subcommand: Option<String>,
4220 default_subcommand_flags: bool,
4222 default_subcommand_help: bool,
4224 flatten_help: bool,
4226 next_help_heading: Option<String>,
4228 headings: Vec<(String, String)>,
4230 examples: Vec<[String; 3]>,
4232 effect: Option<String>,
4234 outputs: Vec<spec::OutputDoc>,
4236 exit_codes: Vec<(u8, String)>,
4238 arg_required_else_help: bool,
4240 name: String,
4242 version: Option<TokenStream2>,
4244 about: Option<String>,
4246 long_about: Option<String>,
4247 after_help: String,
4248 after_long_help: String,
4249 disable_help_flag: bool,
4250 disable_help_short: bool,
4252 disable_version_flag: bool,
4253 disable_help_subcommand: bool,
4254 unknown_flags_value: bool,
4256 long_only: bool,
4258 plus_options: bool,
4260}
4261
4262fn struct_options(input: &DeriveInput) -> syn::Result<StructOptions> {
4263 let mut options = StructOptions::default();
4264 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
4265 attr.parse_nested_meta(|meta| {
4266 if skip_spec(&meta)? {
4267 return Ok(());
4268 }
4269 if meta.path.is_ident("restart_token") {
4270 options.restart_token = Some(meta.value()?.parse::<LitStr>()?.value());
4271 return Ok(());
4272 }
4273 if meta.path.is_ident("default_subcommand") {
4274 options.default_subcommand = Some(meta.value()?.parse::<LitStr>()?.value());
4275 return Ok(());
4276 }
4277 if meta.path.is_ident("default_subcommand_flags") {
4278 options.default_subcommand_flags = true;
4279 return Ok(());
4280 }
4281 if meta.path.is_ident("default_subcommand_help") {
4282 options.default_subcommand_help = true;
4283 return Ok(());
4284 }
4285 if meta.path.is_ident("flatten_help") {
4286 options.flatten_help = true;
4287 return Ok(());
4288 }
4289 if meta.path.is_ident("next_help_heading") {
4290 options.next_help_heading = Some(meta.value()?.parse::<LitStr>()?.value());
4291 return Ok(());
4292 }
4293 if meta.path.is_ident("heading") {
4294 let (title, [help]) = titled(&meta, ["help"])?;
4295 options.headings.push((title, help));
4296 return Ok(());
4297 }
4298 if meta.path.is_ident("effect") {
4299 options.effect = Some(effect_name(&meta)?);
4300 return Ok(());
4301 }
4302 if meta.path.is_ident("output") {
4303 let content;
4304 syn::parenthesized!(content in meta.input);
4305 let mut output = spec::OutputDoc {
4306 name: content.parse::<LitStr>()?.value(),
4307 default: false,
4308 framing: String::new(),
4309 help: String::new(),
4310 };
4311 while !content.is_empty() {
4312 content.parse::<syn::Token![,]>()?;
4313 if content.is_empty() {
4314 break;
4315 }
4316 let key = content.parse::<Ident>()?;
4317 if key == "default" {
4318 output.default = true;
4319 continue;
4320 }
4321 let slot = match key.to_string().as_str() {
4322 "framing" => &mut output.framing,
4323 "help" => &mut output.help,
4324 _ => {
4325 return Err(syn::Error::new(
4326 key.span(),
4327 "expected `default`, `framing` or `help`",
4328 ));
4329 }
4330 };
4331 content.parse::<syn::Token![=]>()?;
4332 *slot = content.parse::<LitStr>()?.value();
4333 }
4334 options.outputs.push(output);
4335 return Ok(());
4336 }
4337 if meta.path.is_ident("exit_code") {
4338 let content;
4339 syn::parenthesized!(content in meta.input);
4340 let code = content.parse::<syn::LitInt>()?.base10_parse::<u8>()?;
4341 content.parse::<syn::Token![,]>()?;
4342 let help = content.parse::<LitStr>()?.value();
4343 options.exit_codes.push((code, help));
4344 return Ok(());
4345 }
4346 if meta.path.is_ident("example") {
4347 let example = if meta.input.peek(syn::Token![=]) {
4348 [meta.value()?.parse::<LitStr>()?.value(), String::new(), String::new()]
4349 } else {
4350 let (code, [header, help]) = titled(&meta, ["header", "help"])?;
4351 [code, header, help]
4352 };
4353 options.examples.push(example);
4354 return Ok(());
4355 }
4356 if meta.path.is_ident("arg_required_else_help") {
4357 options.arg_required_else_help = true;
4358 return Ok(());
4359 }
4360 if meta.path.is_ident("long_only") {
4361 options.long_only = true;
4362 return Ok(());
4363 }
4364 if meta.path.is_ident("plus_options") {
4365 options.plus_options = true;
4366 return Ok(());
4367 }
4368 if meta.path.is_ident("unknown_flags") {
4369 let mode = meta.value()?.parse::<LitStr>()?;
4370 options.unknown_flags_value = match mode.value().as_str() {
4371 "value" => true,
4372 "error" => false,
4373 _ => {
4374 return Err(syn::Error::new(
4375 mode.span(),
4376 "expected \"value\" or \"error\"",
4377 ));
4378 }
4379 };
4380 return Ok(());
4381 }
4382 let text = |meta: &syn::meta::ParseNestedMeta<'_>| -> syn::Result<String> {
4383 Ok(meta.value()?.parse::<LitStr>()?.value())
4384 };
4385 if meta.path.is_ident("name") {
4386 options.name = text(&meta)?;
4387 return Ok(());
4388 }
4389 if meta.path.is_ident("version") {
4390 options.version = Some(if meta.input.peek(syn::Token![=]) {
4391 let version = meta.value()?.parse::<LitStr>()?;
4392 quote!(#version)
4393 } else {
4394 quote!(::core::env!("CARGO_PKG_VERSION"))
4395 });
4396 return Ok(());
4397 }
4398 for (key, slot) in [
4399 ("about", &mut options.about),
4400 ("long_about", &mut options.long_about),
4401 ] {
4402 if meta.path.is_ident(key) {
4403 *slot = Some(text(&meta)?);
4404 return Ok(());
4405 }
4406 }
4407 for (key, slot) in [
4408 ("after_help", &mut options.after_help),
4409 ("after_long_help", &mut options.after_long_help),
4410 ] {
4411 if meta.path.is_ident(key) {
4412 *slot = text(&meta)?;
4413 return Ok(());
4414 }
4415 }
4416 for (key, slot) in [
4417 ("disable_help_flag", &mut options.disable_help_flag),
4418 ("disable_help_short", &mut options.disable_help_short),
4419 ("disable_version_flag", &mut options.disable_version_flag),
4420 (
4421 "disable_help_subcommand",
4422 &mut options.disable_help_subcommand,
4423 ),
4424 ] {
4425 if meta.path.is_ident(key) {
4426 *slot = true;
4427 return Ok(());
4428 }
4429 }
4430 if !meta.path.is_ident("group") {
4431 return Err(meta.error(
4432 "expected `group(\"name\", ...)`, `restart_token`, `default_subcommand`, \
4433 `default_subcommand_flags`, `default_subcommand_help`, `arg_required_else_help`, `unknown_flags`, `long_only`, \
4434 `plus_options`, `name`, `version`, `about`, `long_about`, `after_help`, `after_long_help`, `example`, \
4435 `heading(\"title\", help = ...)`, `next_help_heading`, `flatten_help`, `effect`, `output(\"name\", ...)`, \
4436 `exit_code(n, \"meaning\")` or a `disable_*` option",
4437 ));
4438 }
4439 let content;
4440 syn::parenthesized!(content in meta.input);
4441 let name = content.parse::<LitStr>()?.value();
4442 let (mut required, mut multiple) = (false, false);
4443 while !content.is_empty() {
4444 content.parse::<syn::Token![,]>()?;
4445 let flag = content.parse::<Ident>()?;
4446 match flag.to_string().as_str() {
4447 "required" => required = true,
4448 "multiple" => multiple = true,
4449 _ => {
4450 return Err(syn::Error::new(
4451 flag.span(),
4452 "expected `required` or `multiple`",
4453 ));
4454 }
4455 }
4456 }
4457 options.groups.push(Group {
4458 name,
4459 required,
4460 multiple,
4461 });
4462 Ok(())
4463 })?;
4464 }
4465 Ok(options)
4466}
4467
4468fn effect_name(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<String> {
4470 let name = meta.value()?.parse::<LitStr>()?;
4471 match name.value().as_str() {
4472 "read" | "write" | "destructive" => Ok(name.value()),
4473 _ => Err(syn::Error::new(
4474 name.span(),
4475 "expected \"read\", \"write\" or \"destructive\"",
4476 )),
4477 }
4478}
4479
4480fn titled<const N: usize>(
4483 meta: &syn::meta::ParseNestedMeta<'_>,
4484 names: [&str; N],
4485) -> syn::Result<(String, [String; N])> {
4486 let content;
4487 syn::parenthesized!(content in meta.input);
4488 let text = content.parse::<LitStr>()?.value();
4489 let mut given = [const { String::new() }; N];
4490 while !content.is_empty() {
4491 content.parse::<syn::Token![,]>()?;
4492 if content.is_empty() {
4493 break;
4494 }
4495 let key = content.parse::<Ident>()?;
4496 let Some(at) = names.iter().position(|name| key == name) else {
4497 let names = names.map(|name| format!("`{name}`")).join(" or ");
4498 return Err(syn::Error::new(key.span(), format!("expected {names}")));
4499 };
4500 content.parse::<syn::Token![=]>()?;
4501 given[at] = content.parse::<LitStr>()?.value();
4502 }
4503 Ok((text, given))
4504}
4505
4506struct Rules {
4508 conflicts: Vec<(usize, usize)>,
4510 beats: Vec<Vec<usize>>,
4512 requires: Vec<(usize, usize)>,
4514 required_unless: Vec<(usize, Vec<usize>)>,
4516 members: Vec<Vec<usize>>,
4518 default_ifs: Vec<(usize, usize, Option<String>, String)>,
4520}
4521
4522impl Rules {
4523 fn new(fields: &[Field], groups: &[Group]) -> syn::Result<Self> {
4524 let resolve = |from: &Field, selector: &str| -> syn::Result<usize> {
4525 fields
4526 .iter()
4527 .position(
4528 |f| match (selector.strip_prefix("--"), selector.strip_prefix('-')) {
4529 (Some(long), _) => f.longs().contains(&long),
4530 (None, Some(short)) => {
4531 let mut chars = short.chars();
4532 chars
4533 .next()
4534 .is_some_and(|c| chars.next().is_none() && f.shorts().contains(&c))
4535 }
4536 (None, None) => {
4537 matches!(&f.role, Role::Positional { name, .. } if name == selector)
4538 || f.ident == selector
4539 }
4540 },
4541 )
4542 .ok_or_else(|| {
4543 let negated = selector
4545 .strip_prefix("--")
4546 .and_then(|long| fields.iter().find(|f| f.negate.as_deref() == Some(long)));
4547 let message = match negated {
4548 Some(f) => format!(
4549 "`{selector}` is the negation of `{}`: name that flag instead",
4550 f.display()
4551 ),
4552 None => format!("`{selector}` names no flag or positional of this struct"),
4553 };
4554 syn::Error::new(from.ident.span(), message)
4555 })
4556 };
4557 let mut rules = Rules {
4558 conflicts: Vec::new(),
4559 beats: vec![Vec::new(); fields.len()],
4560 requires: Vec::new(),
4561 required_unless: Vec::new(),
4562 members: vec![Vec::new(); groups.len()],
4563 default_ifs: Vec::new(),
4564 };
4565 for (i, f) in fields.iter().enumerate() {
4566 for selector in &f.conflicts {
4567 let j = resolve(f, selector)?;
4568 let pair = (i.min(j), i.max(j));
4569 if !rules.conflicts.contains(&pair) {
4570 rules.conflicts.push(pair);
4571 }
4572 }
4573 for selector in &f.overrides {
4574 let j = resolve(f, selector)?;
4575 if !rules.beats[i].contains(&j) {
4576 rules.beats[i].push(j);
4577 }
4578 if !rules.beats[j].contains(&i) {
4579 rules.beats[j].push(i);
4580 }
4581 }
4582 for selector in &f.requires {
4583 rules.requires.push((i, resolve(f, selector)?));
4584 }
4585 for (selector, equals, value) in &f.default_ifs {
4586 let other = resolve(f, selector)?;
4587 let comparable = match &fields[other].kind {
4588 Kind::Count(_) => false,
4589 Kind::Switch => equals.as_deref().is_none_or(is_bool_word),
4590 _ => !fields[other].keywords,
4591 };
4592 if equals.is_some() && !comparable {
4593 return Err(syn::Error::new(
4594 f.ident.span(),
4595 format!(
4596 "`{selector}` has no value to compare: use `default_if(\"{selector}\", \"…\")`"
4597 ),
4598 ));
4599 }
4600 rules
4601 .default_ifs
4602 .push((i, other, equals.clone(), value.clone()));
4603 }
4604 if !f.required_unless.is_empty() {
4605 let others = f
4606 .required_unless
4607 .iter()
4608 .map(|selector| resolve(f, selector))
4609 .collect::<syn::Result<_>>()?;
4610 rules.required_unless.push((i, others));
4611 }
4612 if let Some(group) = &f.group {
4613 let g = groups
4614 .iter()
4615 .position(|g| &g.name == group)
4616 .ok_or_else(|| {
4617 syn::Error::new(
4618 f.ident.span(),
4619 format!("no `#[arg(group(\"{group}\"))]` on the struct"),
4620 )
4621 })?;
4622 rules.members[g].push(i);
4623 }
4624 }
4625 if let Some((g, _)) = groups
4626 .iter()
4627 .zip(&rules.members)
4628 .find(|(g, members)| g.required && members.is_empty())
4629 {
4630 return Err(syn::Error::new(
4631 Span::call_site(),
4632 format!("group `{}` is required, but no field is in it", g.name),
4633 ));
4634 }
4635 Ok(rules)
4636 }
4637
4638 fn is_displaced(&self, fields: &[Field], f: &Field) -> bool {
4640 !self.beats[index_of(fields, f)].is_empty()
4641 }
4642
4643 fn displaced_slots(&self, fields: &[Field]) -> Vec<TokenStream2> {
4645 fields
4646 .iter()
4647 .enumerate()
4648 .filter(|(i, _)| !self.beats[*i].is_empty())
4649 .map(|(_, f)| {
4650 let displaced = displaced_flag(&f.ident);
4651 quote!(let mut #displaced = false;)
4652 })
4653 .collect()
4654 }
4655
4656 fn displace(&self, fields: &[Field], f: &Field) -> TokenStream2 {
4658 let i = index_of(fields, f);
4659 if self.beats[i].is_empty() {
4660 return quote!();
4661 }
4662 let standing = displaced_flag(&f.ident);
4663 let clears = self.beats[i].iter().map(|&j| {
4664 let other = &fields[j];
4665 let slot = slot(&other.ident);
4666 let displaced = displaced_flag(&other.ident);
4667 let clear = match &other.kind {
4668 _ if other.keywords => quote!(#slot.clear();),
4669 Kind::Switch if other.negate.is_some() => {
4670 quote!(#slot = ::core::option::Option::None;)
4671 }
4672 Kind::Switch => quote!(#slot = false;),
4673 Kind::Count(_) => quote!(#slot = 0;),
4674 Kind::Optional(_) | Kind::Required(_) => {
4675 quote!(#slot = ::core::option::Option::None;)
4676 }
4677 Kind::Many(_) => quote!(#slot.clear();),
4678 };
4679 quote!(#clear #displaced = true;)
4680 });
4681 quote!(#standing = false; #(#clears)*)
4682 }
4683
4684 fn exclusive(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
4686 let pairs = self.conflicts.iter().map(|&(a, b)| {
4687 let (fa, fb) = (&fields[a], &fields[b]);
4688 let (has_a, has_b) = (fa.has(), fb.has());
4689 let (name_a, name_b) = (fa.display(), fb.display());
4690 quote! {
4691 if #has_a && #has_b {
4692 return ::core::result::Result::Err(
4693 __wa::Error::conflict(__input.offset(), #name_a, #name_b),
4694 );
4695 }
4696 }
4697 });
4698 let at_most_one = groups.iter().zip(&self.members).filter(|(g, _)| !g.multiple).map(|(_, members)| {
4699 let checks = members.iter().map(|&m| {
4700 let has = fields[m].has();
4701 let name = fields[m].display();
4702 quote! {
4703 if #has {
4704 if let ::core::option::Option::Some(__first) = __first {
4705 return ::core::result::Result::Err(
4706 __wa::Error::conflict(__input.offset(), __first, #name),
4707 );
4708 }
4709 __first = ::core::option::Option::Some(#name);
4710 }
4711 }
4712 });
4713 quote! {{
4714 let mut __first: ::core::option::Option<&'static str> = ::core::option::Option::None;
4715 #(#checks)*
4716 }}
4717 });
4718 quote!(#(#pairs)* #(#at_most_one)*)
4719 }
4720
4721 fn default_ifs(&self, fields: &[Field]) -> TokenStream2 {
4724 let (mut conditions, mut fills) = (Vec::new(), Vec::new());
4725 for (n, (field, other, equals, value)) in self.default_ifs.iter().enumerate() {
4726 let (f, other) = (&fields[*field], &fields[*other]);
4727 let holds = format_ident!("__default_if_{n}");
4728 let other_slot = slot(&other.ident);
4729 let is = |ty: &Type, equals: &str| {
4730 let literal = LitByteStr::new(equals.as_bytes(), Span::call_site());
4731 let (ty, unwrap) = other.parsed(ty);
4732 quote! {
4733 |__v| <#ty as ::winnow_args::FromArg>::from_arg(__wa::BStr::new(#literal))
4734 .is_ok_and(|__literal| __literal #unwrap == *__v)
4735 }
4736 };
4737 let condition = match (equals, &other.kind) {
4738 (None, _) => other.has(),
4739 (Some(equals), Kind::Switch) => {
4740 let equals = equals == "true";
4741 match other.negate {
4742 Some(_) => quote!((#other_slot == ::core::option::Option::Some(#equals))),
4743 None => quote!((#other_slot == #equals)),
4744 }
4745 }
4746 (Some(equals), Kind::Optional(ty) | Kind::Required(ty)) => {
4747 let is = is(ty, equals);
4748 quote!(#other_slot.as_ref().is_some_and(#is))
4749 }
4750 (Some(equals), Kind::Many(ty)) => {
4751 let is = is(ty, equals);
4752 quote!(#other_slot.iter().any(#is))
4753 }
4754 (Some(_), Kind::Count(_)) => unreachable!("refused when the rules were read"),
4755 };
4756 conditions.push(quote!(let #holds = #condition;));
4757 let unset = f.unset(self.is_displaced(fields, f));
4758 let slot = slot(&f.ident);
4759 let fill = match &f.kind {
4760 Kind::Switch => {
4761 let value = value == "true";
4762 match f.negate {
4763 Some(_) => quote!(#slot = ::core::option::Option::Some(#value);),
4764 None => quote!(#slot = #value;),
4765 }
4766 }
4767 _ => {
4768 let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
4769 f.assign(quote!(__wa::BStr::new(#bytes)), &f.display())
4770 }
4771 };
4772 fills.push(quote!(if #holds && #unset { #fill }));
4773 }
4774 quote!(#(#conditions)* #(#fills)*)
4775 }
4776
4777 fn supplied(&self, fields: &[Field]) -> TokenStream2 {
4779 let mut seen = Vec::new();
4780 let snapshots = self.requires.iter().filter_map(|&(i, _)| {
4781 if seen.contains(&i) {
4782 return None;
4783 }
4784 seen.push(i);
4785 let supplied = format_ident!("__supplied_{}", fields[i].ident);
4786 let has = fields[i].has();
4787 Some(quote!(let #supplied = #has;))
4788 });
4789 quote!(#(#snapshots)*)
4790 }
4791
4792 fn required(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
4795 let missing = |f: &Field| {
4796 let name = f.display();
4797 match f.role {
4798 Role::Positional { .. } => {
4799 quote!(__wa::Error::missing_argument(__input.offset(), #name))
4800 }
4801 _ => quote!(__wa::Error::missing_required(__input.offset(), #name)),
4802 }
4803 };
4804 let displaced = |f: &Field| {
4806 self.is_displaced(fields, f).then(|| {
4807 let flag = displaced_flag(&f.ident);
4808 quote!(&& !#flag)
4809 })
4810 };
4811 let plain = fields.iter().filter(|f| f.required).map(|f| {
4812 let (has, displaced) = (f.has(), displaced(f));
4813 let error = missing(f);
4814 quote!(if !#has #displaced { return ::core::result::Result::Err(#error); })
4815 });
4816 let unless = self.required_unless.iter().map(|(i, others)| {
4817 let (has, displaced) = (fields[*i].has(), displaced(&fields[*i]));
4818 let others = others.iter().map(|&j| fields[j].has());
4819 let error = missing(&fields[*i]);
4820 quote! {
4821 if !#has #displaced #(&& !#others)* {
4822 return ::core::result::Result::Err(#error);
4823 }
4824 }
4825 });
4826 let one_of = groups
4827 .iter()
4828 .zip(&self.members)
4829 .filter(|(g, _)| g.required)
4830 .map(|(g, members)| {
4831 let name = &g.name;
4832 let has = members.iter().map(|&m| fields[m].has());
4833 let names = members.iter().map(|&m| fields[m].display());
4834 quote! {
4835 if #(!#has)&&* {
4836 return ::core::result::Result::Err(
4837 __wa::Error::missing_one_of(__input.offset(), #name, &[#(#names),*]),
4838 );
4839 }
4840 }
4841 });
4842 let requires = self.requires.iter().map(|&(i, j)| {
4843 let supplied = format_ident!("__supplied_{}", fields[i].ident);
4844 let has = fields[j].has();
4845 let (by, target) = (fields[i].display(), fields[j].display());
4846 quote! {
4847 if #supplied && !#has {
4848 return ::core::result::Result::Err(
4849 __wa::Error::required_by(__input.offset(), #target, #by),
4850 );
4851 }
4852 }
4853 });
4854 quote!(#(#plain)* #(#unless)* #(#one_of)* #(#requires)*)
4855 }
4856}
4857
4858fn vec_field(f: Option<&syn::Field>, shape: &str) -> syn::Result<Option<(Ident, Type)>> {
4860 let Some(f) = f else {
4861 return Ok(None);
4862 };
4863 let ty = last_segment(&f.ty)
4864 .filter(|s| s.ident == "Vec")
4865 .and_then(inner)
4866 .ok_or_else(|| syn::Error::new(f.ty.span(), shape))?;
4867 Ok(Some((f.ident.clone().expect("named field"), ty.clone())))
4868}
4869
4870fn subcommand_type(f: &Field) -> &Type {
4872 match &f.kind {
4873 Kind::Optional(ty) | Kind::Required(ty) => ty,
4874 _ => unreachable!("rejected for subcommands in `field`"),
4875 }
4876}
4877
4878fn displaced_flag(ident: &Ident) -> Ident {
4879 format_ident!("__displaced_{}", ident)
4880}
4881
4882fn index_of(fields: &[Field], f: &Field) -> usize {
4883 fields
4884 .iter()
4885 .position(|g| g.ident == f.ident)
4886 .expect("a field of this struct")
4887}
4888
4889fn slot(ident: &Ident) -> Ident {
4890 format_ident!("__slot_{}", ident)
4891}
4892
4893fn field(f: &syn::Field) -> syn::Result<Field> {
4894 let ident = f.ident.clone().expect("named field");
4895 let bare = ident.to_string().trim_start_matches("r#").to_owned();
4896
4897 let mut short = None;
4898 let mut long = None;
4899 let mut positional = false;
4900 let mut count = false;
4901 let mut subcommand = false;
4902 let mut global = false;
4903 let mut alias = Vec::new();
4904 let mut delimiter = None;
4905 let mut choices = None;
4906 let mut env = None;
4907 let mut default = None;
4908 let mut double_dash = None;
4909 let mut default_missing = None;
4910 let mut negative_numbers = false;
4911 let mut hyphen_values = false;
4912 let mut require_equals = false;
4913 let mut keep_equals = false;
4914 let (mut two_dashes, mut prefix) = (false, false);
4915 let mut values = 1;
4916 let (mut values_max, mut value_terminator) = (None, None);
4917 let (mut default_fn, mut default_note, mut default_ifs) = (None, None, Vec::new());
4918 let mut stop_flags = false;
4919 let mut plus: Option<char> = None;
4920 let mut keywords = false;
4921 let mut short_aliases = Vec::new();
4922 let mut negate: Option<Option<String>> = None;
4923 let (doc_help, doc_long_help) = docs(&f.attrs);
4924 let (mut help, mut long_help, mut heading, mut hide) = (None, None, None, false);
4925 let mut warning = String::new();
4926 let mut from_str = false;
4927 let mut effect = None;
4928 let (mut conflicts, mut overrides, mut requires) = (Vec::new(), Vec::new(), Vec::new());
4929 let (mut required, mut required_unless, mut group) = (false, Vec::new(), None);
4930 let mut value_name = None;
4931 let (mut hint, mut complete) = (None, None);
4932 for attr in f.attrs.iter().filter(|a| is_ours(a)) {
4933 attr.parse_nested_meta(|meta| {
4934 if meta.path.is_ident("hint") {
4935 let name = meta.value()?.parse::<LitStr>()?;
4936 let variant = match name.value().as_str() {
4937 "file" => "Files",
4938 "dir" => "Dirs",
4939 "command" => "Commands",
4940 "none" => "Nothing",
4941 _ => {
4942 return Err(syn::Error::new(
4943 name.span(),
4944 "a hint is \"file\", \"dir\", \"command\" or \"none\"",
4945 ));
4946 }
4947 };
4948 hint = Some(syn::Ident::new(variant, name.span()));
4949 } else if meta.path.is_ident("complete") {
4950 complete = Some(meta.value()?.parse::<syn::Path>()?);
4951 } else if meta.path.is_ident("short") {
4952 let c = if meta.input.peek(syn::Token![=]) {
4953 meta.value()?.parse::<LitChar>()?.value()
4954 } else {
4955 bare.chars()
4956 .next()
4957 .ok_or_else(|| meta.error("cannot infer a short name"))?
4958 };
4959 match short {
4961 None => short = Some(c),
4962 Some(_) => short_aliases.push(c),
4963 }
4964 } else if meta.path.is_ident("long") {
4965 let name = if meta.input.peek(syn::Token![=]) {
4966 meta.value()?.parse::<LitStr>()?.value()
4967 } else {
4968 bare.replace('_', "-")
4969 };
4970 match long {
4972 None => long = Some(name),
4973 Some(_) => alias.push(name),
4974 }
4975 } else if meta.path.is_ident("positional") {
4976 positional = true;
4977 } else if meta.path.is_ident("count") {
4978 count = true;
4979 } else if meta.path.is_ident("subcommand") {
4980 subcommand = true;
4981 } else if meta.path.is_ident("global") {
4982 global = true;
4983 } else if meta.path.is_ident("alias") {
4984 alias.extend(aliases(&meta)?);
4985 } else if meta.path.is_ident("help") {
4986 help = Some(meta.value()?.parse::<LitStr>()?.value());
4987 } else if meta.path.is_ident("long_help") {
4988 long_help = Some(meta.value()?.parse::<LitStr>()?.value());
4989 } else if meta.path.is_ident("help_heading") {
4990 heading = Some(meta.value()?.parse::<LitStr>()?.value());
4991 } else if meta.path.is_ident("hide") {
4992 hide = true;
4993 } else if meta.path.is_ident("from_str") {
4994 from_str = true;
4995 } else if meta.path.is_ident("warning") {
4996 warning = meta.value()?.parse::<LitStr>()?.value();
4997 } else if meta.path.is_ident("effect") {
4998 effect = Some(effect_name(&meta)?);
4999 } else if meta.path.is_ident("negate") {
5000 negate = Some(if meta.input.peek(syn::Token![=]) {
5001 let name = meta.value()?.parse::<LitStr>()?.value();
5002 Some(name.strip_prefix("--").unwrap_or(&name).to_owned())
5004 } else {
5005 None
5006 });
5007 } else if meta.path.is_ident("keywords") {
5008 keywords = true;
5009 } else if skip_spec(&meta)? {
5010 } else if meta.path.is_ident("plus") {
5011 plus = Some(meta.value()?.parse::<LitChar>()?.value());
5012 } else if meta.path.is_ident("stop_flags") {
5013 stop_flags = true;
5014 } else if meta.path.is_ident("values") {
5015 let count = meta.value()?.parse::<syn::Expr>()?;
5016 (values, values_max) = value_count(&count)?;
5017 } else if meta.path.is_ident("value_terminator") {
5018 value_terminator = Some(meta.value()?.parse::<LitStr>()?.value());
5019 } else if meta.path.is_ident("default_fn") {
5020 default_fn = Some(meta.value()?.parse::<syn::ExprPath>()?);
5021 } else if meta.path.is_ident("default_note") {
5022 default_note = Some(meta.value()?.parse::<LitStr>()?.value());
5023 } else if meta.path.is_ident("default_if") {
5024 let content;
5025 syn::parenthesized!(content in meta.input);
5026 let list =
5027 syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
5028 let mut words = list.iter().map(LitStr::value);
5029 default_ifs.push(match (words.next(), words.next(), words.next(), words.next()) {
5030 (Some(selector), Some(value), None, _) => (selector, None, value),
5031 (Some(selector), Some(equals), Some(value), None) => {
5032 (selector, Some(equals), value)
5033 }
5034 _ => {
5035 return Err(meta.error(
5036 "expected `default_if(\"--flag\", \"value\")` or \
5037 `default_if(\"--flag\", \"its value\", \"value\")`",
5038 ));
5039 }
5040 });
5041 } else if meta.path.is_ident("two_dashes") {
5042 two_dashes = true;
5043 } else if meta.path.is_ident("prefix") {
5044 prefix = true;
5045 } else if meta.path.is_ident("require_equals") {
5046 require_equals = true;
5047 } else if meta.path.is_ident("keep_equals") {
5048 keep_equals = true;
5049 } else if meta.path.is_ident("allow_hyphen_values") {
5050 hyphen_values = true;
5051 } else if meta.path.is_ident("allow_negative_numbers") {
5052 negative_numbers = true;
5053 } else if meta.path.is_ident("default_missing") {
5054 default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
5055 } else if meta.path.is_ident("double_dash") {
5056 let mode = meta.value()?.parse::<LitStr>()?;
5057 double_dash = Some(match mode.value().as_str() {
5058 "required" => DoubleDash::Required,
5059 "automatic" => DoubleDash::Automatic,
5060 "optional" => DoubleDash::Optional,
5061 "preserve" => DoubleDash::Preserve,
5062 _ => {
5063 return Err(syn::Error::new(
5064 mode.span(),
5065 "expected \"required\", \"automatic\", \"optional\" or \"preserve\"",
5066 ));
5067 }
5068 });
5069 } else if meta.path.is_ident("conflicts") {
5070 conflicts.extend(aliases(&meta)?);
5071 } else if meta.path.is_ident("overrides") {
5072 overrides.extend(aliases(&meta)?);
5073 } else if meta.path.is_ident("requires") {
5074 requires.extend(aliases(&meta)?);
5075 } else if meta.path.is_ident("required_unless") {
5076 required_unless.extend(aliases(&meta)?);
5077 } else if meta.path.is_ident("required") {
5078 required = true;
5079 } else if meta.path.is_ident("group") {
5080 group = Some(meta.value()?.parse::<LitStr>()?.value());
5081 } else if meta.path.is_ident("env") {
5082 env = Some(meta.value()?.parse::<LitStr>()?.value());
5083 } else if meta.path.is_ident("default") {
5084 default = Some(meta.value()?.parse::<LitStr>()?.value());
5085 } else if meta.path.is_ident("choices") {
5086 let content;
5087 syn::parenthesized!(content in meta.input);
5088 let list =
5089 syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
5090 choices = Some(list.iter().map(LitStr::value).collect());
5091 } else if meta.path.is_ident("delimiter") {
5092 let c = meta.value()?.parse::<LitChar>()?;
5093 if !c.value().is_ascii() {
5094 return Err(syn::Error::new(c.span(), "a delimiter is one ASCII character"));
5095 }
5096 delimiter = Some(c.value() as u8);
5097 } else if meta.path.is_ident("value_name") {
5098 value_name = Some(meta.value()?.parse::<LitStr>()?.value());
5099 } else {
5100 return Err(meta.error(
5101 "unknown `arg` option; expected one of `short`, `long`, `alias`, `global`, `count`, \
5102 `positional`, `value_name`, `double_dash`, `subcommand`, `flatten`, `sequence`, `unknown`, `skip`, \
5103 `delimiter`, `choices`, `env`, `default`, `default_missing`, `allow_negative_numbers`, \
5104 `allow_hyphen_values`, `require_equals`, `keep_equals`, `negate`, `two_dashes`, `prefix`, `values`, \
5105 `value_terminator`, `default_fn`, `default_note`, `default_if`, `from_str`, \
5106 `stop_flags`, `plus`, `keywords`, `conflicts`, `overrides`, `requires`, `required`, `required_unless`, \
5107 `group`, `help`, `long_help`, `help_heading`, `warning`, `effect`, `hide`, `hint`, `complete`",
5108 ));
5109 }
5110 Ok(())
5111 })?;
5112 }
5113
5114 let error = |msg: String| Err(syn::Error::new(f.span(), msg));
5115 if delimiter.is_some() && (count || !matches!(kind(&f.ty), Kind::Many(_))) {
5116 return error("`delimiter` needs a `Vec<T>` field to put the pieces in".into());
5117 }
5118 let kind = if count {
5119 Kind::Count(f.ty.clone())
5120 } else {
5121 kind(&f.ty)
5122 };
5123 if default_missing.is_some()
5124 && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
5125 {
5126 return error("`default_missing` is for flags that take a value".into());
5127 }
5128 if (hyphen_values || require_equals)
5129 && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
5130 {
5131 return error(
5132 "`allow_hyphen_values` and `require_equals` are for flags that take a value".into(),
5133 );
5134 }
5135 if negative_numbers && (subcommand || matches!(kind, Kind::Switch | Kind::Count(_))) {
5136 return error("`allow_negative_numbers` is for fields that take a value".into());
5137 }
5138 let tristate =
5139 plus.is_some() && plus == short && matches!(&kind, Kind::Optional(ty) if is_bool(ty));
5140 if plus.is_some()
5141 && (positional || subcommand || !tristate && matches!(kind, Kind::Switch | Kind::Count(_)))
5142 {
5143 return error(
5144 "`plus` is for a flag taking a value, or an `Option<bool>` with the same `short` letter".into(),
5145 );
5146 }
5147 if keywords
5148 && (positional
5149 || subcommand
5150 || !matches!(kind, Kind::Required(_))
5151 || env.is_some()
5152 || default.is_some()
5153 || default_missing.is_some()
5154 || choices.is_some()
5155 || delimiter.is_some())
5156 {
5157 return error(
5158 "`keywords` is for a flag whose type derives `Args`, without `env`, `default`, \
5159 `default_missing`, `choices` or `delimiter`"
5160 .into(),
5161 );
5162 }
5163 if stop_flags && !positional {
5164 return error("`stop_flags` is for positional fields".into());
5165 }
5166 if double_dash.is_some() && !positional {
5167 return error("`double_dash` is for positional fields".into());
5168 }
5169 if (values > 1 || values_max.is_some())
5170 && (positional
5171 || subcommand
5172 || !matches!(kind, Kind::Many(_))
5173 || delimiter.is_some()
5174 || default_missing.is_some())
5175 {
5176 return error(
5177 "`values` is for a `Vec<T>` flag, without `delimiter` or `default_missing`".into(),
5178 );
5179 }
5180 if values_max.is_some() && require_equals {
5181 return error("a range of `values` takes detached words: not with `require_equals`".into());
5182 }
5183 if value_terminator.is_some() && values_max.is_none() {
5184 return error("`value_terminator` ends a range of `values` (`values = 1..`)".into());
5185 }
5186 let valueless = matches!(kind, Kind::Switch | Kind::Count(_)) && negate.is_none();
5187 if default_fn.is_some() && (default.is_some() || subcommand || keywords || valueless) {
5188 return error(
5189 "`default_fn` is for a field that takes a value, or `negate`, without `default`".into(),
5190 );
5191 }
5192 if default_note.is_some() && default_fn.is_none() {
5193 return error("`default_note` describes a `default_fn`".into());
5194 }
5195 if !default_ifs.is_empty() && (subcommand || keywords || matches!(kind, Kind::Count(_))) {
5196 return error("`default_if` is for a switch or a field that takes a value".into());
5197 }
5198 if let (Kind::Switch, Some((.., value))) =
5199 (&kind, default_ifs.iter().find(|d| !is_bool_word(&d.2)))
5200 {
5201 return error(format!(
5202 "a switch's `default_if` value is `true` or `false`, not `{value}`"
5203 ));
5204 }
5205 if two_dashes && (long.is_none() && alias.is_empty() || positional || subcommand) {
5206 return error("`two_dashes` is for flags with a long name".into());
5207 }
5208 if prefix
5209 && (short.is_none_or(|c| !c.is_ascii())
5210 || positional
5211 || subcommand
5212 || matches!(kind, Kind::Switch | Kind::Count(_)))
5213 {
5214 return error("`prefix` is for a value-taking flag with an ASCII `short` letter".into());
5215 }
5216 let flag_value_name =
5217 (!positional && !subcommand && !matches!(kind, Kind::Switch | Kind::Count(_)))
5218 .then(|| value_name.clone().unwrap_or_else(|| bare.to_uppercase()));
5219 let role = if subcommand {
5220 if positional || count || global || short.is_some() || long.is_some() || !alias.is_empty() {
5221 return error("a subcommand field takes no other `arg` options".into());
5222 }
5223 if !matches!(kind, Kind::Optional(_) | Kind::Required(_)) {
5224 return error("a subcommand field is `E` or `Option<E>`".into());
5225 }
5226 Role::Subcommand
5227 } else if positional {
5228 if short.is_some() || long.is_some() || global || !alias.is_empty() {
5229 return error("a positional field has no `short`, `long`, `alias` or `global`".into());
5230 }
5231 if matches!(kind, Kind::Switch | Kind::Count(_)) {
5232 return error("a positional field cannot be `bool` or `count`".into());
5233 }
5234 Role::Positional {
5235 double_dash: double_dash.unwrap_or(DoubleDash::Optional),
5236 name: value_name.unwrap_or_else(|| bare.to_uppercase()),
5237 }
5238 } else {
5239 if short.is_none() && long.is_none() && plus.is_none() {
5242 long = Some(bare.replace('_', "-"));
5243 }
5244 for l in long.iter().chain(&alias) {
5245 if l.is_empty() || l.starts_with('-') || l.contains('=') {
5246 return error(format!("`{l}` is not a usable long name"));
5247 }
5248 }
5249 if let Some(c) = short
5250 && (c == '-' || c == '=')
5251 {
5252 return error(format!("`{c}` is not a usable short name"));
5253 }
5254 Role::Flag {
5255 short,
5256 long,
5257 aliases: alias,
5258 global,
5259 }
5260 };
5261
5262 if choices.is_some()
5263 && (matches!(kind, Kind::Switch | Kind::Count(_)) || matches!(role, Role::Subcommand))
5264 {
5265 return error("`choices` needs a field that takes a value".into());
5266 }
5267 if (env.is_some() || default.is_some()) && matches!(role, Role::Subcommand) {
5268 return error("a subcommand field takes no `env` or `default`".into());
5269 }
5270 let negate = match negate {
5271 None => None,
5272 Some(_) if !matches!(kind, Kind::Switch) || !matches!(role, Role::Flag { .. }) => {
5273 return error("`negate` is for `bool` flags".into());
5274 }
5275 Some(Some(name)) => Some(name),
5276 Some(None) => match &role {
5277 Role::Flag { long: Some(l), .. } => Some(format!("no-{l}")),
5278 _ => return error("`negate` without a name needs a `long` to prefix".into()),
5279 },
5280 };
5281 if let Some(no) = &negate
5282 && (no.is_empty() || no.starts_with('-') || no.contains('='))
5283 {
5284 return error(format!("`{no}` is not a usable long name"));
5285 }
5286 match (&default, &negate) {
5287 (Some(d), Some(_)) if d != "true" && d != "false" => {
5288 return error("a negatable flag's `default` is \"true\" or \"false\"".into());
5289 }
5290 (Some(_), None) if matches!(kind, Kind::Switch | Kind::Count(_)) => {
5291 return error("`default` needs a field that takes a value, or `negate`".into());
5292 }
5293 _ => {}
5294 }
5295 if (hint.is_some() || complete.is_some())
5296 && (matches!(kind, Kind::Switch | Kind::Count(_))
5297 || !matches!(role, Role::Flag { .. } | Role::Positional { .. }))
5298 {
5299 return error("`hint` and `complete` are for a field that takes a value".into());
5300 }
5301 if hint.is_some() && complete.is_some() {
5302 return error("`complete` answers alone: `hint` beside it would never be asked".into());
5303 }
5304 let spec = spec_name(&f.attrs)?;
5305 if spec.is_some() && matches!(kind, Kind::Switch | Kind::Count(_)) {
5306 return error("`spec` names the type of a field's value, and this field has none".into());
5307 }
5308 Ok(Field {
5309 ident,
5310 spec,
5311 kind,
5312 role,
5313 delimiter,
5314 choices,
5315 env,
5316 default,
5317 conflicts,
5318 overrides,
5319 requires,
5320 required,
5321 required_unless,
5322 group,
5323 default_missing,
5324 long_help: long_help.or_else(|| help.clone()).unwrap_or(doc_long_help),
5325 help: help.unwrap_or(doc_help),
5326 heading,
5327 hide,
5328 value_name: if tristate { None } else { flag_value_name },
5329 hint,
5330 complete,
5331 warning,
5332 from_str,
5333 effect,
5334 negative_numbers,
5335 hyphen_values,
5336 require_equals,
5337 keep_equals,
5338 negate,
5339 two_dashes,
5340 prefix,
5341 values,
5342 values_max,
5343 value_terminator,
5344 default_fn,
5345 default_note,
5346 default_ifs,
5347 stop_flags,
5348 plus,
5349 tristate,
5350 keywords,
5351 short_aliases,
5352 })
5353}
5354
5355fn is_bool_word(word: &str) -> bool {
5356 matches!(word, "true" | "false")
5357}
5358
5359fn value_count(count: &syn::Expr) -> syn::Result<(usize, Option<usize>)> {
5362 let number = |e: &syn::Expr| match e {
5363 syn::Expr::Lit(syn::ExprLit {
5364 lit: syn::Lit::Int(n),
5365 ..
5366 }) => n.base10_parse::<usize>(),
5367 _ => Err(syn::Error::new(e.span(), "expected a number")),
5368 };
5369 let (min, max) = match count {
5370 syn::Expr::Range(range) => {
5371 let min = range.start.as_deref().map(number).transpose()?.unwrap_or(1);
5372 let max = match (&range.end, &range.limits) {
5373 (None, _) => usize::MAX,
5374 (Some(end), syn::RangeLimits::Closed(_)) => number(end)?,
5375 (Some(end), syn::RangeLimits::HalfOpen(_)) => number(end)?.saturating_sub(1),
5376 };
5377 (min, Some(max))
5378 }
5379 exact => (number(exact)?, None),
5380 };
5381 if min == 0 || max.is_some_and(|max| max < min) {
5382 return Err(syn::Error::new(
5383 count.span(),
5384 "`values` is at least 1, and a range does not end below its start",
5385 ));
5386 }
5387 Ok((min, max))
5388}
5389
5390fn spellings(f: &Field) -> Vec<&str> {
5392 let mut longs = f.longs();
5393 longs.extend(f.negate.as_deref());
5394 longs
5395}
5396
5397fn check_duplicates(fields: &[Field]) -> syn::Result<()> {
5398 for (i, a) in fields.iter().enumerate() {
5399 for b in &fields[..i] {
5400 let theirs = b.shorts();
5401 let clash = a
5402 .shorts()
5403 .into_iter()
5404 .find(|c| theirs.contains(c))
5405 .map(|c| format!("-{c}"))
5406 .or_else(|| {
5407 let longs = spellings(b);
5408 spellings(a)
5409 .into_iter()
5410 .find(|l| longs.contains(l))
5411 .map(|l| format!("--{l}"))
5412 });
5413 if let Some(name) = clash {
5414 return Err(syn::Error::new(
5415 a.ident.span(),
5416 format!("`{name}` is already used by `{}`", b.ident),
5417 ));
5418 }
5419 }
5420 }
5421 Ok(())
5422}
5423
5424fn check_positional_order(positionals: &[&Field]) -> syn::Result<()> {
5427 let rank = |f: &Field| match f.kind {
5428 Kind::Required(_) => 0,
5429 Kind::Optional(_) => 1,
5430 Kind::Many(_) | Kind::Switch | Kind::Count(_) => 2,
5431 };
5432 for pair in positionals.windows(2) {
5433 if rank(pair[1]) < rank(pair[0]) || rank(pair[0]) == 2 {
5434 return Err(syn::Error::new(
5435 pair[1].ident.span(),
5436 "positionals must be required, then optional, then one `Vec` last",
5437 ));
5438 }
5439 }
5440 Ok(())
5441}
5442
5443fn last_segment(ty: &Type) -> Option<&syn::PathSegment> {
5444 match ty {
5445 Type::Path(path) if path.qself.is_none() => path.path.segments.last(),
5446 _ => None,
5447 }
5448}
5449
5450fn inner(segment: &syn::PathSegment) -> Option<&Type> {
5451 match &segment.arguments {
5452 PathArguments::AngleBracketed(args) => match args.args.first()? {
5453 GenericArgument::Type(inner) => Some(inner),
5454 _ => None,
5455 },
5456 _ => None,
5457 }
5458}
5459
5460fn kind(ty: &Type) -> Kind {
5461 if is_bool(ty) {
5462 return Kind::Switch;
5463 }
5464 if let Some(last) = last_segment(ty)
5465 && let Some(inner) = inner(last)
5466 {
5467 if last.ident == "Option" {
5468 return Kind::Optional(inner.clone());
5469 }
5470 if last.ident == "Vec" {
5471 return Kind::Many(inner.clone());
5472 }
5473 }
5474 Kind::Required(ty.clone())
5475}