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))]
231pub fn derive_args(input: TokenStream) -> TokenStream {
232 let input = parse_macro_input!(input as DeriveInput);
233 finish(expand(&input))
234}
235
236fn finish(expanded: syn::Result<TokenStream2>) -> TokenStream {
239 let expanded = expanded.unwrap_or_else(syn::Error::into_compile_error);
240 quote! {
241 #expanded
242 const _: ::core::option::Option<&str> = ::core::option_env!("WINNOW_ARGS_SPEC");
243 }
244 .into()
245}
246
247#[proc_macro_derive(Subcommand, attributes(arg, winnow_args))]
305pub fn derive_subcommand(input: TokenStream) -> TokenStream {
306 let input = parse_macro_input!(input as DeriveInput);
307 finish(expand_subcommand(&input))
308}
309
310fn expand_subcommand(input: &DeriveInput) -> syn::Result<TokenStream2> {
312 let Data::Enum(data) = &input.data else {
313 return Err(syn::Error::new(
314 input.span(),
315 "`Subcommand` can only be derived for enums",
316 ));
317 };
318 let mut runs = None;
319 let case = rename_all(input, Some(&mut runs))?;
320 let mut names: Vec<(String, &Ident)> = Vec::new();
321 let mut arms = Vec::new();
322 let mut patterns = Vec::new();
323 let mut subs = Vec::new();
324 let mut held: Vec<(&Ident, Type, bool)> = Vec::new();
326 let spec_on = spec::enabled();
327 let filed = filed(input)?;
328 let identity = spec::identity(input.ident.span(), &filed);
329 let mut spec_variants = Vec::new();
330 let mut spec_checks = Vec::new();
331 for variant in &data.variants {
332 let ident = &variant.ident;
333 let info = variant_names(variant, &mut names, case)?;
334 let pattern = byte_patterns(&info.names);
335 let primary = LitStr::new(&info.names[0], Span::call_site());
336 let inner = match &variant.fields {
337 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
338 let ty = &fields.unnamed[0].ty;
339 Some(boxed(ty).unwrap_or(ty).clone())
340 }
341 _ => None,
342 };
343 match &inner {
344 Some(ty) => {
345 let is_boxed = matches!(&variant.fields, Fields::Unnamed(f) if boxed(&f.unnamed[0].ty).is_some());
346 held.push((ident, ty.clone(), is_boxed));
347 }
348 None if runs.is_some() => {
349 return Err(syn::Error::new(
350 ident.span(),
351 "`run` calls the type a variant holds, and this variant holds none",
352 ));
353 }
354 None => {}
355 }
356 let unit_long_about = inner.is_none().then(|| {
358 let (doc, long_doc) = docs(&variant.attrs);
359 if doc == info.about {
360 long_doc
361 } else {
362 info.about.clone()
363 }
364 });
365 let command = match (&inner, &unit_long_about) {
366 (Some(ty), _) => quote!(<#ty as ::winnow_args::Args>::HELP),
367 (None, long_about) => {
368 let long_about = text(long_about.as_deref().unwrap_or_default());
369 let about = text(&info.about);
370 quote!(&::winnow_args::help::Command {
371 name: "",
372 about: #about,
373 long_about: #long_about,
374 after_help: "",
375 after_long_help: "",
376 items: &[],
377 subcommands: &[],
378 subcommand_required: false,
379 help_flag: true,
380 help_short: true,
381 version: ::core::option::Option::None,
382 })
383 }
384 };
385 let about = match (&inner, info.about.is_empty()) {
386 (Some(ty), true) => quote!(<#ty as ::winnow_args::Args>::HELP.about),
387 _ => text(&info.about),
388 };
389 let shown = &info.names[1..=info.shown];
390 let all = &info.names;
391 let hide = info.hide;
392 if spec_on {
393 let ty = match &inner {
394 Some(ty) => {
395 let name = spec_type(spec_name(&variant.attrs)?, ty)?;
396 spec_checks.extend(spec_filed(&input.generics, quote!(Args), ty, &name));
397 Some(name)
398 }
399 None => None,
400 };
401 spec_variants.push(spec::VariantDoc {
402 name: info.names[0].clone(),
403 aliases: shown.to_vec(),
404 about: info.about.clone(),
405 long_about: unit_long_about.unwrap_or_default(),
406 hide: info.hide,
407 ty,
408 });
409 }
410 subs.push(quote! {
411 ::winnow_args::help::Sub {
412 name: #primary,
413 aliases: &[#(#shown),*],
414 names: &[#(#all),*],
415 command: #command,
416 about: #about,
417 hide: #hide,
418 }
419 });
420 let read_payload = quote! {
422 |__item| match __item {
423 Self::#ident(__read) => {
424 let _ = __read;
425 }
426 #[allow(unreachable_patterns)]
427 _ => {}
428 }
429 };
430 let parse = match &variant.fields {
431 Fields::Unit => {
432 let index = subs.len() - 1;
433 quote! {
434 __wa::finish_with(
435 __input,
436 __globals,
437 <Self as ::winnow_args::Args>::HELP.subcommands[#index].command,
438 )
439 .map(|()| Self::#ident)
440 }
441 }
442 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
443 let ty = &fields.unnamed[0].ty;
444 match boxed(ty) {
445 Some(inner) => quote! {
446 <#inner as ::winnow_args::Args>::parse_argv_with(__input, __globals)
447 .map(|v| Self::#ident(::std::boxed::Box::new(v)))
448 .inspect(#read_payload)
449 },
450 None => quote! {
451 <#ty as ::winnow_args::Args>::parse_argv_with(__input, __globals)
452 .map(Self::#ident)
453 .inspect(#read_payload)
454 },
455 }
456 }
457 _ => {
458 return Err(syn::Error::new(
459 variant.span(),
460 "a subcommand variant is a unit or holds one `Args` type",
461 ));
462 }
463 };
464 arms.push(quote!(#pattern => (#parse).map_err(|e| e.within(#primary)),));
465 patterns.push(pattern);
466 }
467 let (about_text, long_about_text) = docs(&input.attrs);
468 let (about, long_about) = (text(&about_text), text(&long_about_text));
469 if spec_on {
470 spec::write_subcommands(
471 input.ident.span(),
472 &filed,
473 &about_text,
474 &long_about_text,
475 &spec_variants,
476 )?;
477 }
478
479 let name = &input.ident;
480 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
481 let run_impl = runs.map(|runs| run_impl(input, runs, &held));
482 Ok(quote! {
483 #(#spec_checks)*
484 #run_impl
485 impl #impl_generics ::winnow_args::Subcommand for #name #ty_generics #where_clause {
486 const SPEC: &'static str = #identity;
487
488 #[inline]
489 fn has(__name: &[u8]) -> bool {
490 matches!(__name, #(#patterns)|*)
491 }
492
493 fn parse_subcommand(
494 __name: &[u8],
495 __input: &mut ::winnow_args::Argv<'_>,
496 __globals: &mut dyn ::winnow_args::Globals,
497 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
498 use ::winnow_args::__private as __wa;
499 match __name {
500 #(#arms)*
501 _ => ::core::result::Result::Err(__wa::Error::unexpected_arg(
502 __input.offset(),
503 ::std::string::String::from_utf8_lossy(__name),
504 )),
505 }
506 }
507 }
508
509 impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
510 const SPEC: &'static str = #identity;
511
512 #[inline(never)]
515 fn help_data() -> &'static ::winnow_args::help::Command {
516 <Self as ::winnow_args::Args>::HELP
517 }
518
519 const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
520 name: "",
521 about: #about,
522 long_about: #long_about,
523 after_help: "",
524 after_long_help: "",
525 items: &[],
526 subcommands: &[#(#subs),*],
527 subcommand_required: true,
528 help_flag: true,
529 help_short: true,
530 version: ::core::option::Option::None,
531 };
532
533 fn parse_argv_with(
534 __input: &mut ::winnow_args::Argv<'_>,
535 __globals: &mut dyn ::winnow_args::Globals,
536 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
537 use ::winnow_args::__private as __wa;
538 let __help = <Self as ::winnow_args::Args>::HELP;
539 loop {
541 if __input.is_empty() {
542 return ::core::result::Result::Err(
543 __wa::Error::missing_subcommand(__input.offset()),
544 );
545 }
546 let __arg = __wa::arg(__input)?;
547 match __arg {
548 __wa::Arg::Word(__word) => {
549 if <Self as __wa::Subcommand>::has(&**__word.value) {
550 return <Self as __wa::Subcommand>::parse_subcommand(
551 &**__word.value, __input, __globals,
552 );
553 }
554 if &**__word.value == b"help" {
555 return ::core::result::Result::Err(__wa::help_word(__help, __input));
556 }
557 }
558 __wa::Arg::Long(_) | __wa::Arg::Short(_)
559 if __globals.bind(&__arg, __input)? =>
560 {
561 continue;
562 }
563 __wa::Arg::Long(__flag) if __flag.name == b"help" => {
564 return ::core::result::Result::Err(__wa::Error::help(__help, true));
565 }
566 __wa::Arg::Short(__flag) if __flag.letter == 'h' => {
567 return ::core::result::Result::Err(__wa::Error::help(__help, false));
568 }
569 _ => {}
570 }
571 return ::core::result::Result::Err(__arg.unexpected());
572 }
573 }
574 }
575 })
576}
577
578fn run_impl(input: &DeriveInput, runs: Runs, held: &[(&Ident, Type, bool)]) -> TokenStream2 {
581 if held.is_empty() {
582 return quote!();
584 }
585 let name = &input.ident;
586 let (_, ty_generics, where_clause) = input.generics.split_for_impl();
587 let mut generics = input.generics.clone();
589 for param in &mut generics.params {
590 match param {
591 syn::GenericParam::Type(param) => (param.eq_token, param.default) = (None, None),
592 syn::GenericParam::Const(param) => (param.eq_token, param.default) = (None, None),
593 syn::GenericParam::Lifetime(_) => {}
594 }
595 }
596 let params = generics.params.iter();
597 let (the_trait, method) = match runs {
598 Runs::Sync => (quote!(::winnow_args::run::Run), quote!(run)),
599 Runs::Async => (quote!(::winnow_args::run::RunAsync), quote!(run_async)),
600 };
601 let bounds = held
603 .iter()
604 .map(|(_, ty, _)| quote!(#ty: #the_trait<__C, Output = __O>));
605 let own_bounds = where_clause
606 .iter()
607 .flat_map(|clause| clause.predicates.iter());
608 let arms = held.iter().map(|(ident, ty, is_boxed)| {
609 let command = match is_boxed {
610 true => quote!(*__command),
611 false => quote!(__command),
612 };
613 let call = quote!(<#ty as #the_trait<__C>>::#method(#command, __context));
614 match runs {
615 Runs::Sync => quote!(Self::#ident(__command) => #call,),
616 Runs::Async => quote!(Self::#ident(__command) => #call.await,),
617 }
618 });
619 let function = match runs {
620 Runs::Sync => quote!(fn run(self, __context: __C) -> Self::Output),
621 Runs::Async => quote!(async fn run_async(self, __context: __C) -> Self::Output),
622 };
623 quote! {
624 #[automatically_derived]
625 #[allow(clippy::future_not_send)]
627 impl<#(#params,)* __C, __O> #the_trait<__C> for #name #ty_generics
628 where
629 #(#own_bounds,)*
630 #(#bounds,)*
631 {
632 type Output = __O;
633
634 #function {
635 match self {
636 #(#arms)*
637 }
638 }
639 }
640 }
641}
642
643#[proc_macro_derive(Occurrence, attributes(arg, winnow_args))]
709pub fn derive_occurrence(input: TokenStream) -> TokenStream {
710 let input = parse_macro_input!(input as DeriveInput);
711 finish(expand_occurrence(&input))
712}
713
714#[proc_macro_derive(ValueEnum, attributes(arg, winnow_args))]
749pub fn derive_value_enum(input: TokenStream) -> TokenStream {
750 let input = parse_macro_input!(input as DeriveInput);
751 finish(expand_value_enum(&input))
752}
753
754fn expand_occurrence(input: &DeriveInput) -> syn::Result<TokenStream2> {
757 let Data::Enum(data) = &input.data else {
758 return Err(syn::Error::new(
759 input.span(),
760 "`Occurrence` can only be derived for enums",
761 ));
762 };
763 let (mut long_arms, mut short_arms) = (Vec::new(), Vec::new());
764 let (mut single_dash, mut prefixes) = (Vec::new(), Vec::new());
765 let mut letters = Vec::new();
766 let (mut word, mut unknown, mut bundle) = (None, None, None);
767 let mut seen = std::collections::HashMap::new();
768 let mut help_items = Vec::new();
770 let spec_on = spec::enabled();
771 let filed = filed(input)?;
772 let identity = spec::identity(input.ident.span(), &filed);
773 let mut spec_rows = Vec::new();
774 let mut spec_checks = Vec::new();
775 let (mut enum_hyphen_values, mut enum_keep_equals) = (false, false);
778 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
779 attr.parse_nested_meta(|meta| {
780 if meta.path.is_ident("allow_hyphen_values") {
781 enum_hyphen_values = true;
782 Ok(())
783 } else if meta.path.is_ident("keep_equals") {
784 enum_keep_equals = true;
785 Ok(())
786 } else if skip_spec(&meta)? {
787 Ok(())
788 } else {
789 Err(meta.error("expected `allow_hyphen_values`, `keep_equals` or `spec`"))
790 }
791 })?;
792 }
793 for variant in &data.variants {
794 let ident = &variant.ident;
795 let (mut short, mut longs, mut positional) = (None, Vec::new(), false);
796 let (mut two_dashes, mut prefix, mut value_name) = (false, false, None);
797 let (mut hyphen_values, mut negative_numbers) = (enum_hyphen_values, false);
798 let (mut require_equals, mut default_missing) = (false, None);
799 let mut keep_equals = enum_keep_equals;
800 let (mut skip, mut is_unknown, mut is_bundle) = (false, false, false);
801 let stated = spec_name(&variant.attrs)?;
802 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
803 attr.parse_nested_meta(|meta| {
804 if meta.path.is_ident("allow_hyphen_values") {
805 hyphen_values = true;
806 } else if meta.path.is_ident("allow_negative_numbers") {
807 negative_numbers = true;
808 } else if meta.path.is_ident("require_equals") {
809 require_equals = true;
810 } else if meta.path.is_ident("keep_equals") {
811 keep_equals = true;
812 } else if meta.path.is_ident("default_missing") {
813 default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
814 } else if meta.path.is_ident("skip") {
815 skip = true;
816 } else if meta.path.is_ident("bundle") {
817 is_bundle = true;
818 } else if meta.path.is_ident("unknown") {
819 is_unknown = true;
820 } else if meta.path.is_ident("short") {
821 if short.is_some() {
822 return Err(meta.error("an `Occurrence` variant has one `short`"));
823 }
824 short = Some(meta.value()?.parse::<LitChar>()?.value());
825 } else if meta.path.is_ident("long") {
826 longs.push(if meta.input.peek(syn::Token![=]) {
827 meta.value()?.parse::<LitStr>()?.value()
828 } else {
829 kebab_case(&ident.to_string())
830 });
831 } else if meta.path.is_ident("alias") {
832 longs.extend(aliases(&meta)?);
833 } else if meta.path.is_ident("positional") {
834 positional = true;
835 } else if meta.path.is_ident("two_dashes") {
836 two_dashes = true;
837 } else if meta.path.is_ident("prefix") {
838 prefix = true;
839 } else if meta.path.is_ident("value_name") {
840 value_name = Some(meta.value()?.parse::<LitStr>()?.value());
841 } else if skip_spec(&meta)? {
842 } else {
843 return Err(meta.error(
844 "expected `short`, `long`, `alias`, `positional`, `two_dashes`, `prefix`, \
845 `value_name`, `allow_hyphen_values`, `allow_negative_numbers`, \
846 `require_equals`, `keep_equals`, `default_missing`, `skip`, `unknown` or `bundle`",
847 ));
848 }
849 Ok(())
850 })?;
851 }
852 let takes = match &variant.fields {
853 Fields::Unit => None,
854 Fields::Unnamed(f) if f.unnamed.len() == 1 => Some(&f.unnamed[0].ty),
855 _ => {
856 return Err(syn::Error::new(
857 variant.span(),
858 "an `Occurrence` variant is a unit (a switch) or holds one value",
859 ));
860 }
861 };
862 if skip {
863 continue;
865 }
866 let spanned = takes.and_then(|ty| {
868 last_segment(ty)
869 .filter(|s| s.ident == "Spanned")
870 .and_then(inner)
871 });
872 let optional = takes.and_then(|ty| {
875 last_segment(ty)
876 .filter(|s| s.ident == "Option")
877 .and_then(inner)
878 });
879 let wrap = |value: TokenStream2, offset: TokenStream2, attached: TokenStream2| {
880 if spanned.is_some() {
881 quote! {
882 ::winnow_args::value::Spanned {
883 value: #value,
884 offset: #offset,
885 attached: #attached,
886 }
887 }
888 } else {
889 value
890 }
891 };
892 if positional || is_unknown || is_bundle {
893 let Some(ty) = takes else {
894 return Err(syn::Error::new(
895 variant.span(),
896 "a positional, `unknown` or `bundle` variant holds its value",
897 ));
898 };
899 let ty = spanned.unwrap_or(ty);
900 let slot = if positional {
901 &mut word
902 } else if is_unknown {
903 &mut unknown
904 } else {
905 &mut bundle
906 };
907 if slot.is_some() {
908 return Err(syn::Error::new(
909 variant.span(),
910 "at most one positional, one `unknown` and one `bundle` variant",
911 ));
912 }
913 let name = value_name.unwrap_or_else(|| ident.to_string().to_uppercase());
914 if positional {
915 help_items.push(occurrence_item(
916 variant,
917 None,
918 &[],
919 Some(&name),
920 Some(ty),
921 false,
922 false,
923 ));
924 if spec_on {
925 spec_checks.extend(spec_check(&input.generics, Some(ty), stated.as_ref()));
926 spec_rows.push(occurrence_spec(
927 variant,
928 spec::ItemDoc {
929 value_name: Some(name.clone()),
930 choices_ty: choices_name(stated.as_ref(), ty),
931 ..spec::ItemDoc::default()
932 },
933 ));
934 }
935 }
936 let value = wrap(
937 quote!(__word.convert::<#ty>(#name)?),
938 quote!(__word.offset),
939 quote!(false),
940 );
941 *slot = Some(quote!({
942 let __item = Self::#ident(#value);
943 match &__item {
945 Self::#ident(__read) => {
946 let _ = __read;
947 }
948 #[allow(unreachable_patterns)]
949 _ => {}
950 }
951 ::core::result::Result::Ok(::core::option::Option::Some(__item))
952 }));
953 continue;
954 }
955 if short.is_none() && longs.is_empty() {
956 longs.push(kebab_case(&ident.to_string()));
957 }
958 let spelled = short
959 .map(|c| format!("-{c}"))
960 .into_iter()
961 .chain(longs.iter().map(|l| format!("--{l}")));
962 for name in spelled {
963 if let Some(other) = seen.insert(name.clone(), ident.clone()) {
964 return Err(syn::Error::new(
965 variant.span(),
966 format!("`{name}` is already used by `{other}`"),
967 ));
968 }
969 }
970 let value = takes.map(|ty| spanned.or(optional).unwrap_or(ty));
971 help_items.push(occurrence_item(
972 variant,
973 short,
974 &longs,
975 takes
976 .is_some()
977 .then(|| value_name.as_deref().unwrap_or("VALUE")),
978 value,
979 require_equals,
980 default_missing.is_some(),
981 ));
982 if spec_on {
983 let value_ty = takes.map(|ty| spanned.or(optional).unwrap_or(ty));
984 spec_checks.extend(spec_check(&input.generics, value_ty, stated.as_ref()));
985 spec_rows.push(occurrence_spec(
986 variant,
987 spec::ItemDoc {
988 short,
989 long: longs.first().cloned(),
990 aliases: longs.iter().skip(1).cloned().collect(),
991 value_name: value_ty
992 .map(|_| value_name.clone().unwrap_or_else(|| "VALUE".to_owned())),
993 choices_ty: value_ty.and_then(|ty| choices_name(stated.as_ref(), ty)),
994 require_equals,
995 optional_value: default_missing.is_some(),
996 two_dashes,
997 prefix,
998 ..spec::ItemDoc::default()
999 },
1000 ));
1001 }
1002 let body = match takes {
1003 None => quote! {
1004 __arg.check_switch()?;
1005 ::core::result::Result::Ok(::core::option::Option::Some(Self::#ident))
1006 },
1007 Some(ty) => {
1008 let ty = spanned.or(optional).unwrap_or(ty);
1009 let options = quote! {
1010 ::winnow_args::token::ValueOptions {
1011 negative_numbers: #negative_numbers,
1012 hyphen_values: #hyphen_values,
1013 require_equals: #require_equals,
1014 keep_equals: #keep_equals,
1015 }
1016 };
1017 let attached = spanned
1018 .is_some()
1019 .then(|| quote!(let __attached = __arg.has_attached_value(__input);));
1020 let value = wrap(
1021 quote!(__arg.convert::<#ty>(__value)?),
1022 quote!(__arg.offset()),
1023 quote!(__attached),
1024 );
1025 let item = if optional.is_some() && require_equals && default_missing.is_none() {
1026 quote! {
1027 if let ::core::option::Option::Some(__value) =
1028 __arg.read_value_opt_with(__input, #options)
1029 {
1030 Self::#ident(::core::option::Option::Some(#value))
1031 } else {
1032 Self::#ident(::core::option::Option::None)
1033 }
1034 }
1035 } else {
1036 let read = match &default_missing {
1037 Some(missing) => {
1038 let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
1039 quote! {
1040 __arg.read_value_or_with(
1041 __input,
1042 #options,
1043 ::winnow_args::__private::BStr::new(#bytes),
1044 )
1045 }
1046 }
1047 None => quote!(__arg.read_value_with(__input, #options)?),
1048 };
1049 quote! {{
1050 let __value = #read;
1051 Self::#ident(#value)
1052 }}
1053 };
1054 quote! {
1055 #attached
1056 let __item = #item;
1057 match &__item {
1059 Self::#ident(__read) => {
1060 let _ = __read;
1061 }
1062 #[allow(unreachable_patterns)]
1063 _ => {}
1064 }
1065 ::core::result::Result::Ok(::core::option::Option::Some(__item))
1066 }
1067 }
1068 };
1069 if !longs.is_empty() {
1070 let pattern = byte_patterns(&longs);
1071 long_arms.push(quote!(#pattern => { #body }));
1072 if !two_dashes {
1073 single_dash.extend(longs.iter().cloned());
1074 }
1075 }
1076 if let Some(c) = short {
1077 if prefix {
1078 if !c.is_ascii() || takes.is_none() {
1079 return Err(syn::Error::new(
1080 variant.span(),
1081 "`prefix` is for a value-taking variant with an ASCII `short` letter",
1082 ));
1083 }
1084 prefixes.push(c as u8);
1085 }
1086 let letter = LitChar::new(c, Span::call_site());
1087 let takes_value = takes.is_some();
1088 letters.push(quote!(#letter => ::core::option::Option::Some(#takes_value),));
1089 short_arms.push(quote!(#letter => { #body }));
1090 }
1091 }
1092 let has_positional = word.is_some();
1093 let word =
1094 word.unwrap_or_else(|| quote!(::core::result::Result::Ok(::core::option::Option::None)));
1095 let bundle = bundle.map(|body| {
1096 quote! {
1097 const BUNDLES: bool = true;
1098
1099 fn from_bundle(
1100 __word: &::winnow_args::token::Word<'_>,
1101 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1102 #body
1103 }
1104 }
1105 });
1106 let unknown = unknown.map(|body| {
1107 quote! {
1108 fn from_unknown(
1109 __word: &::winnow_args::token::Word<'_>,
1110 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1111 #body
1112 }
1113 }
1114 });
1115 let is_long = matches_name(&single_dash);
1116 let name = &input.ident;
1117 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1118 if spec_on {
1119 spec::write_occurrence(input.ident.span(), &filed, &spec_rows)?;
1120 }
1121 Ok(quote! {
1122 #(#spec_checks)*
1123 impl #impl_generics ::winnow_args::Occurrence for #name #ty_generics #where_clause {
1124 const SPEC: &'static str = #identity;
1125
1126 const PREFIXES: &'static [u8] = &[#(#prefixes),*];
1127
1128 const POSITIONAL: bool = #has_positional;
1129
1130 const ITEMS: &'static [::winnow_args::help::Item] = &[#(#help_items),*];
1131
1132 fn from_arg<'__i>(
1133 __arg: &::winnow_args::Arg<'__i>,
1134 __input: &mut ::winnow_args::Argv<'__i>,
1135 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1136 let _ = &__input;
1137 match __arg {
1138 ::winnow_args::Arg::Long(__flag) => match __flag.name {
1139 #(#long_arms)*
1140 _ => ::core::result::Result::Ok(::core::option::Option::None),
1141 },
1142 ::winnow_args::Arg::Short(__flag) if !__flag.plus => match __flag.letter {
1143 #(#short_arms)*
1144 _ => ::core::result::Result::Ok(::core::option::Option::None),
1145 },
1146 _ => ::core::result::Result::Ok(::core::option::Option::None),
1147 }
1148 }
1149
1150 fn from_word(
1151 __word: &::winnow_args::token::Word<'_>,
1152 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1153 let _ = __word;
1154 #word
1155 }
1156
1157 fn is_long(__name: &[u8]) -> bool {
1158 #is_long
1159 }
1160
1161 #unknown
1162
1163 #bundle
1164
1165 fn short(__letter: char) -> ::core::option::Option<bool> {
1166 match __letter {
1167 #(#letters)*
1168 _ => ::core::option::Option::None,
1169 }
1170 }
1171 }
1172 })
1173}
1174
1175fn occurrence_item(
1178 variant: &syn::Variant,
1179 short: Option<char>,
1180 longs: &[String],
1181 value_name: Option<&str>,
1182 value: Option<&Type>,
1183 require_equals: bool,
1184 optional_value: bool,
1185) -> TokenStream2 {
1186 let (doc, long_doc) = docs(&variant.attrs);
1187 let (help, long_help) = (text(&doc), text(&long_doc));
1188 let (choices, hint) = match value {
1190 Some(ty) => (
1191 quote!(<#ty as ::winnow_args::FromArg>::CHOICES),
1192 quote!(<#ty as ::winnow_args::FromArg>::HINT),
1193 ),
1194 None => (quote!(&[]), quote!(::winnow_args::complete::Hint::Nothing)),
1195 };
1196 let positional = short.is_none() && longs.is_empty();
1197 let short = opt_char(short);
1198 let long = opt_str(longs.first().map(String::as_str));
1199 let aliases = longs.iter().skip(1);
1200 let value_name = opt_str(value_name);
1201 quote!(::winnow_args::help::Item {
1202 short: #short,
1203 long: #long,
1204 aliases: &[#(#aliases),*],
1205 negate: ::core::option::Option::None,
1206 value_name: #value_name,
1207 help: #help,
1208 long_help: #long_help,
1209 heading: ::core::option::Option::None,
1210 hide: false,
1211 positional: #positional,
1212 required: false,
1213 multiple: #positional,
1214 trailing: false,
1215 default: ::core::option::Option::None,
1216 env: ::core::option::Option::None,
1217 choices: #choices,
1218 require_equals: #require_equals,
1219 global: false,
1220 more_shorts: &[],
1221 plus: ::core::option::Option::None,
1222 optional_value: #optional_value,
1223 values: 1,
1224 more_values: false,
1225 hint: #hint,
1226 complete: ::core::option::Option::None,
1227 })
1228}
1229
1230fn spec_name(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
1233 let mut name = None;
1234 for attr in attrs.iter().filter(|a| is_ours(a)) {
1235 let _ = attr.parse_nested_meta(|meta| {
1237 if meta.path.is_ident("spec") {
1238 name = Some(meta.value()?.parse::<LitStr>()?);
1239 } else if meta.input.peek(syn::Token![=]) {
1240 meta.value()?.parse::<syn::Expr>()?;
1241 } else if meta.input.peek(syn::token::Paren) {
1242 let _content;
1243 syn::parenthesized!(_content in meta.input);
1244 _content.parse::<TokenStream2>()?;
1245 }
1246 Ok(())
1247 });
1248 }
1249 let Some(name) = name else {
1250 return Ok(None);
1251 };
1252 let text = name.value();
1253 if text.is_empty() || text.contains(['/', '\\', ':', '.']) {
1254 return Err(syn::Error::new(
1255 name.span(),
1256 "`spec` is a name as a type's is: a file is named after it",
1257 ));
1258 }
1259 Ok(Some(text))
1260}
1261
1262fn skip_spec(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<bool> {
1264 if !meta.path.is_ident("spec") {
1265 return Ok(false);
1266 }
1267 meta.value()?.parse::<LitStr>()?;
1268 Ok(true)
1269}
1270
1271fn filed(input: &DeriveInput) -> syn::Result<String> {
1273 Ok(spec_name(&input.attrs)?.unwrap_or_else(|| input.ident.unraw().to_string()))
1274}
1275
1276fn type_path(ty: &Type) -> Option<String> {
1285 let Type::Path(path) = boxed(ty).unwrap_or(ty) else {
1286 return None;
1287 };
1288 if path.qself.is_some() {
1289 return None;
1290 }
1291 let mut names: Vec<String> = path
1292 .path
1293 .segments
1294 .iter()
1295 .map(|segment| segment.ident.unraw().to_string())
1296 .collect();
1297 if let [name] = &names[..] {
1298 match spec::origin(ty.span(), name) {
1299 spec::Origin::Here => {}
1300 spec::Origin::Imported(from) => names = from,
1301 spec::Origin::Unknown => names.insert(0, "?".to_owned()),
1302 }
1303 }
1304 let rooted = names
1305 .iter()
1306 .take_while(|name| matches!(name.as_str(), "crate" | "self" | "super"))
1307 .count();
1308 if rooted > 0 {
1309 let supers = names[..rooted]
1310 .iter()
1311 .filter(|name| *name == "super")
1312 .count();
1313 let relative = names[0] != "crate";
1314 let modules = relative.then(|| spec::above(supers)).flatten();
1315 let root = ["crate".to_owned()].into_iter();
1316 names.splice(..rooted, root.chain(modules.unwrap_or_default()));
1317 }
1318 (names.len() > usize::from(rooted > 0)).then(|| names.join("::"))
1319}
1320
1321fn spec_type(stated: Option<String>, ty: &Type) -> syn::Result<String> {
1324 stated.or_else(|| type_path(ty)).ok_or_else(|| {
1325 syn::Error::new(
1326 ty.span(),
1327 "this type needs a name when `WINNOW_ARGS_SPEC` is set: state it, as `spec = \"Name\"`",
1328 )
1329 })
1330}
1331
1332fn spec_filed(
1336 generics: &syn::Generics,
1337 kind: TokenStream2,
1338 ty: &Type,
1339 name: &str,
1340) -> Option<TokenStream2> {
1341 if !generics.params.is_empty() {
1342 return None;
1343 }
1344 let ty = boxed(ty).unwrap_or(ty);
1345 let name = name.strip_prefix("?::").unwrap_or(name);
1346 Some(quote::quote_spanned! {ty.span()=>
1347 const _: () = ::core::assert!(
1348 ::winnow_args::__private::filed_as(<#ty as ::winnow_args::#kind>::SPEC, #name),
1349 "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\"`",
1350 );
1351 })
1352}
1353
1354fn value_type(field: &Field) -> Option<&Type> {
1356 if !field.takes_value() || field.keywords || matches!(field.role, Role::Subcommand) {
1357 return None;
1358 }
1359 match &field.kind {
1360 Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty) => Some(ty),
1361 Kind::Switch | Kind::Count(_) => None,
1362 }
1363}
1364
1365fn choices_type(field: &Field) -> Option<&Type> {
1368 value_type(field).filter(|_| field.choices.is_none())
1369}
1370
1371fn spec_check_stated(generics: &syn::Generics, field: &Field) -> Option<TokenStream2> {
1374 let stated = field.choices.as_ref()?;
1375 let ty = value_type(field).filter(|_| generics.params.is_empty())?;
1376 Some(quote::quote_spanned! {ty.span()=>
1377 const _: () = ::core::assert!(
1378 ::winnow_args::__private::stated_choices::<#ty>(&[#(#stated),*]),
1379 "the choices stated on this field are not those of its type, in number, spelling or order",
1380 );
1381 })
1382}
1383
1384fn choices_link(field: &Field) -> Option<String> {
1387 choices_name(field.spec.as_ref(), choices_type(field)?)
1388}
1389
1390fn choices_name(stated: Option<&String>, ty: &Type) -> Option<String> {
1391 stated.cloned().or_else(|| type_path(ty))
1392}
1393
1394fn spec_check(
1398 generics: &syn::Generics,
1399 ty: Option<&Type>,
1400 stated: Option<&String>,
1401) -> Option<TokenStream2> {
1402 let ty = ty.filter(|_| generics.params.is_empty())?;
1403 let name = choices_name(stated, ty)?;
1404 let name = name.strip_prefix("?::").unwrap_or(&name);
1405 Some(quote::quote_spanned! {ty.span()=>
1406 const _: () = ::core::assert!(
1407 ::winnow_args::__private::documented_choices::<#ty>(#name),
1408 "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\")`",
1409 );
1410 })
1411}
1412
1413fn package_version(version: &Option<TokenStream2>, enabled: bool) -> Option<String> {
1416 if !enabled {
1417 return None;
1418 }
1419 let tokens = version.as_ref()?;
1420 if let Ok(lit) = syn::parse2::<LitStr>(tokens.clone()) {
1421 let text = lit.value();
1422 return (!text.is_empty()).then_some(text);
1423 }
1424 std::env::var("CARGO_PKG_VERSION")
1425 .ok()
1426 .filter(|text| !text.is_empty())
1427}
1428
1429fn spec_item(field: &Field) -> syn::Result<spec::ItemDoc> {
1430 let longs = field.longs();
1431 let keywords = if field.keywords {
1432 match &field.kind {
1433 Kind::Required(ty) => Some(spec_type(field.spec.clone(), ty)?),
1434 _ => {
1435 return Err(syn::Error::new(
1436 field.ident.span(),
1437 "`keywords` needs a named type",
1438 ));
1439 }
1440 }
1441 } else {
1442 None
1443 };
1444 let required = (matches!(field.kind, Kind::Required(_))
1445 && field.default.is_none()
1446 && field.default_fn.is_none()
1447 && field.env.is_none()
1448 && !field.keywords)
1449 || field.required;
1450 Ok(spec::ItemDoc {
1451 short: field.short(),
1452 more_shorts: field.short_aliases.clone(),
1453 plus: field.plus,
1454 long: longs.first().map(|name| (*name).to_owned()),
1455 aliases: longs
1456 .iter()
1457 .skip(1)
1458 .map(|name| (*name).to_owned())
1459 .collect(),
1460 negate: field.negate.clone(),
1461 value_name: if field.is_positional() {
1462 Some(field.display())
1463 } else {
1464 field.value_name.clone()
1465 },
1466 help: field.help.clone(),
1467 long_help: field.long_help.clone(),
1468 heading: field.heading.clone(),
1469 hide: field.hide,
1470 positional: field.is_positional(),
1471 required,
1472 multiple: matches!(field.kind, Kind::Many(_) | Kind::Count(_)),
1473 trailing: field.double_dash() == DoubleDash::Required,
1474 default: field.default.clone().or_else(|| field.default_note.clone()),
1475 env: field.env.clone(),
1476 choices: field.choices.clone().unwrap_or_default(),
1477 choices_ty: choices_link(field),
1478 require_equals: field.require_equals,
1479 global: field.is_global(),
1480 optional_value: field.default_missing.is_some()
1481 || field.require_equals && matches!(field.kind, Kind::Optional(_)),
1482 values: field.values,
1483 more_values: field.values_max.is_some(),
1484 two_dashes: field.two_dashes,
1485 prefix: field.prefix,
1486 stop_flags: field.stop_flags,
1487 keywords,
1488 })
1489}
1490
1491fn occurrence_spec(variant: &syn::Variant, mut item: spec::ItemDoc) -> spec::ItemDoc {
1492 let (help, long_help) = docs(&variant.attrs);
1493 item.help = help;
1494 item.long_help = long_help;
1495 item.positional = item.short.is_none() && item.long.is_none();
1496 item.multiple = item.positional;
1497 item
1498}
1499
1500fn expand_value_enum(input: &DeriveInput) -> syn::Result<TokenStream2> {
1502 let Data::Enum(data) = &input.data else {
1503 return Err(syn::Error::new(
1504 input.span(),
1505 "`ValueEnum` can only be derived for enums",
1506 ));
1507 };
1508 let case = rename_all(input, None)?;
1509 let mut names: Vec<(String, &Ident)> = Vec::new();
1510 let mut arms = Vec::new();
1511 let mut visible = Vec::new();
1512 let spec_on = spec::enabled();
1513 let filed = filed(input)?;
1514 let identity = spec::identity(input.ident.span(), &filed);
1515 let mut spec_choices = Vec::new();
1516 for variant in &data.variants {
1517 if !matches!(variant.fields, Fields::Unit) {
1518 return Err(syn::Error::new(
1519 variant.span(),
1520 "a `ValueEnum` variant holds no data",
1521 ));
1522 }
1523 let Variant {
1524 names: spellings,
1525 hide,
1526 ..
1527 } = variant_names(variant, &mut names, case)?;
1528 let name = LitStr::new(&spellings[0], Span::call_site());
1529 if !hide {
1530 if spec_on {
1531 spec_choices.push(spellings[0].clone());
1532 }
1533 visible.push(name);
1534 }
1535 let pattern = byte_patterns(&spellings);
1536 let ident = &variant.ident;
1537 arms.push(quote!(#pattern => ::core::result::Result::Ok(Self::#ident),));
1538 }
1539 let name = &input.ident;
1540 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1541 if spec_on {
1542 spec::write_choices(input.ident.span(), &filed, &spec_choices)?;
1543 }
1544 Ok(quote! {
1545 impl #impl_generics ::winnow_args::FromArg for #name #ty_generics #where_clause {
1546 const CHOICES: &'static [&'static str] = &[#(#visible),*];
1547 const SPEC: &'static str = #identity;
1548
1549 fn from_arg(
1550 __value: &::winnow_args::__private::BStr,
1551 ) -> ::core::result::Result<Self, ::winnow_args::__private::BoxError> {
1552 match &**__value {
1553 #(#arms)*
1554 _ => ::core::result::Result::Err(::std::boxed::Box::new(
1555 ::winnow_args::ChoiceError { choices: Self::CHOICES },
1556 )),
1557 }
1558 }
1559 }
1560 })
1561}
1562
1563fn text(prose: &str) -> TokenStream2 {
1565 if prose.is_empty() {
1566 quote!("")
1567 } else {
1568 quote!(::winnow_args::__text!(#prose))
1569 }
1570}
1571
1572struct Variant {
1574 names: Vec<String>,
1576 shown: usize,
1578 hide: bool,
1579 about: String,
1580}
1581
1582#[derive(Clone, Copy)]
1585enum Case {
1586 Kebab,
1588 Lower,
1590 Upper,
1592 Snake,
1594 Verbatim,
1596}
1597
1598impl Case {
1599 fn apply(self, ident: &str) -> String {
1600 let ident = ident.trim_start_matches("r#");
1601 match self {
1602 Case::Kebab => kebab_case(ident),
1603 Case::Lower => ident.to_lowercase(),
1604 Case::Upper => ident.to_uppercase(),
1605 Case::Snake => kebab_case(ident).replace('-', "_"),
1606 Case::Verbatim => ident.to_owned(),
1607 }
1608 }
1609}
1610
1611#[derive(Clone, Copy, PartialEq)]
1614enum Runs {
1615 Sync,
1616 Async,
1617}
1618
1619fn rename_all(input: &DeriveInput, runs: Option<&mut Option<Runs>>) -> syn::Result<Case> {
1622 let mut case = Case::Kebab;
1623 let mut found = None;
1624 let may_run = runs.is_some();
1625 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
1626 attr.parse_nested_meta(|meta| {
1627 if skip_spec(&meta)? {
1628 return Ok(());
1629 }
1630 let run = match () {
1631 () if meta.path.is_ident("run") => Some(Runs::Sync),
1632 () if meta.path.is_ident("run_async") => Some(Runs::Async),
1633 () => None,
1634 };
1635 if let (Some(run), true) = (run, may_run) {
1636 if found.replace(run).is_some() {
1637 return Err(meta.error("`run` or `run_async`, once"));
1638 }
1639 return Ok(());
1640 }
1641 if !meta.path.is_ident("rename_all") {
1642 return Err(meta.error(match may_run {
1643 true => "expected `rename_all`, `run`, `run_async` or `spec`",
1644 false => "expected `rename_all` or `spec`",
1645 }));
1646 }
1647 let lit = meta.value()?.parse::<LitStr>()?;
1648 case = match lit.value().as_str() {
1649 "kebab-case" => Case::Kebab,
1650 "lowercase" => Case::Lower,
1651 "UPPERCASE" => Case::Upper,
1652 "snake_case" => Case::Snake,
1653 "verbatim" => Case::Verbatim,
1654 _ => {
1655 return Err(syn::Error::new(
1656 lit.span(),
1657 "expected \"kebab-case\", \"lowercase\", \"UPPERCASE\", \"snake_case\" or \"verbatim\"",
1658 ));
1659 }
1660 };
1661 Ok(())
1662 })?;
1663 }
1664 if let Some(runs) = runs {
1665 *runs = found;
1666 }
1667 Ok(case)
1668}
1669
1670fn variant_names<'a>(
1672 variant: &'a syn::Variant,
1673 seen: &mut Vec<(String, &'a Ident)>,
1674 case: Case,
1675) -> syn::Result<Variant> {
1676 let holds_type = !matches!(variant.fields, Fields::Unit);
1677 let (mut name, mut alias, mut hidden, mut hide) = (None, Vec::new(), Vec::new(), false);
1678 let mut help: Option<String> = None;
1679 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
1680 attr.parse_nested_meta(|meta| {
1681 if meta.path.is_ident("name") {
1682 name = Some(meta.value()?.parse::<LitStr>()?.value());
1683 } else if meta.path.is_ident("alias") {
1684 alias.extend(aliases(&meta)?);
1685 } else if meta.path.is_ident("alias_hidden") {
1686 hidden.extend(aliases(&meta)?);
1687 } else if meta.path.is_ident("hide") {
1688 hide = true;
1689 } else if meta.path.is_ident("help") {
1690 help = Some(meta.value()?.parse::<LitStr>()?.value());
1691 } else if meta.path.is_ident("long_help") {
1692 meta.value()?.parse::<LitStr>()?;
1694 } else if skip_spec(&meta)? {
1695 if !holds_type {
1696 return Err(meta
1697 .error("`spec` names the type a variant holds, and this one holds none"));
1698 }
1699 } else {
1700 return Err(meta.error(
1701 "expected `name`, `alias`, `alias_hidden`, `hide`, `help` or `long_help`",
1702 ));
1703 }
1704 Ok(())
1705 })?;
1706 }
1707 let ident = &variant.ident;
1708 let shown = alias.len();
1709 let names: Vec<String> =
1710 std::iter::once(name.unwrap_or_else(|| case.apply(&ident.to_string())))
1711 .chain(alias)
1712 .chain(hidden)
1713 .collect();
1714 for spelling in &names {
1715 if let Some((_, other)) = seen.iter().find(|(n, _)| n == spelling) {
1716 return Err(syn::Error::new(
1717 ident.span(),
1718 format!("`{spelling}` is already used by `{other}`"),
1719 ));
1720 }
1721 seen.push((spelling.clone(), ident));
1722 }
1723 Ok(Variant {
1724 names,
1725 shown,
1726 hide,
1727 about: help
1728 .map(|h| {
1729 h.split("\n\n")
1730 .next()
1731 .unwrap_or_default()
1732 .replace('\n', " ")
1733 })
1734 .unwrap_or_else(|| docs(&variant.attrs).0),
1735 })
1736}
1737
1738fn docs(attrs: &[syn::Attribute]) -> (String, String) {
1740 let lines: Vec<String> = attrs
1741 .iter()
1742 .filter(|a| a.path().is_ident("doc"))
1743 .filter_map(|a| match &a.meta {
1744 syn::Meta::NameValue(nv) => match &nv.value {
1745 syn::Expr::Lit(syn::ExprLit {
1746 lit: syn::Lit::Str(s),
1747 ..
1748 }) => Some(s.value()),
1749 _ => None,
1750 },
1751 _ => None,
1752 })
1753 .map(|line| {
1754 line.strip_prefix(' ')
1755 .unwrap_or(&line)
1756 .trim_end()
1757 .to_owned()
1758 })
1759 .collect();
1760 let long = lines.join("\n").trim().to_owned();
1761 let short = long
1762 .split("\n\n")
1763 .next()
1764 .unwrap_or_default()
1765 .replace('\n', " ");
1766 (short, long)
1767}
1768
1769fn opt_str(s: Option<&str>) -> TokenStream2 {
1771 match s {
1772 Some(s) => quote!(::core::option::Option::Some(#s)),
1773 None => quote!(::core::option::Option::None),
1774 }
1775}
1776
1777fn opt_char(c: Option<char>) -> TokenStream2 {
1779 match c {
1780 Some(c) => quote!(::core::option::Option::Some(#c)),
1781 None => quote!(::core::option::Option::None),
1782 }
1783}
1784
1785fn byte_patterns<S: AsRef<str>>(names: &[S]) -> TokenStream2 {
1787 let literals = names
1788 .iter()
1789 .map(|n| LitByteStr::new(n.as_ref().as_bytes(), Span::call_site()));
1790 quote!(#(#literals)|*)
1791}
1792
1793fn matches_name(names: &[String]) -> TokenStream2 {
1795 if names.is_empty() {
1796 quote!(false)
1797 } else {
1798 let names = byte_patterns(names);
1799 quote!(matches!(__name, #names))
1800 }
1801}
1802
1803fn aliases(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<Vec<String>> {
1805 if meta.input.peek(syn::Token![=]) {
1806 return Ok(vec![meta.value()?.parse::<LitStr>()?.value()]);
1807 }
1808 let content;
1809 syn::parenthesized!(content in meta.input);
1810 let names = syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
1811 Ok(names.iter().map(LitStr::value).collect())
1812}
1813
1814fn kebab_case(ident: &str) -> String {
1816 let mut out = String::new();
1817 for (i, c) in ident.trim_start_matches("r#").chars().enumerate() {
1818 if c.is_uppercase() {
1819 if i > 0 {
1820 out.push('-');
1821 }
1822 out.extend(c.to_lowercase());
1823 } else {
1824 out.push(c);
1825 }
1826 }
1827 out
1828}
1829
1830fn is_ours(attr: &syn::Attribute) -> bool {
1833 attr.path().is_ident("arg") || attr.path().is_ident("winnow_args")
1834}
1835
1836fn is_bool(ty: &Type) -> bool {
1838 last_segment(ty).is_some_and(|s| s.ident == "bool" && s.arguments.is_none())
1839}
1840
1841fn is_sequence(f: &syn::Field) -> bool {
1843 has_flag(f, "sequence")
1844}
1845
1846fn is_flatten(f: &syn::Field) -> bool {
1848 has_flag(f, "flatten")
1849}
1850
1851fn is_unknown(f: &syn::Field) -> bool {
1853 has_flag(f, "unknown") && !is_sequence(f)
1854}
1855
1856fn is_skipped(f: &syn::Field) -> bool {
1858 has_flag(f, "skip")
1859}
1860
1861fn has_flag(f: &syn::Field, name: &str) -> bool {
1863 f.attrs.iter().filter(|a| is_ours(a)).any(|a| {
1864 let mut found = false;
1865 let _ = a.parse_nested_meta(|meta| {
1866 if meta.path.is_ident(name) {
1867 found = true;
1868 } else if meta.input.peek(syn::Token![=]) {
1869 meta.value()?.parse::<syn::Expr>()?;
1870 } else if meta.input.peek(syn::token::Paren) {
1871 let _content;
1872 syn::parenthesized!(_content in meta.input);
1873 }
1874 Ok(())
1875 });
1876 found
1877 })
1878}
1879
1880fn role_alone(f: &syn::Field) -> syn::Result<()> {
1883 let role = ["skip", "flatten", "sequence", "unknown"]
1884 .into_iter()
1885 .find(|role| has_flag(f, role));
1886 let Some(role) = role else {
1887 return Ok(());
1888 };
1889 for attr in f.attrs.iter().filter(|a| is_ours(a)) {
1890 attr.parse_nested_meta(|meta| {
1891 let alone =
1892 meta.path.is_ident(role) || (role == "sequence" && meta.path.is_ident("unknown"));
1893 let named = matches!(role, "flatten" | "sequence") && skip_spec(&meta)?;
1896 if alone || named {
1897 Ok(())
1898 } else {
1899 Err(meta.error(format!("a `{role}` field takes no other `arg` options")))
1900 }
1901 })?;
1902 }
1903 Ok(())
1904}
1905
1906fn boxed(ty: &Type) -> Option<&Type> {
1908 let last = last_segment(ty)?;
1909 if last.ident == "Box" {
1910 inner(last)
1911 } else {
1912 None
1913 }
1914}
1915
1916enum Kind {
1917 Switch,
1919 Count(Type),
1921 Optional(Type),
1923 Required(Type),
1925 Many(Type),
1927}
1928
1929enum Role {
1930 Flag {
1931 short: Option<char>,
1932 long: Option<String>,
1933 aliases: Vec<String>,
1935 global: bool,
1937 },
1938 Positional {
1939 name: String,
1940 double_dash: DoubleDash,
1941 },
1942 Subcommand,
1943}
1944
1945#[derive(Clone, Copy, PartialEq, Eq)]
1947enum DoubleDash {
1948 Optional,
1950 Required,
1952 Automatic,
1954 Preserve,
1956}
1957
1958struct Field {
1959 ident: Ident,
1960 spec: Option<String>,
1963 kind: Kind,
1964 role: Role,
1965 delimiter: Option<u8>,
1967 choices: Option<Vec<String>>,
1969 env: Option<String>,
1971 default: Option<String>,
1973 conflicts: Vec<String>,
1975 overrides: Vec<String>,
1977 requires: Vec<String>,
1979 required: bool,
1981 required_unless: Vec<String>,
1983 group: Option<String>,
1985 default_missing: Option<String>,
1987 help: String,
1989 long_help: String,
1990 heading: Option<String>,
1991 hide: bool,
1992 value_name: Option<String>,
1994 hint: Option<syn::Ident>,
1997 complete: Option<syn::Path>,
1999 negative_numbers: bool,
2001 hyphen_values: bool,
2003 require_equals: bool,
2005 keep_equals: bool,
2007 negate: Option<String>,
2010 two_dashes: bool,
2012 values: usize,
2014 values_max: Option<usize>,
2017 value_terminator: Option<String>,
2019 default_fn: Option<syn::ExprPath>,
2021 default_note: Option<String>,
2023 default_ifs: Vec<(String, Option<String>, String)>,
2025 stop_flags: bool,
2027 plus: Option<char>,
2029 tristate: bool,
2032 keywords: bool,
2035 short_aliases: Vec<char>,
2037 prefix: bool,
2040}
2041
2042impl Field {
2043 fn help_item(&self) -> TokenStream2 {
2045 let short = opt_char(self.short());
2046 let long = opt_str(self.longs().first().copied());
2047 let aliases = self.longs().into_iter().skip(1);
2048 let negate = opt_str(self.negate.as_deref());
2049 let require_equals = self.require_equals;
2050 let global = self.is_global();
2051 let positional = self.is_positional();
2052 let trailing = self.double_dash() == DoubleDash::Required;
2053 let display = self.display();
2054 let value_name = if positional {
2055 opt_str(Some(&display))
2056 } else {
2057 opt_str(self.value_name.as_deref())
2058 };
2059 let (help, long_help) = (text(&self.help), text(&self.long_help));
2060 let heading = opt_str(self.heading.as_deref());
2061 let hide = self.hide;
2062 let more_shorts = self.short_aliases.iter();
2063 let plus = opt_char(self.plus);
2064 let optional_value = self.default_missing.is_some()
2065 || require_equals && matches!(self.kind, Kind::Optional(_));
2066 let values = self.values;
2067 let more_values = self.values_max.is_some();
2068 let hint = match (&self.hint, &self.kind, &self.role) {
2069 (Some(hint), _, _) => quote!(::winnow_args::complete::Hint::#hint),
2070 (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), role)
2071 if !matches!(role, Role::Subcommand) && !self.keywords && !self.tristate =>
2072 {
2073 quote!(<#ty as ::winnow_args::FromArg>::HINT)
2074 }
2075 (None, _, _) => quote!(::winnow_args::complete::Hint::Nothing),
2076 };
2077 let complete = match &self.complete {
2078 Some(function) => quote!(::core::option::Option::Some(#function)),
2079 None => quote!(::core::option::Option::None),
2080 };
2081 let required = matches!(self.kind, Kind::Required(_))
2082 && self.default.is_none()
2083 && self.default_fn.is_none()
2084 && self.env.is_none()
2085 && !self.keywords
2086 || self.required;
2087 let multiple = matches!(self.kind, Kind::Many(_) | Kind::Count(_));
2088 let default = opt_str(self.default.as_deref().or(self.default_note.as_deref()));
2089 let env = opt_str(self.env.as_deref());
2090 let choices = match (&self.choices, &self.kind, &self.role) {
2092 (Some(choices), _, _) => quote!(&[#(#choices),*]),
2093 (None, _, Role::Subcommand) | (None, Kind::Switch | Kind::Count(_), _) => quote!(&[]),
2094 (None, _, _) if self.keywords || self.tristate => quote!(&[]),
2095 (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), _) => {
2096 quote!(<#ty as ::winnow_args::FromArg>::CHOICES)
2097 }
2098 };
2099 quote! {
2100 ::winnow_args::help::Item {
2101 short: #short,
2102 long: #long,
2103 aliases: &[#(#aliases),*],
2104 negate: #negate,
2105 value_name: #value_name,
2106 help: #help,
2107 long_help: #long_help,
2108 heading: #heading,
2109 hide: #hide,
2110 positional: #positional,
2111 required: #required,
2112 multiple: #multiple,
2113 trailing: #trailing,
2114 default: #default,
2115 env: #env,
2116 choices: #choices,
2117 require_equals: #require_equals,
2118 global: #global,
2119 more_shorts: &[#(#more_shorts),*],
2120 plus: #plus,
2121 optional_value: #optional_value,
2122 values: #values,
2123 more_values: #more_values,
2124 hint: #hint,
2125 complete: #complete,
2126 }
2127 }
2128 }
2129
2130 fn value_options(&self) -> TokenStream2 {
2132 let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
2133 self.negative_numbers,
2134 self.hyphen_values,
2135 self.require_equals,
2136 self.keep_equals,
2137 );
2138 quote! {
2139 __wa::ValueOptions {
2140 negative_numbers: #negative_numbers,
2141 hyphen_values: #hyphen_values,
2142 require_equals: #require_equals,
2143 keep_equals: #keep_equals,
2144 }
2145 }
2146 }
2147
2148 fn read(&self) -> TokenStream2 {
2150 let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
2151 self.negative_numbers,
2152 self.hyphen_values,
2153 self.require_equals,
2154 self.keep_equals,
2155 );
2156 let options = self.value_options();
2157 match &self.default_missing {
2158 None if !negative_numbers && !hyphen_values && !require_equals && !keep_equals => {
2159 quote!(__arg.read_value(__input)?)
2160 }
2161 None => quote!(__arg.read_value_with(__input, #options)?),
2162 Some(missing) => {
2163 let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
2164 quote!(__arg.read_value_or_with(__input, #options, __wa::BStr::new(#bytes)))
2165 }
2166 }
2167 }
2168
2169 fn check(
2171 &self,
2172 value: TokenStream2,
2173 error: impl FnOnce(TokenStream2) -> TokenStream2,
2174 ) -> TokenStream2 {
2175 let Some(choices) = &self.choices else {
2176 return quote!();
2177 };
2178 let pattern = byte_patterns(choices);
2179 let listed = choices.iter().map(|c| LitStr::new(c, Span::call_site()));
2180 let cause = quote!(::winnow_args::ChoiceError { choices: &[#(#listed),*] });
2181 let error = error(cause);
2182 quote! {
2183 if !matches!(&**#value, #pattern) {
2184 return ::core::result::Result::Err(#error);
2185 }
2186 }
2187 }
2188
2189 fn flag_value(&self, ty: &Type, value: TokenStream2) -> TokenStream2 {
2191 let check = self.check(value.clone(), |cause| {
2192 quote!(__wa::Error::invalid_value(__arg.offset(), __arg.spelling(), #value, #cause))
2193 });
2194 quote!({ #check __arg.convert::<#ty>(#value)? })
2195 }
2196
2197 fn source_value(&self, ty: &Type, value: TokenStream2, source: &str) -> TokenStream2 {
2200 let source = LitStr::new(source, Span::call_site());
2201 let check = self.check(
2202 value.clone(),
2203 |cause| quote!(__wa::Error::invalid_value(__input.offset(), #source, #value, #cause)),
2204 );
2205 quote!({
2206 #check
2207 <#ty as __wa::FromArg>::from_arg(#value).map_err(|cause| {
2208 __wa::Error::invalid_value(__input.offset(), #source, #value, cause)
2209 })?
2210 })
2211 }
2212
2213 fn has(&self) -> TokenStream2 {
2216 let slot = slot(&self.ident);
2217 if self.keywords {
2218 return quote!((!#slot.is_empty()));
2219 }
2220 match &self.kind {
2221 Kind::Switch if self.negate.is_some() => {
2222 quote!((#slot == ::core::option::Option::Some(true)))
2223 }
2224 Kind::Switch => quote!(#slot),
2225 Kind::Count(_) => quote!((#slot != 0)),
2226 Kind::Optional(_) | Kind::Required(_) => quote!(#slot.is_some()),
2227 Kind::Many(_) => quote!((!#slot.is_empty())),
2228 }
2229 }
2230
2231 fn assign(&self, value: TokenStream2, source: &str) -> TokenStream2 {
2234 let slot = slot(&self.ident);
2235 match &self.kind {
2236 Kind::Optional(ty) | Kind::Required(ty) => {
2237 let value = self.source_value(ty, value, source);
2238 quote!(#slot = ::core::option::Option::Some(#value);)
2239 }
2240 Kind::Many(ty) => match self.delimiter {
2241 None => {
2242 let value = self.source_value(ty, value, source);
2243 quote!(#slot.push(#value);)
2244 }
2245 Some(d) => {
2246 let piece = self.source_value(ty, quote!(__piece), source);
2247 quote!(for __piece in __wa::split(#value, #d) { #slot.push(#piece); })
2248 }
2249 },
2250 Kind::Switch | Kind::Count(_) => unreachable!("a switch or a count takes no value"),
2251 }
2252 }
2253
2254 fn unset(&self, displaced: bool) -> TokenStream2 {
2256 let slot = slot(&self.ident);
2257 if self.negate.is_some() {
2258 return quote!(#slot.is_none());
2259 }
2260 let has = self.has();
2261 let not_displaced = displaced.then(|| {
2262 let displaced = displaced_flag(&self.ident);
2263 quote!(&& !#displaced)
2264 });
2265 quote!(!#has #not_displaced)
2266 }
2267
2268 fn fallback(&self, env: bool, displaced: bool) -> TokenStream2 {
2271 let slot = slot(&self.ident);
2272 if self.negate.is_some() {
2273 return self.negatable_fallback(env);
2274 }
2275 let unset = self.unset(displaced);
2276 let assign = |value: TokenStream2, source: &str| self.assign(value, source);
2277 if !env {
2278 if let Some(function) = &self.default_fn {
2279 let set = match &self.kind {
2280 Kind::Many(_) => quote!(#slot = #function();),
2281 _ => quote!(#slot = ::core::option::Option::Some(#function());),
2282 };
2283 return quote!(if #unset { #set });
2284 }
2285 return self
2286 .default
2287 .as_ref()
2288 .map(|value| {
2289 let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
2290 let assign = assign(quote!(__wa::BStr::new(#bytes)), &self.display());
2291 quote!(if #unset { #assign })
2292 })
2293 .unwrap_or_default();
2294 }
2295 self.env
2296 .as_ref()
2297 .map(|var| {
2298 let name = LitStr::new(var, Span::call_site());
2299 let apply = match &self.kind {
2300 Kind::Switch => quote!(#slot = __wa::env::truthy(&__raw);),
2301 Kind::Count(ty) => quote! {
2302 if let ::core::option::Option::Some(::core::result::Result::Ok(__n)) =
2303 __raw.to_str().map(str::parse::<#ty>)
2304 {
2305 #slot = __n;
2306 }
2307 },
2308 _ => {
2309 let assign = assign(quote!(__value), &format!("${var}"));
2310 quote! {
2311 let __value = __wa::BStr::new(__raw.as_encoded_bytes());
2312 #assign
2313 }
2314 }
2315 };
2316 quote! {
2317 if #unset {
2318 if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
2319 #apply
2320 }
2321 }
2322 }
2323 })
2324 .unwrap_or_default()
2325 }
2326
2327 fn negatable_fallback(&self, env: bool) -> TokenStream2 {
2329 let slot = slot(&self.ident);
2330 let value = if env {
2331 let Some(var) = &self.env else {
2332 return quote!();
2333 };
2334 let name = LitStr::new(var, Span::call_site());
2335 quote! {
2336 if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
2337 #slot = ::core::option::Option::Some(__wa::env::truthy(&__raw));
2338 }
2339 }
2340 } else {
2341 match (&self.default_fn, &self.default) {
2342 (Some(function), _) => quote!(#slot = ::core::option::Option::Some(#function());),
2343 (None, Some(default)) => {
2344 let default = default == "true";
2345 quote!(#slot = ::core::option::Option::Some(#default);)
2346 }
2347 (None, None) => return quote!(),
2348 }
2349 };
2350 quote!(if #slot.is_none() { #value })
2351 }
2352
2353 fn word_value(&self, ty: &Type, word: TokenStream2, display: &LitStr) -> TokenStream2 {
2355 let check = self.check(
2356 quote!(#word.value),
2357 |cause| quote!(__wa::Error::invalid_value(#word.offset, #display, #word.value, #cause)),
2358 );
2359 quote!({ #check #word.convert::<#ty>(#display)? })
2360 }
2361
2362 fn display(&self) -> String {
2364 match &self.role {
2365 Role::Flag { long: Some(l), .. } => format!("--{l}"),
2366 Role::Flag { short: Some(c), .. } => format!("-{c}"),
2367 Role::Flag { .. } => match self.plus {
2368 Some(c) => format!("+{c}"),
2369 None => unreachable!("every flag has a name"),
2370 },
2371 Role::Positional { name, .. } => name.clone(),
2372 Role::Subcommand => "<COMMAND>".to_owned(),
2373 }
2374 }
2375
2376 fn short_pattern(&self) -> Option<TokenStream2> {
2378 let first = self.short()?;
2379 let letters = std::iter::once(first)
2380 .chain(self.short_aliases.iter().copied())
2381 .map(|c| LitChar::new(c, Span::call_site()));
2382 Some(quote!(#(#letters)|*))
2383 }
2384
2385 fn shorts(&self) -> Vec<char> {
2387 self.short()
2388 .into_iter()
2389 .chain(self.short_aliases.iter().copied())
2390 .collect()
2391 }
2392
2393 fn short(&self) -> Option<char> {
2394 match self.role {
2395 Role::Flag { short, .. } => short,
2396 Role::Positional { .. } | Role::Subcommand => None,
2397 }
2398 }
2399
2400 fn longs(&self) -> Vec<&str> {
2402 match &self.role {
2403 Role::Flag { long, aliases, .. } => {
2404 long.iter().chain(aliases).map(String::as_str).collect()
2405 }
2406 Role::Positional { .. } | Role::Subcommand => Vec::new(),
2407 }
2408 }
2409
2410 fn long_pattern(&self) -> Option<TokenStream2> {
2412 let longs = self.longs();
2413 (!longs.is_empty()).then(|| byte_patterns(&longs))
2414 }
2415
2416 fn takes_value(&self) -> bool {
2418 !self.tristate && !matches!(self.kind, Kind::Switch | Kind::Count(_))
2419 }
2420
2421 fn is_positional(&self) -> bool {
2422 matches!(self.role, Role::Positional { .. })
2423 }
2424
2425 fn is_global(&self) -> bool {
2426 matches!(self.role, Role::Flag { global: true, .. })
2427 }
2428
2429 fn double_dash(&self) -> DoubleDash {
2431 match self.role {
2432 Role::Positional { double_dash, .. } => double_dash,
2433 _ => DoubleDash::Optional,
2434 }
2435 }
2436}
2437
2438fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
2439 let Data::Struct(data) = &input.data else {
2440 return Err(syn::Error::new(
2441 input.span(),
2442 "`Args` can only be derived for structs",
2443 ));
2444 };
2445 let Fields::Named(named) = &data.fields else {
2446 return Err(syn::Error::new(
2447 data.fields.span(),
2448 "`Args` needs a struct with named fields",
2449 ));
2450 };
2451 for f in &named.named {
2452 role_alone(f)?;
2453 }
2454 let fields = named
2455 .named
2456 .iter()
2457 .filter(|f| !is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
2458 .map(field)
2459 .collect::<syn::Result<Vec<_>>>()?;
2460 let skipped: Vec<&Ident> = named
2461 .named
2462 .iter()
2463 .filter(|f| is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
2464 .filter_map(|f| f.ident.as_ref())
2465 .collect();
2466 let sequences: Vec<&syn::Field> = named.named.iter().filter(|f| is_sequence(f)).collect();
2469 if let Some(extra) = sequences.get(1) {
2470 return Err(syn::Error::new(
2471 extra.span(),
2472 "a struct has at most one `sequence` field",
2473 ));
2474 }
2475 let sequence_unknown = sequences.first().is_some_and(|f| has_flag(f, "unknown"));
2478 let sequence = vec_field(
2479 sequences.first().copied(),
2480 "a `sequence` field is a `Vec<T>` of an `Occurrence` enum",
2481 )?;
2482 let sequence_ty = sequence.as_ref().map(|(_, ty)| ty);
2483 let unknowns: Vec<&syn::Field> = named.named.iter().filter(|f| is_unknown(f)).collect();
2485 if let Some(extra) = unknowns.get(1) {
2486 return Err(syn::Error::new(
2487 extra.span(),
2488 "a struct has at most one `unknown` field",
2489 ));
2490 }
2491 let unknown = vec_field(
2492 unknowns.first().copied(),
2493 "an `unknown` field is a `Vec<T>`",
2494 )?;
2495 let flattens: Vec<(Ident, Type)> = named
2497 .named
2498 .iter()
2499 .filter(|f| is_flatten(f))
2500 .map(|f| (f.ident.clone().expect("named field"), f.ty.clone()))
2501 .collect();
2502 if !flattens.is_empty() && !input.generics.params.is_empty() {
2503 return Err(syn::Error::new(
2504 input.generics.span(),
2505 "`flatten` in a generic struct is not supported yet",
2506 ));
2507 }
2508 check_duplicates(&fields)?;
2509 let positionals: Vec<&Field> = fields
2512 .iter()
2513 .filter(|f| f.is_positional() && f.double_dash() != DoubleDash::Required)
2514 .collect();
2515 check_positional_order(&positionals)?;
2516 let trailing: Vec<&Field> = fields
2517 .iter()
2518 .filter(|f| f.double_dash() == DoubleDash::Required)
2519 .collect();
2520 if let Some(extra) = trailing.get(1) {
2521 return Err(syn::Error::new(
2522 extra.ident.span(),
2523 "a struct has at most one `double_dash = \"required\"` positional",
2524 ));
2525 }
2526 let trailing = trailing.first().copied();
2527 let preserving: Vec<usize> = positionals
2529 .iter()
2530 .enumerate()
2531 .filter(|(_, f)| f.double_dash() == DoubleDash::Preserve)
2532 .map(|(i, _)| i)
2533 .collect();
2534 if let (Some(t), false) = (trailing, preserving.is_empty()) {
2535 return Err(syn::Error::new(
2536 t.ident.span(),
2537 "a `double_dash = \"preserve\"` positional keeps the `--` this one waits for",
2538 ));
2539 }
2540 let StructOptions {
2541 groups,
2542 restart_token,
2543 default_subcommand,
2544 default_subcommand_flags,
2545 arg_required_else_help,
2546 name: program_name,
2547 version,
2548 about,
2549 long_about,
2550 after_help,
2551 after_long_help,
2552 disable_help_flag,
2553 disable_help_short,
2554 disable_version_flag,
2555 disable_help_subcommand,
2556 unknown_flags_value,
2557 long_only,
2558 plus_options,
2559 } = struct_options(input)?;
2560 let (doc_about, doc_long_about) = docs(&input.attrs);
2561 let about = about.unwrap_or(doc_about);
2562 let long_about = long_about.unwrap_or(doc_long_about);
2563 let (about_text, long_about_text) = (about, long_about);
2564 let (after_text, after_long_text) = (after_help, after_long_help);
2565 let (about, long_about) = (text(&about_text), text(&long_about_text));
2566 let (after_help, after_long_help) = (text(&after_text), text(&after_long_text));
2567 let rules = Rules::new(&fields, &groups)?;
2568
2569 let name = &input.ident;
2570 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2571
2572 let slot_parts: Vec<(Ident, TokenStream2, TokenStream2)> = fields
2574 .iter()
2575 .map(|f| {
2576 let none = quote!(::core::option::Option::None);
2577 let new = quote!(::std::vec::Vec::new());
2578 let (ty, init) = match &f.kind {
2579 _ if f.keywords => (quote!(::std::vec::Vec<&__wa::BStr>), new),
2581 Kind::Switch if f.negate.is_some() => (quote!(::core::option::Option<bool>), none),
2582 Kind::Switch => (quote!(bool), quote!(false)),
2583 Kind::Count(ty) => (quote!(#ty), quote!(0)),
2584 Kind::Optional(ty) | Kind::Required(ty) => {
2585 (quote!(::core::option::Option<#ty>), none)
2586 }
2587 Kind::Many(ty) => (quote!(::std::vec::Vec<#ty>), new),
2588 };
2589 (slot(&f.ident), ty, init)
2590 })
2591 .collect();
2592 let slots = slot_parts
2593 .iter()
2594 .map(|(ident, ty, init)| quote!(let mut #ident: #ty = #init;));
2595
2596 let store = |f: &Field| {
2598 let ident = slot(&f.ident);
2599 let displace = rules.displace(&fields, f);
2600 let stored = match &f.kind {
2601 _ if f.keywords => {
2602 let read = f.read();
2603 quote! {
2604 let __value = #read;
2605 #ident.push(__value);
2606 }
2607 }
2608 _ if f.tristate || f.negate.is_some() => quote! {
2609 __arg.check_switch()?;
2610 #ident = ::core::option::Option::Some(true);
2611 },
2612 Kind::Switch => quote! {
2613 __arg.check_switch()?;
2614 #ident = true;
2615 },
2616 Kind::Count(_) => quote! {
2617 __arg.check_switch()?;
2618 #ident = #ident.saturating_add(1);
2619 },
2620 Kind::Required(ty) => {
2621 let value = f.flag_value(ty, quote!(__value));
2622 let read = f.read();
2623 quote! {
2624 let __value = #read;
2625 #ident = ::core::option::Option::Some(#value);
2626 }
2627 }
2628 Kind::Optional(ty) => {
2629 let value = f.flag_value(ty, quote!(__value));
2630 match &f.default_missing {
2631 Some(_) => {
2635 let read = f.read();
2636 quote! {
2637 let __value = #read;
2638 #ident = ::core::option::Option::Some(#value);
2639 }
2640 }
2641 None if f.require_equals => {
2642 let options = f.value_options();
2643 quote! {
2644 if let ::core::option::Option::Some(__value) =
2645 __arg.read_value_opt_with(__input, #options)
2646 {
2647 #ident = ::core::option::Option::Some(#value);
2648 }
2649 }
2650 }
2651 None => {
2652 let read = f.read();
2653 quote! {
2654 let __value = #read;
2655 #ident = ::core::option::Option::Some(#value);
2656 }
2657 }
2658 }
2659 }
2660 Kind::Many(ty) => match f.delimiter {
2661 None => {
2662 let value = f.flag_value(ty, quote!(__value));
2663 let read = f.read();
2664 let more = match f.values_max {
2665 None => (f.values > 1).then(|| {
2667 let more = f.values - 1;
2668 quote! {
2669 for _ in 0..#more {
2670 let __value = __arg.read_next(__input)?;
2671 #ident.push(#value);
2672 }
2673 }
2674 }),
2675 Some(max) => {
2677 let (min, options) = (f.values, f.value_options());
2678 let terminator = match &f.value_terminator {
2679 Some(word) => {
2680 let word = LitByteStr::new(word.as_bytes(), Span::call_site());
2681 quote!(::core::option::Option::Some(__wa::BStr::new(#word)))
2682 }
2683 None => quote!(::core::option::Option::None),
2684 };
2685 Some(quote! {
2686 let mut __taken = 1usize;
2687 while __taken < #max {
2688 let ::core::option::Option::Some(__value) =
2689 __arg.read_more(__input, #options, #terminator)
2690 else {
2691 break;
2692 };
2693 #ident.push(#value);
2694 __taken += 1;
2695 }
2696 __arg.check_values(__taken, #min)?;
2697 })
2698 }
2699 };
2700 quote! {
2701 let __value = #read;
2702 #ident.push(#value);
2703 #more
2704 }
2705 }
2706 Some(d) => {
2707 let value = f.flag_value(ty, quote!(__piece));
2708 let read = f.read();
2709 quote! {
2710 for __piece in __wa::split(#read, #d) {
2711 #ident.push(#value);
2712 }
2713 }
2714 }
2715 },
2716 };
2717 quote!(#stored #displace)
2718 };
2719
2720 let negated = |f: &Field| {
2721 let no = LitByteStr::new(f.negate.as_ref()?.as_bytes(), Span::call_site());
2722 let ident = slot(&f.ident);
2723 let displace = rules.displace(&fields, f);
2724 Some((
2725 quote!(#no),
2726 quote! {
2727 __arg.check_switch()?;
2728 #ident = ::core::option::Option::Some(false);
2729 #displace
2730 },
2731 ))
2732 };
2733 let plus = |f: &Field| {
2735 let letter = LitChar::new(f.plus?, Span::call_site());
2736 let body = if f.tristate {
2737 let ident = slot(&f.ident);
2738 let displace = rules.displace(&fields, f);
2739 quote!(#ident = ::core::option::Option::Some(false); #displace)
2740 } else {
2741 store(f)
2742 };
2743 Some((quote!(#letter), body))
2744 };
2745 type Part<'a> = &'a dyn Fn(&Field) -> Option<(TokenStream2, TokenStream2)>;
2748 let arms = |keep: &dyn Fn(&Field) -> bool, tail: TokenStream2| {
2749 let of = |part: Part<'_>| -> Vec<TokenStream2> {
2750 fields
2751 .iter()
2752 .filter(|f| keep(f))
2753 .filter_map(part)
2754 .map(|(pattern, body)| quote!(#pattern => { #body #tail }))
2755 .collect()
2756 };
2757 let mut long = of(&|f| Some((f.long_pattern()?, store(f))));
2758 long.extend(of(&negated));
2759 let short = of(&|f| Some((f.short_pattern()?, store(f))));
2760 (long, short, of(&plus))
2761 };
2762 let (long_arms, short_arms, plus_arms) = arms(&|_| true, quote!());
2763
2764 let unexpected = quote!(return ::core::result::Result::Err(__arg.unexpected()));
2765 let subcommands: Vec<&Field> = fields
2766 .iter()
2767 .filter(|f| matches!(f.role, Role::Subcommand))
2768 .collect();
2769 if let Some(extra) = subcommands.get(1) {
2770 return Err(syn::Error::new(
2771 extra.ident.span(),
2772 "a struct has at most one subcommand field",
2773 ));
2774 }
2775 let subcommand = subcommands.first();
2776 let track_filled = subcommand.is_some() && !positionals.is_empty();
2778 let filled = track_filled.then(|| quote!(__filled = true;));
2779
2780 let overflow = match trailing {
2783 Some(t) => {
2784 let display = LitStr::new(&t.display(), Span::call_site());
2785 quote! {
2786 return ::core::result::Result::Err(
2787 __wa::Error::requires_double_dash(__word.offset, #display),
2788 )
2789 }
2790 }
2791 None => unexpected.clone(),
2792 };
2793 let positional_match = if positionals.is_empty() {
2794 quote!({ #overflow; })
2795 } else {
2796 let arms = positionals.iter().enumerate().map(|(i, f)| {
2797 let ident = slot(&f.ident);
2798 let display = LitStr::new(&f.display(), Span::call_site());
2799 let stop = (f.double_dash() == DoubleDash::Automatic || f.stop_flags)
2800 .then(|| quote!(__input.stop_flags();));
2801 match &f.kind {
2802 Kind::Optional(ty) | Kind::Required(ty) => {
2803 let value = f.word_value(ty, quote!(__word), &display);
2804 quote! {
2805 #i => {
2806 #ident = ::core::option::Option::Some(#value);
2807 __position += 1;
2808 #filled
2809 #stop
2810 }
2811 }
2812 }
2813 Kind::Many(ty) => {
2814 let push = match f.delimiter {
2815 None => {
2816 let value = f.word_value(ty, quote!(__word), &display);
2817 quote! {
2820 if #ident.capacity() == 0 {
2821 #ident.reserve_exact(__input.words_left() + 1);
2822 }
2823 #ident.push(#value);
2824 }
2825 }
2826 Some(d) => {
2827 let value = f.word_value(ty, quote!(__piece), &display);
2828 quote! {
2829 for __piece in __word.split(#d) {
2830 #ident.push(#value);
2831 }
2832 }
2833 }
2834 };
2835 quote! {
2836 #i => {
2837 #push
2838 #filled
2839 #stop
2840 }
2841 }
2842 }
2843 Kind::Switch | Kind::Count(_) => {
2844 unreachable!("rejected for positionals in `field`")
2845 }
2846 }
2847 });
2848 quote! {
2849 match __position {
2850 #(#arms)*
2851 _ => { #overflow; }
2852 }
2853 }
2854 };
2855 let positional_match = match trailing {
2856 None => positional_match,
2857 Some(t) => {
2858 let ident = slot(&t.ident);
2859 let display = LitStr::new(&t.display(), Span::call_site());
2860 let store = match &t.kind {
2861 Kind::Many(ty) => match t.delimiter {
2862 None => {
2863 let value = t.word_value(ty, quote!(__word), &display);
2864 quote!(#ident.push(#value);)
2865 }
2866 Some(d) => {
2867 let value = t.word_value(ty, quote!(__piece), &display);
2868 quote!(for __piece in __word.split(#d) { #ident.push(#value); })
2869 }
2870 },
2871 Kind::Optional(ty) | Kind::Required(ty) => {
2872 let value = t.word_value(ty, quote!(__word), &display);
2873 quote! {
2874 if #ident.is_some() { #unexpected; }
2875 #ident = ::core::option::Option::Some(#value);
2876 }
2877 }
2878 Kind::Switch | Kind::Count(_) => {
2879 unreachable!("rejected for positionals in `field`")
2880 }
2881 };
2882 quote! {
2883 if __word.after_separator {
2884 #store
2885 } else {
2886 #positional_match
2887 }
2888 }
2889 }
2890 };
2891 let routes = subcommand.map(|f| {
2892 let ident = slot(&f.ident);
2893 let ty = subcommand_type(f);
2894 let not_filled = track_filled.then(|| quote!(!__filled &&));
2895 let (global_longs, global_shorts, global_plus) = arms(
2896 &Field::is_global,
2897 quote!(return ::core::result::Result::Ok(true);),
2898 );
2899 let flat_globals = flattens.iter().map(|(ident, ty)| {
2902 let slot = format_ident!("__flat_{}", ident);
2903 quote! {
2904 if <#ty as __wa::Flatten>::bind_global(&mut #slot, __arg, __input)? {
2905 return ::core::result::Result::Ok(true);
2906 }
2907 }
2908 });
2909 let flat_shorts = flattens
2910 .iter()
2911 .map(|(_, ty)| quote!(.or_else(|| <#ty as __wa::Flatten>::global_short(__c))));
2912 let (inherit, handler) = if fields.iter().any(Field::is_global) || !flattens.is_empty() {
2913 let shorts = fields.iter().filter(|f| f.is_global()).flat_map(|f| {
2914 let takes_value = f.takes_value();
2915 f.shorts().into_iter().map(move |c| {
2916 let c = LitChar::new(c, Span::call_site());
2917 quote!((#c, #takes_value))
2918 })
2919 });
2920 let setup = quote! {
2921 let mut __inherit = __wa::inherit(
2922 &[#(#shorts),*],
2923 |__c| ::core::option::Option::<bool>::None #(#flat_shorts)*,
2924 __globals,
2925 |__arg, __input, __parent| {
2926 match __arg {
2927 __wa::Arg::Long(__flag) => match __flag.name {
2928 #(#global_longs)*
2929 _ => {}
2930 },
2931 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
2932 #(#global_plus)*
2933 _ => {}
2934 },
2935 __wa::Arg::Short(__flag) => match __flag.letter {
2936 #(#global_shorts)*
2937 _ => {}
2938 },
2939 _ => {}
2940 }
2941 #(#flat_globals)*
2942 __parent.bind(__arg, __input)
2943 });
2944 };
2945 (setup, quote!(&mut __inherit))
2946 } else {
2947 (quote!(), quote!(&mut *__globals))
2948 };
2949 let help_word = (!disable_help_subcommand).then(|| {
2951 quote! {
2952 if &**__word.value == b"help" {
2953 return ::core::result::Result::Err(
2954 __wa::help_word(<Self as ::winnow_args::Args>::HELP, __input),
2955 );
2956 }
2957 }
2958 });
2959 let fallback = default_subcommand.as_ref().map(|name| {
2961 let name = LitByteStr::new(name.as_bytes(), Span::call_site());
2962 quote! {
2963 let __sub = {
2964 #inherit
2965 *__input = __start;
2966 <#ty as __wa::Subcommand>::parse_subcommand(#name, __input, #handler)?
2967 };
2968 #ident = ::core::option::Option::Some(__sub);
2969 break;
2970 }
2971 });
2972 let flag_fallback = fallback.as_ref().filter(|_| default_subcommand_flags).map(
2974 |fallback| match track_filled {
2975 true => quote!(if __filled { #unexpected; } #fallback),
2976 false => fallback.clone(),
2977 },
2978 );
2979 let route = quote! {
2980 if #not_filled !__word.after_separator {
2981 if <#ty as __wa::Subcommand>::has(&**__word.value) {
2982 let __sub = {
2983 #inherit
2984 <#ty as __wa::Subcommand>::parse_subcommand(
2985 &**__word.value, __input, #handler,
2986 )?
2987 };
2988 #ident = ::core::option::Option::Some(__sub);
2989 break;
2990 }
2991 #help_word
2992 #fallback
2993 }
2994 };
2995 (route, flag_fallback)
2996 });
2997 let (route, flag_fallback) = match routes {
2998 Some((route, flag_fallback)) => (Some(route), flag_fallback),
2999 None => (None, None),
3000 };
3001 let restart = restart_token.map(|token| {
3003 let token = LitByteStr::new(token.as_bytes(), Span::call_site());
3004 let clears = fields.iter().filter(|f| f.is_positional()).map(|f| {
3005 let slot = slot(&f.ident);
3006 match f.kind {
3007 Kind::Many(_) => quote!(#slot.clear();),
3008 _ => quote!(#slot = ::core::option::Option::None;),
3009 }
3010 });
3011 let position = (!positionals.is_empty()).then(|| quote!(__position = 0;));
3012 let filled = track_filled.then(|| quote!(__filled = false;));
3013 quote! {
3014 if &**__word.value == #token {
3015 #(#clears)*
3016 #position
3017 #filled
3018 __input.resume_flags();
3019 continue;
3020 }
3021 }
3022 });
3023 let sequence_word = sequence_ty.map(|ty| {
3024 quote! {
3025 if let ::core::option::Option::Some(__item) =
3026 <#ty as ::winnow_args::Occurrence>::from_word(&__word)?
3027 {
3028 __sequence.push(__item);
3029 continue;
3030 }
3031 }
3032 });
3033 let word_arm = quote! {
3034 __wa::Arg::Word(__word) => {
3035 #restart
3036 #route
3037 #sequence_word
3038 #positional_match
3039 }
3040 };
3041 let separator_arm = if preserving.is_empty() {
3042 quote!(__wa::Arg::Separator { .. } => {})
3043 } else {
3044 quote! {
3045 __wa::Arg::Separator { offset: __offset } => {
3046 if matches!(__position, #(#preserving)|*) {
3047 __input.resume_flags();
3048 let __word = __wa::Word {
3049 value: __wa::BStr::new(b"--"),
3050 offset: __offset,
3051 after_separator: false,
3052 };
3053 #positional_match
3054 }
3055 }
3056 }
3057 };
3058 let position = (!positionals.is_empty()).then(|| quote!(let mut __position: usize = 0;));
3059 let filled_slot = track_filled.then(|| quote!(let mut __filled = false;));
3060
3061 let opted: Vec<usize> = positionals
3064 .iter()
3065 .enumerate()
3066 .filter(|(_, f)| f.negative_numbers)
3067 .map(|(i, _)| i)
3068 .collect();
3069 let (unknown_slot, unknown_build) = match &unknown {
3070 Some((ident, ty)) => (
3071 quote!(let mut __unknown: ::std::vec::Vec<#ty> = ::std::vec::Vec::new();),
3072 quote!(#ident: __unknown,),
3073 ),
3074 None => (quote!(), quote!()),
3075 };
3076 let (sequence_slot, sequence_arg, sequence_build) = match &sequence {
3077 Some((ident, ty)) => (
3078 quote! {
3080 let mut __sequence: ::std::vec::Vec<#ty> =
3081 ::std::vec::Vec::with_capacity(__input.words_left());
3082 },
3083 quote! {
3084 if let ::core::option::Option::Some(__item) =
3085 <#ty as ::winnow_args::Occurrence>::from_arg(&__arg, __input)?
3086 {
3087 __sequence.push(__item);
3088 } else
3089 },
3090 quote!(#ident: __sequence,),
3091 ),
3092 None => (quote!(), quote!(), quote!()),
3093 };
3094 let bundle_start = sequence_ty.map(|_| {
3098 quote! {
3099 let __bundle_word = __input.front();
3100 let __bundle_start = __input.mode() == ::winnow_args::stream::Mode::Word;
3101 let __bundle_at = __sequence.len();
3102 }
3103 });
3104 let bundle_end = sequence_ty.map(|ty| {
3105 quote! {
3106 if <#ty as ::winnow_args::Occurrence>::BUNDLES
3107 && __bundle_start
3108 && matches!(__arg, __wa::Arg::Short(__flag) if !__flag.plus)
3109 && __input.mode() == ::winnow_args::stream::Mode::Bundle
3110 {
3111 let __word = __wa::Word {
3112 value: __wa::BStr::new(__bundle_word),
3113 offset: __arg.offset(),
3114 after_separator: false,
3115 };
3116 if let ::core::option::Option::Some(__bundle) =
3117 <#ty as ::winnow_args::Occurrence>::from_bundle(&__word)?
3118 {
3119 __sequence.insert(__bundle_at, __bundle);
3120 }
3121 }
3122 }
3123 });
3124 let flat_slot = |ident: &Ident| format_ident!("__flat_{}", ident);
3127 let flatten_slots = flattens.iter().map(|(ident, ty)| {
3128 let slot = flat_slot(ident);
3129 quote! {
3130 let mut #slot = <<#ty as __wa::Flatten>::Slots as ::core::default::Default>::default();
3131 }
3132 });
3133 let flatten_arg: TokenStream2 = flattens
3134 .iter()
3135 .map(|(ident, ty)| {
3136 let slot = flat_slot(ident);
3137 quote!(if <#ty as __wa::Flatten>::bind(&mut #slot, &__arg, __input)? {} else)
3138 })
3139 .collect();
3140 let flatten_build = flattens.iter().map(|(ident, ty)| {
3141 let slot = flat_slot(ident);
3142 quote!(#ident: <#ty as __wa::Flatten>::finish(#slot, __input)?,)
3143 });
3144 let flatten_shorts: Vec<TokenStream2> = flattens
3145 .iter()
3146 .map(|(_, ty)| {
3147 quote! {
3148 __c if <#ty as __wa::Flatten>::short(__c).is_some() => {
3149 <#ty as __wa::Flatten>::short(__c)
3150 }
3151 }
3152 })
3153 .collect();
3154 let flatten_longs: Vec<TokenStream2> = flattens
3155 .iter()
3156 .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_long(__name)))
3157 .collect();
3158 let own_shorts: Vec<TokenStream2> = fields
3160 .iter()
3161 .filter_map(|f| {
3162 let letters = f.short_pattern()?;
3163 let takes_value = f.takes_value();
3164 Some(quote!(#letters => ::core::option::Option::Some(#takes_value),))
3165 })
3166 .collect();
3167 let own_longs: Vec<String> = fields
3170 .iter()
3171 .filter(|f| !f.two_dashes)
3172 .flat_map(|f| spellings(f).into_iter().map(str::to_owned))
3173 .collect();
3174 let prefixes: Vec<u8> = fields
3175 .iter()
3176 .filter(|f| f.prefix)
3177 .filter_map(|f| f.short().map(|c| c as u8))
3178 .collect();
3179 let flatten_prefixes = flattens.iter().map(|(_, ty)| {
3180 quote!(&& !__name.first().is_some_and(|__c| <#ty as __wa::Flatten>::is_prefix(*__c)))
3181 });
3182 let lexer = if plus_options {
3183 quote!(__wa::arg_plus)
3184 } else {
3185 quote!(__wa::arg)
3186 };
3187 if !plus_options && let Some(f) = fields.iter().find(|f| f.plus.is_some()) {
3188 return Err(syn::Error::new(
3189 f.ident.span(),
3190 "`plus` needs `#[arg(plus_options)]` on the struct",
3191 ));
3192 }
3193 let plus_unknown = match &flag_fallback {
3195 Some(fallback) => quote!(#fallback),
3196 None => quote!(#unexpected;),
3197 };
3198 let plus_match = plus_options.then(|| {
3199 quote! {
3200 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3201 #(#plus_arms)*
3202 _ => #flatten_arg if !__globals.bind(&__arg, __input)? { #plus_unknown }
3203 },
3204 }
3205 });
3206 let lex = if opted.is_empty() {
3207 quote!(#lexer(__input)?)
3208 } else {
3209 let digits: Vec<LitByteStr> = fields
3210 .iter()
3211 .filter_map(|f| f.short().filter(char::is_ascii_digit))
3212 .map(|c| LitByteStr::new(format!("-{c}").as_bytes(), Span::call_site()))
3213 .collect();
3214 let not_a_short =
3215 (!digits.is_empty()).then(|| quote!(&&!matches!(__input.front(), #(#digits)|*)));
3216 quote! {
3217 match matches!(__position, #(#opted)|*) #not_a_short {
3218 true => match __wa::number(__input) {
3219 ::core::result::Result::Ok(__word) => __wa::Arg::Word(__word),
3220 ::core::result::Result::Err(_) => #lexer(__input)?,
3221 },
3222 false => #lexer(__input)?,
3223 }
3224 }
3225 };
3226 let start = default_subcommand
3227 .is_some()
3228 .then(|| quote!(let __start = *__input;));
3229 let help_on_empty = arg_required_else_help.then(|| {
3230 quote! {
3231 if __input.is_empty() {
3232 return ::core::result::Result::Err(
3233 __wa::Error::bare_help(<Self as ::winnow_args::Args>::HELP),
3234 );
3235 }
3236 }
3237 });
3238 if default_subcommand.is_some() && subcommand.is_none() {
3239 return Err(syn::Error::new(
3240 input.ident.span(),
3241 "`default_subcommand` needs an `#[arg(subcommand)]` field",
3242 ));
3243 }
3244 if default_subcommand_flags && default_subcommand.is_none() {
3245 return Err(syn::Error::new(
3246 input.ident.span(),
3247 "`default_subcommand_flags` needs `default_subcommand`",
3248 ));
3249 }
3250 if default_subcommand_flags && (unknown_flags_value || unknown.is_some() || sequence_unknown) {
3251 return Err(syn::Error::new(
3252 input.ident.span(),
3253 "`default_subcommand_flags` hands an unknown flag to the default subcommand: \
3254 this struct cannot keep unknown flags too",
3255 ));
3256 }
3257 let declares_long = |name: &str| fields.iter().any(|f| f.longs().contains(&name));
3260 let declares_short = |c: char| fields.iter().any(|f| f.shorts().contains(&c));
3261 let help = quote!(<Self as ::winnow_args::Args>::HELP);
3262 let help_long = (!disable_help_flag && !declares_long("help")).then(
3263 || quote!(b"help" => return ::core::result::Result::Err(__wa::Error::help(#help, true)),),
3264 );
3265 let help_short = (!disable_help_flag && !disable_help_short && !declares_short('h')).then(
3266 || quote!('h' => return ::core::result::Result::Err(__wa::Error::help(#help, false)),),
3267 );
3268 let with_version = version.is_some() && !disable_version_flag;
3269 let version_long = (with_version && !declares_long("version")).then(
3270 || quote!(b"version" => return ::core::result::Result::Err(__wa::Error::version(#help)),),
3271 );
3272 let version_short = (with_version && !declares_short('V'))
3273 .then(|| quote!('V' => return ::core::result::Result::Err(__wa::Error::version(#help)),));
3274 let help_flag = help_long.is_some();
3276 let help_short_flag = help_short.is_some();
3277 let version_short_flag = version_short.is_some();
3278 let late = !flattens.is_empty() || sequence.is_some();
3282 let late_if = |on: bool, test: TokenStream2, error: TokenStream2| {
3283 (late && on).then(|| quote!(if #test { return ::core::result::Result::Err(#error); } else))
3284 };
3285 let late_long: TokenStream2 = [
3286 late_if(
3287 help_long.is_some(),
3288 quote!(__flag.name == b"help"),
3289 quote!(__wa::Error::help(#help, true)),
3290 ),
3291 late_if(
3292 version_long.is_some(),
3293 quote!(__flag.name == b"version"),
3294 quote!(__wa::Error::version(#help)),
3295 ),
3296 ]
3297 .into_iter()
3298 .flatten()
3299 .collect();
3300 let late_short: TokenStream2 = [
3301 late_if(
3302 help_short.is_some(),
3303 quote!(__flag.letter == 'h'),
3304 quote!(__wa::Error::help(#help, false)),
3305 ),
3306 late_if(
3307 version_short.is_some(),
3308 quote!(__flag.letter == 'V'),
3309 quote!(__wa::Error::version(#help)),
3310 ),
3311 ]
3312 .into_iter()
3313 .flatten()
3314 .collect();
3315 let keep = |arm: Option<TokenStream2>| arm.filter(|_| !late);
3316 let (help_long, help_short) = (keep(help_long), keep(help_short));
3317 let (version_long, version_short) = (keep(version_long), keep(version_short));
3318
3319 let (lenient_bundle, save_token, unknown_flag) = if unknown_flags_value
3329 || unknown.is_some()
3330 || sequence_unknown
3331 {
3332 let next = (!positionals.is_empty() || trailing.is_some()).then(|| quote!(continue;));
3334 let (sink, sink_next) = match (&unknown, sequence_ty.filter(|_| sequence_unknown)) {
3335 (_, Some(ty)) => (
3336 quote! {
3337 match <#ty as ::winnow_args::Occurrence>::from_unknown(&__word)? {
3338 ::core::option::Option::Some(__item) => __sequence.push(__item),
3339 ::core::option::Option::None => {
3340 return ::core::result::Result::Err(__wa::Error::unknown_flag(
3341 __word.offset,
3342 ::std::string::String::from_utf8_lossy(&__word.value),
3343 ));
3344 }
3345 }
3346 },
3347 quote!(continue;),
3348 ),
3349 (Some((_, ty)), None) => (
3350 quote!(__unknown.push(__word.convert::<#ty>("<UNKNOWN>")?);),
3351 quote!(continue;),
3352 ),
3353 (None, None) => (
3354 quote! {
3355 let __arg = __wa::Arg::Word(__word);
3356 #positional_match
3357 },
3358 quote!(#next),
3359 ),
3360 };
3361 let theirs = match sequence_ty {
3362 Some(ty) => quote! {
3363 __c => match <#ty as ::winnow_args::Occurrence>::short(__c) {
3364 ::core::option::Option::None => __globals.short(__c),
3365 __known => __known,
3366 },
3367 },
3368 None => quote!(_ => __globals.short(__c),),
3369 };
3370 let builtin = help_short_flag
3372 .then(|| quote!('h' => ::core::option::Option::Some(false),))
3373 .into_iter()
3374 .chain(version_short_flag.then(|| quote!('V' => ::core::option::Option::Some(false),)));
3375 (
3376 quote! {
3377 if let ::core::option::Option::Some(__word) = __wa::unknown_bundle(__input, |__c| match __c {
3378 #(#own_shorts)*
3379 #(#builtin)*
3380 #(#flatten_shorts)*
3381 #theirs
3382 }) {
3383 #sink
3384 #sink_next
3385 }
3386 },
3387 quote!(let __token = __input.front();),
3388 quote! {
3389 let __word = __wa::Word {
3390 value: __wa::BStr::new(__token),
3391 offset: __flag.offset,
3392 after_separator: false,
3393 };
3394 #sink
3395 },
3396 )
3397 } else {
3398 let unknown_flag = match &flag_fallback {
3399 Some(fallback) => quote!(#fallback),
3400 None => quote!(#unexpected;),
3401 };
3402 (quote!(), quote!(), unknown_flag)
3403 };
3404 let mut names = Vec::new();
3405 if long_only {
3406 names.clone_from(&own_longs);
3407 names.extend(help_long.is_some().then(|| "help".to_owned()));
3408 names.extend(version_long.is_some().then(|| "version".to_owned()));
3409 }
3410 let long_only_lex =
3411 long_only && (!names.is_empty() || sequence_ty.is_some() || !flattens.is_empty());
3412 let lex = if long_only_lex {
3413 let own = matches_name(&names);
3414 let (their_names, their_prefixes) = match sequence_ty {
3415 Some(ty) => (
3416 quote!(|| <#ty as ::winnow_args::Occurrence>::is_long(__name)),
3417 quote! {
3418 && !__name.first().is_some_and(|__c| {
3419 <#ty as ::winnow_args::Occurrence>::PREFIXES.contains(__c)
3420 })
3421 },
3422 ),
3423 None => (quote!(), quote!()),
3424 };
3425 let not_prefixed = (!prefixes.is_empty()).then(
3426 || quote!(!matches!(__name.first(), ::core::option::Option::Some(#(#prefixes)|*)) &&),
3427 );
3428 quote! {
3429 match __wa::long_only(__input, |__name| {
3430 #not_prefixed (#own #their_names #(#flatten_longs)*) #their_prefixes #(#flatten_prefixes)*
3431 }) {
3432 ::core::option::Option::Some(__arg) => __arg,
3433 ::core::option::Option::None => {
3434 #lenient_bundle
3435 #lex
3436 }
3437 }
3438 }
3439 } else {
3440 lex
3441 };
3442 let lenient_bundle = if long_only_lex {
3443 quote!()
3444 } else {
3445 lenient_bundle
3446 };
3447
3448 let help_version = match &version {
3449 Some(v) if with_version => quote!(::core::option::Option::Some(#v)),
3450 _ => quote!(::core::option::Option::None),
3451 };
3452 let help_items = fields
3453 .iter()
3454 .filter(|f| !matches!(f.role, Role::Subcommand))
3455 .map(|f| f.help_item());
3456 let (help_subcommands, subcommand_required) = match subcommand {
3457 Some(f) => {
3458 let ty = subcommand_type(f);
3459 (
3460 quote!(<#ty as ::winnow_args::Args>::HELP.subcommands),
3461 matches!(f.kind, Kind::Required(_)),
3462 )
3463 }
3464 None => (quote!(&[]), false),
3465 };
3466 let displaced = rules.displaced_slots(&fields);
3467 let env_fallbacks: Vec<TokenStream2> = fields
3468 .iter()
3469 .map(|f| f.fallback(true, rules.is_displaced(&fields, f)))
3470 .collect();
3471 let default_fallbacks: Vec<TokenStream2> = fields
3472 .iter()
3473 .map(|f| f.fallback(false, rules.is_displaced(&fields, f)))
3474 .collect();
3475 let exclusive = rules.exclusive(&fields, &groups);
3476 let supplied = rules.supplied(&fields);
3477 let default_ifs = rules.default_ifs(&fields);
3478 let required = rules.required(&fields, &groups);
3479 let build = fields.iter().map(|f| {
3480 let ident = &f.ident;
3481 let slot = slot(ident);
3482 match &f.kind {
3483 Kind::Required(ty) if f.keywords => {
3484 let display = LitStr::new(&f.display(), Span::call_site());
3485 quote!(#ident: __wa::keywords::<#ty>(&#slot, #display, __input.offset())?)
3486 }
3487 Kind::Switch if f.negate.is_some() => quote!(#ident: #slot.unwrap_or(false)),
3488 Kind::Switch | Kind::Count(_) | Kind::Optional(_) | Kind::Many(_) => {
3489 quote!(#ident: #slot)
3490 }
3491 Kind::Required(_) => {
3492 let display = LitStr::new(&f.display(), Span::call_site());
3493 let error = match f.role {
3494 Role::Positional { .. } => {
3495 quote!(__wa::Error::missing_argument(__input.offset(), #display))
3496 }
3497 Role::Flag { .. } => {
3498 quote!(__wa::Error::missing_required(__input.offset(), #display))
3499 }
3500 Role::Subcommand => quote!(__wa::Error::missing_subcommand(__input.offset())),
3501 };
3502 quote! {
3503 #ident: match #slot {
3504 ::core::option::Option::Some(v) => v,
3505 ::core::option::Option::None => {
3506 return ::core::result::Result::Err(#error);
3507 }
3508 }
3509 }
3510 }
3511 }
3512 });
3513 let build: Vec<TokenStream2> = build.collect();
3514 let field_reads: Vec<TokenStream2> = fields
3518 .iter()
3519 .map(|f| &f.ident)
3520 .chain(skipped.iter().copied())
3521 .chain(sequence.iter().chain(&unknown).map(|(ident, _)| ident))
3522 .chain(flattens.iter().map(|(ident, _)| ident))
3523 .map(|ident| quote!(let _ = &__built.#ident;))
3524 .collect();
3525 let flatten_build: Vec<TokenStream2> = flatten_build.collect();
3526 let flattenable = positionals.is_empty()
3529 && trailing.is_none()
3530 && subcommand.is_none()
3531 && sequence.is_none()
3532 && unknown.is_none()
3533 && !unknown_flags_value
3534 && input.generics.params.is_empty()
3535 && !fields.iter().any(|f| f.keywords);
3536 let flatten_impl = flattenable.then(|| {
3537 let vis = &input.vis;
3538 let slots_name = format_ident!("__{}WinnowArgsSlots", name);
3539 let displaced_idents: Vec<Ident> = fields
3540 .iter()
3541 .filter(|f| rules.is_displaced(&fields, f))
3542 .map(|f| displaced_flag(&f.ident))
3543 .collect();
3544 let nested: Vec<(Ident, &Type)> = flattens
3545 .iter()
3546 .map(|(ident, ty)| (flat_slot(ident), ty))
3547 .collect();
3548 let decls = slot_parts.iter().map(|(ident, ty, _)| quote!(#ident: #ty,));
3549 let inits = slot_parts
3550 .iter()
3551 .map(|(ident, _, init)| quote!(#ident: #init,));
3552 let all: Vec<&Ident> = slot_parts
3553 .iter()
3554 .map(|(ident, _, _)| ident)
3555 .chain(&displaced_idents)
3556 .chain(nested.iter().map(|(n, _)| n))
3557 .collect();
3558 let names: Vec<TokenStream2> = all.iter().map(|ident| quote!(#ident,)).collect();
3559 let mutable: Vec<TokenStream2> = all.iter().map(|ident| quote!(mut #ident,)).collect();
3560 let displaced_decls = displaced_idents.iter().map(|d| quote!(#d: bool,));
3561 let displaced_inits = displaced_idents.iter().map(|d| quote!(#d: false,));
3562 let nested_decls = nested
3563 .iter()
3564 .map(|(n, ty)| quote!(#n: <#ty as ::winnow_args::__private::Flatten>::Slots,));
3565 let nested_inits = nested
3566 .iter()
3567 .map(|(n, _)| quote!(#n: ::core::default::Default::default(),));
3568 let (bind_long, bind_short, bind_plus) = arms(&|_| true, quote!(true));
3569 let (global_long, global_short, global_plus) = arms(&Field::is_global, quote!(true));
3572 let global_nested: TokenStream2 = nested
3573 .iter()
3574 .map(|(n, ty)| {
3575 quote!(if <#ty as __wa::Flatten>::bind_global(&mut #n, &__arg, __input)? { true } else)
3576 })
3577 .collect();
3578 let global_letters = fields.iter().filter(|f| f.is_global()).filter_map(|f| {
3579 let letters = f.short_pattern()?;
3580 let takes_value = f.takes_value();
3581 Some(quote!(#letters => ::core::option::Option::Some(#takes_value),))
3582 });
3583 let nested_global_letters = nested
3584 .iter()
3585 .map(|(_, ty)| quote!(.or_else(|| <#ty as __wa::Flatten>::global_short(__c))));
3586 let bind_nested: TokenStream2 = nested
3587 .iter()
3588 .map(|(n, ty)| {
3589 quote!(if <#ty as __wa::Flatten>::bind(&mut #n, &__arg, __input)? { true } else)
3590 })
3591 .collect();
3592 let plus = if plus_options {
3594 quote! {
3595 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3596 #(#bind_plus)*
3597 _ => #bind_nested { false }
3598 },
3599 }
3600 } else {
3601 quote!(__wa::Arg::Short(__flag) if __flag.plus => #bind_nested { false },)
3602 };
3603 let own_longs = matches_name(&own_longs);
3604 let own_prefixes = if prefixes.is_empty() {
3605 quote!(false)
3606 } else {
3607 quote!(matches!(__c, #(#prefixes)|*))
3608 };
3609 let nested_prefixes = nested
3610 .iter()
3611 .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_prefix(__c)));
3612 quote! {
3613 #[doc(hidden)]
3614 #[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction)]
3615 #vis struct #slots_name {
3616 #(#decls)*
3617 #(#displaced_decls)*
3618 #(#nested_decls)*
3619 }
3620
3621 #[automatically_derived]
3622 impl ::core::default::Default for #slots_name {
3623 fn default() -> Self {
3624 Self {
3625 #(#inits)*
3626 #(#displaced_inits)*
3627 #(#nested_inits)*
3628 }
3629 }
3630 }
3631
3632 #[automatically_derived]
3633 #[allow(
3634 unused_mut,
3635 unused_variables,
3636 unreachable_code,
3637 clippy::all,
3638 clippy::pedantic,
3639 clippy::nursery,
3640 clippy::restriction
3641 )]
3642 impl ::winnow_args::__private::Flatten for #name {
3643 type Slots = #slots_name;
3644
3645 fn bind<'__i>(
3646 __slots: &mut #slots_name,
3647 __arg: &::winnow_args::Arg<'__i>,
3648 __input: &mut ::winnow_args::Argv<'__i>,
3649 ) -> ::core::result::Result<bool, ::winnow_args::Error> {
3650 use ::winnow_args::__private as __wa;
3651 let __arg = *__arg;
3652 let #slots_name { #(#mutable)* } = ::core::mem::take(__slots);
3653 let __bound = match __arg {
3654 __wa::Arg::Long(__flag) => match __flag.name {
3655 #(#bind_long)*
3656 _ => #bind_nested { false }
3657 },
3658 #plus
3659 __wa::Arg::Short(__flag) => match __flag.letter {
3660 #(#bind_short)*
3661 _ => #bind_nested { false }
3662 },
3663 _ => false,
3664 };
3665 *__slots = #slots_name { #(#names)* };
3666 ::core::result::Result::Ok(__bound)
3667 }
3668
3669 fn short(__c: char) -> ::core::option::Option<bool> {
3670 use ::winnow_args::__private as __wa;
3671 match __c {
3672 #(#own_shorts)*
3673 #(#flatten_shorts)*
3674 _ => ::core::option::Option::None,
3675 }
3676 }
3677
3678 fn bind_global<'__i>(
3679 __slots: &mut #slots_name,
3680 __arg: &::winnow_args::Arg<'__i>,
3681 __input: &mut ::winnow_args::Argv<'__i>,
3682 ) -> ::core::result::Result<bool, ::winnow_args::Error> {
3683 use ::winnow_args::__private as __wa;
3684 let __arg = *__arg;
3685 let #slots_name { #(#mutable)* } = ::core::mem::take(__slots);
3686 let __bound = match __arg {
3687 __wa::Arg::Long(__flag) => match __flag.name {
3688 #(#global_long)*
3689 _ => #global_nested { false }
3690 },
3691 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3692 #(#global_plus)*
3693 _ => #global_nested { false }
3694 },
3695 __wa::Arg::Short(__flag) => match __flag.letter {
3696 #(#global_short)*
3697 _ => #global_nested { false }
3698 },
3699 _ => false,
3700 };
3701 *__slots = #slots_name { #(#names)* };
3702 ::core::result::Result::Ok(__bound)
3703 }
3704
3705 fn global_short(__c: char) -> ::core::option::Option<bool> {
3706 use ::winnow_args::__private as __wa;
3707 let __own = match __c {
3708 #(#global_letters)*
3709 _ => ::core::option::Option::None,
3710 };
3711 __own #(#nested_global_letters)*
3712 }
3713
3714 fn is_prefix(__c: u8) -> bool {
3715 use ::winnow_args::__private as __wa;
3716 #own_prefixes #(#nested_prefixes)*
3717 }
3718
3719 fn is_long(__name: &[u8]) -> bool {
3720 use ::winnow_args::__private as __wa;
3721 #own_longs #(#flatten_longs)*
3722 }
3723
3724 fn finish(
3725 __slots: #slots_name,
3726 __input: &::winnow_args::Argv<'_>,
3727 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
3728 use ::winnow_args::__private as __wa;
3729 let #slots_name { #(#mutable)* } = __slots;
3730 #(#env_fallbacks)*
3731 #exclusive
3732 #supplied
3733 #default_ifs
3734 #(#default_fallbacks)*
3735 #required
3736 let __built = Self {
3737 #(#build,)*
3738 #(#skipped: ::core::default::Default::default(),)*
3739 #(#flatten_build)*
3740 };
3741 #(#field_reads)*
3742 ::core::result::Result::Ok(__built)
3743 }
3744 }
3745 }
3746 });
3747 let own_items = quote!(&[#(#help_items),*]);
3748 let positional_clash = sequence_ty
3750 .filter(|_| !positionals.is_empty() || trailing.is_some())
3751 .map(|ty| {
3752 quote! {
3753 const _: () = assert!(
3754 !<#ty as ::winnow_args::Occurrence>::POSITIONAL,
3755 "the sequence enum has a positional variant, which takes every word: this struct's positionals would never be filled",
3756 );
3757 }
3758 });
3759 let more: Vec<TokenStream2> = flattens
3761 .iter()
3762 .map(|(_, ty)| quote!(<#ty as ::winnow_args::Args>::HELP.items))
3763 .chain(
3764 sequence_ty
3765 .iter()
3766 .map(|ty| quote!(<#ty as ::winnow_args::Occurrence>::ITEMS)),
3767 )
3768 .collect();
3769 let items = if more.is_empty() {
3770 own_items
3771 } else {
3772 quote!({
3773 const __OWN: &[::winnow_args::help::Item] = #own_items;
3774 const _: () = assert!(
3775 !::winnow_args::help::items_clash(&[__OWN, #(#more),*]),
3776 "a flattened struct or the sequence enum declares a flag that this struct, or another of them, also declares",
3777 );
3778 #positional_clash
3779 const __N: usize = __OWN.len() #(+ #more.len())*;
3780 const __ALL: [::winnow_args::help::Item; __N] = ::winnow_args::__private::concat_items::<__N>(
3781 &[__OWN, #(#more),*],
3782 );
3783 &__ALL
3784 })
3785 };
3786 let mut spec_checks = Vec::new();
3787 let filed = filed(input)?;
3788 let identity = spec::identity(input.ident.span(), &filed);
3789 if spec::enabled() {
3790 spec_checks.extend(fields.iter().filter_map(|field| {
3791 spec_check(&input.generics, choices_type(field), field.spec.as_ref())
3792 .or_else(|| spec_check_stated(&input.generics, field))
3793 }));
3794 let stated = |ident: &Ident| {
3797 let field = named.named.iter().find(|f| f.ident.as_ref() == Some(ident));
3798 field.map_or(Ok(None), |f| spec_name(&f.attrs))
3799 };
3800 let mut link = |kind: TokenStream2, stated: Option<String>, ty: &Type| {
3801 let name = spec_type(stated, ty)?;
3802 spec_checks.extend(spec_filed(&input.generics, kind, ty, &name));
3803 syn::Result::Ok(name)
3804 };
3805 for field in fields.iter().filter(|field| field.keywords) {
3806 if let Kind::Required(ty) = &field.kind {
3807 link(quote!(Args), field.spec.clone(), ty)?;
3808 }
3809 }
3810 let items = fields
3811 .iter()
3812 .filter(|field| !matches!(field.role, Role::Subcommand))
3813 .map(spec_item)
3814 .collect::<syn::Result<Vec<_>>>()?;
3815 let flatten = flattens
3816 .iter()
3817 .map(|(ident, ty)| link(quote!(Args), stated(ident)?, ty))
3818 .collect::<syn::Result<Vec<_>>>()?;
3819 let sequence = sequence
3820 .as_ref()
3821 .map(|(ident, ty)| link(quote!(Occurrence), stated(ident)?, ty))
3822 .transpose()?;
3823 let subcommand_ty = subcommand
3824 .map(|field| {
3825 link(
3826 quote!(Subcommand),
3827 field.spec.clone(),
3828 subcommand_type(field),
3829 )
3830 })
3831 .transpose()?;
3832 spec::write_args(
3833 input.ident.span(),
3834 &spec::ArgsDoc {
3835 ident: filed.clone(),
3836 name: program_name.clone(),
3837 about: about_text,
3838 long_about: long_about_text,
3839 after_help: after_text,
3840 after_long_help: after_long_text,
3841 package_version: package_version(&version, with_version),
3842 items,
3843 flatten,
3844 sequence,
3845 subcommand: subcommand_ty,
3846 subcommand_required,
3847 help_flag,
3848 help_short: help_short_flag,
3849 long_only,
3850 unknown_flags_value,
3851 },
3852 )?;
3853 }
3854
3855 Ok(quote! {
3856 #(#spec_checks)*
3857 impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
3858 const SPEC: &'static str = #identity;
3859
3860 #[inline(never)]
3863 fn help_data() -> &'static ::winnow_args::help::Command {
3864 <Self as ::winnow_args::Args>::HELP
3865 }
3866
3867 const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
3868 name: #program_name,
3869 about: #about,
3870 long_about: #long_about,
3871 after_help: #after_help,
3872 after_long_help: #after_long_help,
3873 items: #items,
3874 subcommands: #help_subcommands,
3875 subcommand_required: #subcommand_required,
3876 help_flag: #help_flag,
3877 help_short: #help_short_flag,
3878 version: #help_version,
3879 };
3880
3881 fn parse_argv_with(
3882 __input: &mut ::winnow_args::Argv<'_>,
3883 __globals: &mut dyn ::winnow_args::Globals,
3884 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
3885 use ::winnow_args::__private as __wa;
3886 #(#slots)*
3887 #sequence_slot
3888 #unknown_slot
3889 #(#flatten_slots)*
3890 #(#displaced)*
3891 #position
3892 #filled_slot
3893 #help_on_empty
3894 while !__input.is_empty() {
3895 #start
3896 #lenient_bundle
3897 #save_token
3898 #bundle_start
3899 let __arg = #lex;
3900 match __arg {
3901 __wa::Arg::Long(__flag) => match __flag.name {
3902 #(#long_arms)*
3903 #help_long
3904 #version_long
3905 _ => #sequence_arg #flatten_arg #late_long if !__globals.bind(&__arg, __input)? { #unknown_flag },
3906 },
3907 #plus_match
3908 __wa::Arg::Short(__flag) => match __flag.letter {
3909 #(#short_arms)*
3910 #help_short
3911 #version_short
3912 _ => #sequence_arg #flatten_arg #late_short if !__globals.bind(&__arg, __input)? { #unknown_flag },
3913 },
3914 #separator_arm
3915 #word_arm
3916 }
3917 #bundle_end
3918 }
3919 #(#env_fallbacks)*
3920 #exclusive
3921 #supplied
3922 #default_ifs
3923 #(#default_fallbacks)*
3924 #required
3925 let __built = Self {
3926 #(#build,)*
3927 #(#skipped: ::core::default::Default::default(),)*
3928 #sequence_build
3929 #unknown_build
3930 #(#flatten_build)*
3931 };
3932 #(#field_reads)*
3933 ::core::result::Result::Ok(__built)
3934 }
3935 }
3936
3937 #flatten_impl
3938 })
3939}
3940
3941struct Group {
3943 name: String,
3944 required: bool,
3945 multiple: bool,
3946}
3947
3948#[derive(Default)]
3950struct StructOptions {
3951 groups: Vec<Group>,
3952 restart_token: Option<String>,
3954 default_subcommand: Option<String>,
3956 default_subcommand_flags: bool,
3958 arg_required_else_help: bool,
3960 name: String,
3962 version: Option<TokenStream2>,
3964 about: Option<String>,
3966 long_about: Option<String>,
3967 after_help: String,
3968 after_long_help: String,
3969 disable_help_flag: bool,
3970 disable_help_short: bool,
3972 disable_version_flag: bool,
3973 disable_help_subcommand: bool,
3974 unknown_flags_value: bool,
3976 long_only: bool,
3978 plus_options: bool,
3980}
3981
3982fn struct_options(input: &DeriveInput) -> syn::Result<StructOptions> {
3983 let mut options = StructOptions::default();
3984 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
3985 attr.parse_nested_meta(|meta| {
3986 if skip_spec(&meta)? {
3987 return Ok(());
3988 }
3989 if meta.path.is_ident("restart_token") {
3990 options.restart_token = Some(meta.value()?.parse::<LitStr>()?.value());
3991 return Ok(());
3992 }
3993 if meta.path.is_ident("default_subcommand") {
3994 options.default_subcommand = Some(meta.value()?.parse::<LitStr>()?.value());
3995 return Ok(());
3996 }
3997 if meta.path.is_ident("default_subcommand_flags") {
3998 options.default_subcommand_flags = true;
3999 return Ok(());
4000 }
4001 if meta.path.is_ident("arg_required_else_help") {
4002 options.arg_required_else_help = true;
4003 return Ok(());
4004 }
4005 if meta.path.is_ident("long_only") {
4006 options.long_only = true;
4007 return Ok(());
4008 }
4009 if meta.path.is_ident("plus_options") {
4010 options.plus_options = true;
4011 return Ok(());
4012 }
4013 if meta.path.is_ident("unknown_flags") {
4014 let mode = meta.value()?.parse::<LitStr>()?;
4015 options.unknown_flags_value = match mode.value().as_str() {
4016 "value" => true,
4017 "error" => false,
4018 _ => {
4019 return Err(syn::Error::new(
4020 mode.span(),
4021 "expected \"value\" or \"error\"",
4022 ));
4023 }
4024 };
4025 return Ok(());
4026 }
4027 let text = |meta: &syn::meta::ParseNestedMeta<'_>| -> syn::Result<String> {
4028 Ok(meta.value()?.parse::<LitStr>()?.value())
4029 };
4030 if meta.path.is_ident("name") {
4031 options.name = text(&meta)?;
4032 return Ok(());
4033 }
4034 if meta.path.is_ident("version") {
4035 options.version = Some(if meta.input.peek(syn::Token![=]) {
4036 let version = meta.value()?.parse::<LitStr>()?;
4037 quote!(#version)
4038 } else {
4039 quote!(::core::env!("CARGO_PKG_VERSION"))
4040 });
4041 return Ok(());
4042 }
4043 for (key, slot) in [
4044 ("about", &mut options.about),
4045 ("long_about", &mut options.long_about),
4046 ] {
4047 if meta.path.is_ident(key) {
4048 *slot = Some(text(&meta)?);
4049 return Ok(());
4050 }
4051 }
4052 for (key, slot) in [
4053 ("after_help", &mut options.after_help),
4054 ("after_long_help", &mut options.after_long_help),
4055 ] {
4056 if meta.path.is_ident(key) {
4057 *slot = text(&meta)?;
4058 return Ok(());
4059 }
4060 }
4061 for (key, slot) in [
4062 ("disable_help_flag", &mut options.disable_help_flag),
4063 ("disable_help_short", &mut options.disable_help_short),
4064 ("disable_version_flag", &mut options.disable_version_flag),
4065 (
4066 "disable_help_subcommand",
4067 &mut options.disable_help_subcommand,
4068 ),
4069 ] {
4070 if meta.path.is_ident(key) {
4071 *slot = true;
4072 return Ok(());
4073 }
4074 }
4075 if !meta.path.is_ident("group") {
4076 return Err(meta.error(
4077 "expected `group(\"name\", ...)`, `restart_token`, `default_subcommand`, \
4078 `default_subcommand_flags`, `arg_required_else_help`, `unknown_flags`, `long_only`, `plus_options`, `name`, `version`, `about`, \
4079 `long_about`, `after_help`, `after_long_help` or a `disable_*` option",
4080 ));
4081 }
4082 let content;
4083 syn::parenthesized!(content in meta.input);
4084 let name = content.parse::<LitStr>()?.value();
4085 let (mut required, mut multiple) = (false, false);
4086 while !content.is_empty() {
4087 content.parse::<syn::Token![,]>()?;
4088 let flag = content.parse::<Ident>()?;
4089 match flag.to_string().as_str() {
4090 "required" => required = true,
4091 "multiple" => multiple = true,
4092 _ => {
4093 return Err(syn::Error::new(
4094 flag.span(),
4095 "expected `required` or `multiple`",
4096 ));
4097 }
4098 }
4099 }
4100 options.groups.push(Group {
4101 name,
4102 required,
4103 multiple,
4104 });
4105 Ok(())
4106 })?;
4107 }
4108 Ok(options)
4109}
4110
4111struct Rules {
4113 conflicts: Vec<(usize, usize)>,
4115 beats: Vec<Vec<usize>>,
4117 requires: Vec<(usize, usize)>,
4119 required_unless: Vec<(usize, Vec<usize>)>,
4121 members: Vec<Vec<usize>>,
4123 default_ifs: Vec<(usize, usize, Option<String>, String)>,
4125}
4126
4127impl Rules {
4128 fn new(fields: &[Field], groups: &[Group]) -> syn::Result<Self> {
4129 let resolve = |from: &Field, selector: &str| -> syn::Result<usize> {
4130 fields
4131 .iter()
4132 .position(
4133 |f| match (selector.strip_prefix("--"), selector.strip_prefix('-')) {
4134 (Some(long), _) => f.longs().contains(&long),
4135 (None, Some(short)) => {
4136 let mut chars = short.chars();
4137 chars
4138 .next()
4139 .is_some_and(|c| chars.next().is_none() && f.shorts().contains(&c))
4140 }
4141 (None, None) => {
4142 matches!(&f.role, Role::Positional { name, .. } if name == selector)
4143 || f.ident == selector
4144 }
4145 },
4146 )
4147 .ok_or_else(|| {
4148 let negated = selector
4150 .strip_prefix("--")
4151 .and_then(|long| fields.iter().find(|f| f.negate.as_deref() == Some(long)));
4152 let message = match negated {
4153 Some(f) => format!(
4154 "`{selector}` is the negation of `{}`: name that flag instead",
4155 f.display()
4156 ),
4157 None => format!("`{selector}` names no flag or positional of this struct"),
4158 };
4159 syn::Error::new(from.ident.span(), message)
4160 })
4161 };
4162 let mut rules = Rules {
4163 conflicts: Vec::new(),
4164 beats: vec![Vec::new(); fields.len()],
4165 requires: Vec::new(),
4166 required_unless: Vec::new(),
4167 members: vec![Vec::new(); groups.len()],
4168 default_ifs: Vec::new(),
4169 };
4170 for (i, f) in fields.iter().enumerate() {
4171 for selector in &f.conflicts {
4172 let j = resolve(f, selector)?;
4173 let pair = (i.min(j), i.max(j));
4174 if !rules.conflicts.contains(&pair) {
4175 rules.conflicts.push(pair);
4176 }
4177 }
4178 for selector in &f.overrides {
4179 let j = resolve(f, selector)?;
4180 if !rules.beats[i].contains(&j) {
4181 rules.beats[i].push(j);
4182 }
4183 if !rules.beats[j].contains(&i) {
4184 rules.beats[j].push(i);
4185 }
4186 }
4187 for selector in &f.requires {
4188 rules.requires.push((i, resolve(f, selector)?));
4189 }
4190 for (selector, equals, value) in &f.default_ifs {
4191 let other = resolve(f, selector)?;
4192 let comparable = match &fields[other].kind {
4193 Kind::Count(_) => false,
4194 Kind::Switch => equals.as_deref().is_none_or(is_bool_word),
4195 _ => !fields[other].keywords,
4196 };
4197 if equals.is_some() && !comparable {
4198 return Err(syn::Error::new(
4199 f.ident.span(),
4200 format!(
4201 "`{selector}` has no value to compare: use `default_if(\"{selector}\", \"…\")`"
4202 ),
4203 ));
4204 }
4205 rules
4206 .default_ifs
4207 .push((i, other, equals.clone(), value.clone()));
4208 }
4209 if !f.required_unless.is_empty() {
4210 let others = f
4211 .required_unless
4212 .iter()
4213 .map(|selector| resolve(f, selector))
4214 .collect::<syn::Result<_>>()?;
4215 rules.required_unless.push((i, others));
4216 }
4217 if let Some(group) = &f.group {
4218 let g = groups
4219 .iter()
4220 .position(|g| &g.name == group)
4221 .ok_or_else(|| {
4222 syn::Error::new(
4223 f.ident.span(),
4224 format!("no `#[arg(group(\"{group}\"))]` on the struct"),
4225 )
4226 })?;
4227 rules.members[g].push(i);
4228 }
4229 }
4230 if let Some((g, _)) = groups
4231 .iter()
4232 .zip(&rules.members)
4233 .find(|(g, members)| g.required && members.is_empty())
4234 {
4235 return Err(syn::Error::new(
4236 Span::call_site(),
4237 format!("group `{}` is required, but no field is in it", g.name),
4238 ));
4239 }
4240 Ok(rules)
4241 }
4242
4243 fn is_displaced(&self, fields: &[Field], f: &Field) -> bool {
4245 !self.beats[index_of(fields, f)].is_empty()
4246 }
4247
4248 fn displaced_slots(&self, fields: &[Field]) -> Vec<TokenStream2> {
4250 fields
4251 .iter()
4252 .enumerate()
4253 .filter(|(i, _)| !self.beats[*i].is_empty())
4254 .map(|(_, f)| {
4255 let displaced = displaced_flag(&f.ident);
4256 quote!(let mut #displaced = false;)
4257 })
4258 .collect()
4259 }
4260
4261 fn displace(&self, fields: &[Field], f: &Field) -> TokenStream2 {
4263 let i = index_of(fields, f);
4264 if self.beats[i].is_empty() {
4265 return quote!();
4266 }
4267 let standing = displaced_flag(&f.ident);
4268 let clears = self.beats[i].iter().map(|&j| {
4269 let other = &fields[j];
4270 let slot = slot(&other.ident);
4271 let displaced = displaced_flag(&other.ident);
4272 let clear = match &other.kind {
4273 _ if other.keywords => quote!(#slot.clear();),
4274 Kind::Switch if other.negate.is_some() => {
4275 quote!(#slot = ::core::option::Option::None;)
4276 }
4277 Kind::Switch => quote!(#slot = false;),
4278 Kind::Count(_) => quote!(#slot = 0;),
4279 Kind::Optional(_) | Kind::Required(_) => {
4280 quote!(#slot = ::core::option::Option::None;)
4281 }
4282 Kind::Many(_) => quote!(#slot.clear();),
4283 };
4284 quote!(#clear #displaced = true;)
4285 });
4286 quote!(#standing = false; #(#clears)*)
4287 }
4288
4289 fn exclusive(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
4291 let pairs = self.conflicts.iter().map(|&(a, b)| {
4292 let (fa, fb) = (&fields[a], &fields[b]);
4293 let (has_a, has_b) = (fa.has(), fb.has());
4294 let (name_a, name_b) = (fa.display(), fb.display());
4295 quote! {
4296 if #has_a && #has_b {
4297 return ::core::result::Result::Err(
4298 __wa::Error::conflict(__input.offset(), #name_a, #name_b),
4299 );
4300 }
4301 }
4302 });
4303 let at_most_one = groups.iter().zip(&self.members).filter(|(g, _)| !g.multiple).map(|(_, members)| {
4304 let checks = members.iter().map(|&m| {
4305 let has = fields[m].has();
4306 let name = fields[m].display();
4307 quote! {
4308 if #has {
4309 if let ::core::option::Option::Some(__first) = __first {
4310 return ::core::result::Result::Err(
4311 __wa::Error::conflict(__input.offset(), __first, #name),
4312 );
4313 }
4314 __first = ::core::option::Option::Some(#name);
4315 }
4316 }
4317 });
4318 quote! {{
4319 let mut __first: ::core::option::Option<&'static str> = ::core::option::Option::None;
4320 #(#checks)*
4321 }}
4322 });
4323 quote!(#(#pairs)* #(#at_most_one)*)
4324 }
4325
4326 fn default_ifs(&self, fields: &[Field]) -> TokenStream2 {
4329 let (mut conditions, mut fills) = (Vec::new(), Vec::new());
4330 for (n, (field, other, equals, value)) in self.default_ifs.iter().enumerate() {
4331 let (f, other) = (&fields[*field], &fields[*other]);
4332 let holds = format_ident!("__default_if_{n}");
4333 let other_slot = slot(&other.ident);
4334 let is = |ty: &Type, equals: &str| {
4335 let literal = LitByteStr::new(equals.as_bytes(), Span::call_site());
4336 quote! {
4337 |__v| <#ty as ::winnow_args::FromArg>::from_arg(__wa::BStr::new(#literal))
4338 .is_ok_and(|__literal| __literal == *__v)
4339 }
4340 };
4341 let condition = match (equals, &other.kind) {
4342 (None, _) => other.has(),
4343 (Some(equals), Kind::Switch) => {
4344 let equals = equals == "true";
4345 match other.negate {
4346 Some(_) => quote!((#other_slot == ::core::option::Option::Some(#equals))),
4347 None => quote!((#other_slot == #equals)),
4348 }
4349 }
4350 (Some(equals), Kind::Optional(ty) | Kind::Required(ty)) => {
4351 let is = is(ty, equals);
4352 quote!(#other_slot.as_ref().is_some_and(#is))
4353 }
4354 (Some(equals), Kind::Many(ty)) => {
4355 let is = is(ty, equals);
4356 quote!(#other_slot.iter().any(#is))
4357 }
4358 (Some(_), Kind::Count(_)) => unreachable!("refused when the rules were read"),
4359 };
4360 conditions.push(quote!(let #holds = #condition;));
4361 let unset = f.unset(self.is_displaced(fields, f));
4362 let slot = slot(&f.ident);
4363 let fill = match &f.kind {
4364 Kind::Switch => {
4365 let value = value == "true";
4366 match f.negate {
4367 Some(_) => quote!(#slot = ::core::option::Option::Some(#value);),
4368 None => quote!(#slot = #value;),
4369 }
4370 }
4371 _ => {
4372 let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
4373 f.assign(quote!(__wa::BStr::new(#bytes)), &f.display())
4374 }
4375 };
4376 fills.push(quote!(if #holds && #unset { #fill }));
4377 }
4378 quote!(#(#conditions)* #(#fills)*)
4379 }
4380
4381 fn supplied(&self, fields: &[Field]) -> TokenStream2 {
4383 let mut seen = Vec::new();
4384 let snapshots = self.requires.iter().filter_map(|&(i, _)| {
4385 if seen.contains(&i) {
4386 return None;
4387 }
4388 seen.push(i);
4389 let supplied = format_ident!("__supplied_{}", fields[i].ident);
4390 let has = fields[i].has();
4391 Some(quote!(let #supplied = #has;))
4392 });
4393 quote!(#(#snapshots)*)
4394 }
4395
4396 fn required(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
4399 let missing = |f: &Field| {
4400 let name = f.display();
4401 match f.role {
4402 Role::Positional { .. } => {
4403 quote!(__wa::Error::missing_argument(__input.offset(), #name))
4404 }
4405 _ => quote!(__wa::Error::missing_required(__input.offset(), #name)),
4406 }
4407 };
4408 let displaced = |f: &Field| {
4410 self.is_displaced(fields, f).then(|| {
4411 let flag = displaced_flag(&f.ident);
4412 quote!(&& !#flag)
4413 })
4414 };
4415 let plain = fields.iter().filter(|f| f.required).map(|f| {
4416 let (has, displaced) = (f.has(), displaced(f));
4417 let error = missing(f);
4418 quote!(if !#has #displaced { return ::core::result::Result::Err(#error); })
4419 });
4420 let unless = self.required_unless.iter().map(|(i, others)| {
4421 let (has, displaced) = (fields[*i].has(), displaced(&fields[*i]));
4422 let others = others.iter().map(|&j| fields[j].has());
4423 let error = missing(&fields[*i]);
4424 quote! {
4425 if !#has #displaced #(&& !#others)* {
4426 return ::core::result::Result::Err(#error);
4427 }
4428 }
4429 });
4430 let one_of = groups
4431 .iter()
4432 .zip(&self.members)
4433 .filter(|(g, _)| g.required)
4434 .map(|(g, members)| {
4435 let name = &g.name;
4436 let has = members.iter().map(|&m| fields[m].has());
4437 let names = members.iter().map(|&m| fields[m].display());
4438 quote! {
4439 if #(!#has)&&* {
4440 return ::core::result::Result::Err(
4441 __wa::Error::missing_one_of(__input.offset(), #name, &[#(#names),*]),
4442 );
4443 }
4444 }
4445 });
4446 let requires = self.requires.iter().map(|&(i, j)| {
4447 let supplied = format_ident!("__supplied_{}", fields[i].ident);
4448 let has = fields[j].has();
4449 let (by, target) = (fields[i].display(), fields[j].display());
4450 quote! {
4451 if #supplied && !#has {
4452 return ::core::result::Result::Err(
4453 __wa::Error::required_by(__input.offset(), #target, #by),
4454 );
4455 }
4456 }
4457 });
4458 quote!(#(#plain)* #(#unless)* #(#one_of)* #(#requires)*)
4459 }
4460}
4461
4462fn vec_field(f: Option<&syn::Field>, shape: &str) -> syn::Result<Option<(Ident, Type)>> {
4464 let Some(f) = f else {
4465 return Ok(None);
4466 };
4467 let ty = last_segment(&f.ty)
4468 .filter(|s| s.ident == "Vec")
4469 .and_then(inner)
4470 .ok_or_else(|| syn::Error::new(f.ty.span(), shape))?;
4471 Ok(Some((f.ident.clone().expect("named field"), ty.clone())))
4472}
4473
4474fn subcommand_type(f: &Field) -> &Type {
4476 match &f.kind {
4477 Kind::Optional(ty) | Kind::Required(ty) => ty,
4478 _ => unreachable!("rejected for subcommands in `field`"),
4479 }
4480}
4481
4482fn displaced_flag(ident: &Ident) -> Ident {
4483 format_ident!("__displaced_{}", ident)
4484}
4485
4486fn index_of(fields: &[Field], f: &Field) -> usize {
4487 fields
4488 .iter()
4489 .position(|g| g.ident == f.ident)
4490 .expect("a field of this struct")
4491}
4492
4493fn slot(ident: &Ident) -> Ident {
4494 format_ident!("__slot_{}", ident)
4495}
4496
4497fn field(f: &syn::Field) -> syn::Result<Field> {
4498 let ident = f.ident.clone().expect("named field");
4499 let bare = ident.to_string().trim_start_matches("r#").to_owned();
4500
4501 let mut short = None;
4502 let mut long = None;
4503 let mut positional = false;
4504 let mut count = false;
4505 let mut subcommand = false;
4506 let mut global = false;
4507 let mut alias = Vec::new();
4508 let mut delimiter = None;
4509 let mut choices = None;
4510 let mut env = None;
4511 let mut default = None;
4512 let mut double_dash = None;
4513 let mut default_missing = None;
4514 let mut negative_numbers = false;
4515 let mut hyphen_values = false;
4516 let mut require_equals = false;
4517 let mut keep_equals = false;
4518 let (mut two_dashes, mut prefix) = (false, false);
4519 let mut values = 1;
4520 let (mut values_max, mut value_terminator) = (None, None);
4521 let (mut default_fn, mut default_note, mut default_ifs) = (None, None, Vec::new());
4522 let mut stop_flags = false;
4523 let mut plus: Option<char> = None;
4524 let mut keywords = false;
4525 let mut short_aliases = Vec::new();
4526 let mut negate: Option<Option<String>> = None;
4527 let (doc_help, doc_long_help) = docs(&f.attrs);
4528 let (mut help, mut long_help, mut heading, mut hide) = (None, None, None, false);
4529 let (mut conflicts, mut overrides, mut requires) = (Vec::new(), Vec::new(), Vec::new());
4530 let (mut required, mut required_unless, mut group) = (false, Vec::new(), None);
4531 let mut value_name = None;
4532 let (mut hint, mut complete) = (None, None);
4533 for attr in f.attrs.iter().filter(|a| is_ours(a)) {
4534 attr.parse_nested_meta(|meta| {
4535 if meta.path.is_ident("hint") {
4536 let name = meta.value()?.parse::<LitStr>()?;
4537 let variant = match name.value().as_str() {
4538 "file" => "Files",
4539 "dir" => "Dirs",
4540 "command" => "Commands",
4541 "none" => "Nothing",
4542 _ => {
4543 return Err(syn::Error::new(
4544 name.span(),
4545 "a hint is \"file\", \"dir\", \"command\" or \"none\"",
4546 ));
4547 }
4548 };
4549 hint = Some(syn::Ident::new(variant, name.span()));
4550 } else if meta.path.is_ident("complete") {
4551 complete = Some(meta.value()?.parse::<syn::Path>()?);
4552 } else if meta.path.is_ident("short") {
4553 let c = if meta.input.peek(syn::Token![=]) {
4554 meta.value()?.parse::<LitChar>()?.value()
4555 } else {
4556 bare.chars()
4557 .next()
4558 .ok_or_else(|| meta.error("cannot infer a short name"))?
4559 };
4560 match short {
4562 None => short = Some(c),
4563 Some(_) => short_aliases.push(c),
4564 }
4565 } else if meta.path.is_ident("long") {
4566 let name = if meta.input.peek(syn::Token![=]) {
4567 meta.value()?.parse::<LitStr>()?.value()
4568 } else {
4569 bare.replace('_', "-")
4570 };
4571 match long {
4573 None => long = Some(name),
4574 Some(_) => alias.push(name),
4575 }
4576 } else if meta.path.is_ident("positional") {
4577 positional = true;
4578 } else if meta.path.is_ident("count") {
4579 count = true;
4580 } else if meta.path.is_ident("subcommand") {
4581 subcommand = true;
4582 } else if meta.path.is_ident("global") {
4583 global = true;
4584 } else if meta.path.is_ident("alias") {
4585 alias.extend(aliases(&meta)?);
4586 } else if meta.path.is_ident("help") {
4587 help = Some(meta.value()?.parse::<LitStr>()?.value());
4588 } else if meta.path.is_ident("long_help") {
4589 long_help = Some(meta.value()?.parse::<LitStr>()?.value());
4590 } else if meta.path.is_ident("help_heading") {
4591 heading = Some(meta.value()?.parse::<LitStr>()?.value());
4592 } else if meta.path.is_ident("hide") {
4593 hide = true;
4594 } else if meta.path.is_ident("negate") {
4595 negate = Some(if meta.input.peek(syn::Token![=]) {
4596 let name = meta.value()?.parse::<LitStr>()?.value();
4597 Some(name.strip_prefix("--").unwrap_or(&name).to_owned())
4599 } else {
4600 None
4601 });
4602 } else if meta.path.is_ident("keywords") {
4603 keywords = true;
4604 } else if skip_spec(&meta)? {
4605 } else if meta.path.is_ident("plus") {
4606 plus = Some(meta.value()?.parse::<LitChar>()?.value());
4607 } else if meta.path.is_ident("stop_flags") {
4608 stop_flags = true;
4609 } else if meta.path.is_ident("values") {
4610 let count = meta.value()?.parse::<syn::Expr>()?;
4611 (values, values_max) = value_count(&count)?;
4612 } else if meta.path.is_ident("value_terminator") {
4613 value_terminator = Some(meta.value()?.parse::<LitStr>()?.value());
4614 } else if meta.path.is_ident("default_fn") {
4615 default_fn = Some(meta.value()?.parse::<syn::ExprPath>()?);
4616 } else if meta.path.is_ident("default_note") {
4617 default_note = Some(meta.value()?.parse::<LitStr>()?.value());
4618 } else if meta.path.is_ident("default_if") {
4619 let content;
4620 syn::parenthesized!(content in meta.input);
4621 let list =
4622 syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
4623 let mut words = list.iter().map(LitStr::value);
4624 default_ifs.push(match (words.next(), words.next(), words.next(), words.next()) {
4625 (Some(selector), Some(value), None, _) => (selector, None, value),
4626 (Some(selector), Some(equals), Some(value), None) => {
4627 (selector, Some(equals), value)
4628 }
4629 _ => {
4630 return Err(meta.error(
4631 "expected `default_if(\"--flag\", \"value\")` or \
4632 `default_if(\"--flag\", \"its value\", \"value\")`",
4633 ));
4634 }
4635 });
4636 } else if meta.path.is_ident("two_dashes") {
4637 two_dashes = true;
4638 } else if meta.path.is_ident("prefix") {
4639 prefix = true;
4640 } else if meta.path.is_ident("require_equals") {
4641 require_equals = true;
4642 } else if meta.path.is_ident("keep_equals") {
4643 keep_equals = true;
4644 } else if meta.path.is_ident("allow_hyphen_values") {
4645 hyphen_values = true;
4646 } else if meta.path.is_ident("allow_negative_numbers") {
4647 negative_numbers = true;
4648 } else if meta.path.is_ident("default_missing") {
4649 default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
4650 } else if meta.path.is_ident("double_dash") {
4651 let mode = meta.value()?.parse::<LitStr>()?;
4652 double_dash = Some(match mode.value().as_str() {
4653 "required" => DoubleDash::Required,
4654 "automatic" => DoubleDash::Automatic,
4655 "optional" => DoubleDash::Optional,
4656 "preserve" => DoubleDash::Preserve,
4657 _ => {
4658 return Err(syn::Error::new(
4659 mode.span(),
4660 "expected \"required\", \"automatic\", \"optional\" or \"preserve\"",
4661 ));
4662 }
4663 });
4664 } else if meta.path.is_ident("conflicts") {
4665 conflicts.extend(aliases(&meta)?);
4666 } else if meta.path.is_ident("overrides") {
4667 overrides.extend(aliases(&meta)?);
4668 } else if meta.path.is_ident("requires") {
4669 requires.extend(aliases(&meta)?);
4670 } else if meta.path.is_ident("required_unless") {
4671 required_unless.extend(aliases(&meta)?);
4672 } else if meta.path.is_ident("required") {
4673 required = true;
4674 } else if meta.path.is_ident("group") {
4675 group = Some(meta.value()?.parse::<LitStr>()?.value());
4676 } else if meta.path.is_ident("env") {
4677 env = Some(meta.value()?.parse::<LitStr>()?.value());
4678 } else if meta.path.is_ident("default") {
4679 default = Some(meta.value()?.parse::<LitStr>()?.value());
4680 } else if meta.path.is_ident("choices") {
4681 let content;
4682 syn::parenthesized!(content in meta.input);
4683 let list =
4684 syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
4685 choices = Some(list.iter().map(LitStr::value).collect());
4686 } else if meta.path.is_ident("delimiter") {
4687 let c = meta.value()?.parse::<LitChar>()?;
4688 if !c.value().is_ascii() {
4689 return Err(syn::Error::new(c.span(), "a delimiter is one ASCII character"));
4690 }
4691 delimiter = Some(c.value() as u8);
4692 } else if meta.path.is_ident("value_name") {
4693 value_name = Some(meta.value()?.parse::<LitStr>()?.value());
4694 } else {
4695 return Err(meta.error(
4696 "unknown `arg` option; expected one of `short`, `long`, `alias`, `global`, `count`, \
4697 `positional`, `value_name`, `double_dash`, `subcommand`, `flatten`, `sequence`, `unknown`, `skip`, \
4698 `delimiter`, `choices`, `env`, `default`, `default_missing`, `allow_negative_numbers`, \
4699 `allow_hyphen_values`, `require_equals`, `keep_equals`, `negate`, `two_dashes`, `prefix`, `values`, \
4700 `value_terminator`, `default_fn`, `default_note`, `default_if`, \
4701 `stop_flags`, `plus`, `keywords`, `conflicts`, `overrides`, `requires`, `required`, `required_unless`, \
4702 `group`, `help`, `long_help`, `help_heading`, `hide`, `hint`, `complete`",
4703 ));
4704 }
4705 Ok(())
4706 })?;
4707 }
4708
4709 let error = |msg: String| Err(syn::Error::new(f.span(), msg));
4710 if delimiter.is_some() && (count || !matches!(kind(&f.ty), Kind::Many(_))) {
4711 return error("`delimiter` needs a `Vec<T>` field to put the pieces in".into());
4712 }
4713 let kind = if count {
4714 Kind::Count(f.ty.clone())
4715 } else {
4716 kind(&f.ty)
4717 };
4718 if default_missing.is_some()
4719 && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
4720 {
4721 return error("`default_missing` is for flags that take a value".into());
4722 }
4723 if (hyphen_values || require_equals)
4724 && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
4725 {
4726 return error(
4727 "`allow_hyphen_values` and `require_equals` are for flags that take a value".into(),
4728 );
4729 }
4730 if negative_numbers && (subcommand || matches!(kind, Kind::Switch | Kind::Count(_))) {
4731 return error("`allow_negative_numbers` is for fields that take a value".into());
4732 }
4733 let tristate =
4734 plus.is_some() && plus == short && matches!(&kind, Kind::Optional(ty) if is_bool(ty));
4735 if plus.is_some()
4736 && (positional || subcommand || !tristate && matches!(kind, Kind::Switch | Kind::Count(_)))
4737 {
4738 return error(
4739 "`plus` is for a flag taking a value, or an `Option<bool>` with the same `short` letter".into(),
4740 );
4741 }
4742 if keywords
4743 && (positional
4744 || subcommand
4745 || !matches!(kind, Kind::Required(_))
4746 || env.is_some()
4747 || default.is_some()
4748 || default_missing.is_some()
4749 || choices.is_some()
4750 || delimiter.is_some())
4751 {
4752 return error(
4753 "`keywords` is for a flag whose type derives `Args`, without `env`, `default`, \
4754 `default_missing`, `choices` or `delimiter`"
4755 .into(),
4756 );
4757 }
4758 if stop_flags && !positional {
4759 return error("`stop_flags` is for positional fields".into());
4760 }
4761 if double_dash.is_some() && !positional {
4762 return error("`double_dash` is for positional fields".into());
4763 }
4764 if (values > 1 || values_max.is_some())
4765 && (positional
4766 || subcommand
4767 || !matches!(kind, Kind::Many(_))
4768 || delimiter.is_some()
4769 || default_missing.is_some())
4770 {
4771 return error(
4772 "`values` is for a `Vec<T>` flag, without `delimiter` or `default_missing`".into(),
4773 );
4774 }
4775 if values_max.is_some() && require_equals {
4776 return error("a range of `values` takes detached words: not with `require_equals`".into());
4777 }
4778 if value_terminator.is_some() && values_max.is_none() {
4779 return error("`value_terminator` ends a range of `values` (`values = 1..`)".into());
4780 }
4781 let valueless = matches!(kind, Kind::Switch | Kind::Count(_)) && negate.is_none();
4782 if default_fn.is_some() && (default.is_some() || subcommand || keywords || valueless) {
4783 return error(
4784 "`default_fn` is for a field that takes a value, or `negate`, without `default`".into(),
4785 );
4786 }
4787 if default_note.is_some() && default_fn.is_none() {
4788 return error("`default_note` describes a `default_fn`".into());
4789 }
4790 if !default_ifs.is_empty() && (subcommand || keywords || matches!(kind, Kind::Count(_))) {
4791 return error("`default_if` is for a switch or a field that takes a value".into());
4792 }
4793 if let (Kind::Switch, Some((.., value))) =
4794 (&kind, default_ifs.iter().find(|d| !is_bool_word(&d.2)))
4795 {
4796 return error(format!(
4797 "a switch's `default_if` value is `true` or `false`, not `{value}`"
4798 ));
4799 }
4800 if two_dashes && (long.is_none() && alias.is_empty() || positional || subcommand) {
4801 return error("`two_dashes` is for flags with a long name".into());
4802 }
4803 if prefix
4804 && (short.is_none_or(|c| !c.is_ascii())
4805 || positional
4806 || subcommand
4807 || matches!(kind, Kind::Switch | Kind::Count(_)))
4808 {
4809 return error("`prefix` is for a value-taking flag with an ASCII `short` letter".into());
4810 }
4811 let flag_value_name =
4812 (!positional && !subcommand && !matches!(kind, Kind::Switch | Kind::Count(_)))
4813 .then(|| value_name.clone().unwrap_or_else(|| bare.to_uppercase()));
4814 let role = if subcommand {
4815 if positional || count || global || short.is_some() || long.is_some() || !alias.is_empty() {
4816 return error("a subcommand field takes no other `arg` options".into());
4817 }
4818 if !matches!(kind, Kind::Optional(_) | Kind::Required(_)) {
4819 return error("a subcommand field is `E` or `Option<E>`".into());
4820 }
4821 Role::Subcommand
4822 } else if positional {
4823 if short.is_some() || long.is_some() || global || !alias.is_empty() {
4824 return error("a positional field has no `short`, `long`, `alias` or `global`".into());
4825 }
4826 if matches!(kind, Kind::Switch | Kind::Count(_)) {
4827 return error("a positional field cannot be `bool` or `count`".into());
4828 }
4829 Role::Positional {
4830 double_dash: double_dash.unwrap_or(DoubleDash::Optional),
4831 name: value_name.unwrap_or_else(|| bare.to_uppercase()),
4832 }
4833 } else {
4834 if short.is_none() && long.is_none() && plus.is_none() {
4837 long = Some(bare.replace('_', "-"));
4838 }
4839 for l in long.iter().chain(&alias) {
4840 if l.is_empty() || l.starts_with('-') || l.contains('=') {
4841 return error(format!("`{l}` is not a usable long name"));
4842 }
4843 }
4844 if let Some(c) = short
4845 && (c == '-' || c == '=')
4846 {
4847 return error(format!("`{c}` is not a usable short name"));
4848 }
4849 Role::Flag {
4850 short,
4851 long,
4852 aliases: alias,
4853 global,
4854 }
4855 };
4856
4857 if choices.is_some()
4858 && (matches!(kind, Kind::Switch | Kind::Count(_)) || matches!(role, Role::Subcommand))
4859 {
4860 return error("`choices` needs a field that takes a value".into());
4861 }
4862 if (env.is_some() || default.is_some()) && matches!(role, Role::Subcommand) {
4863 return error("a subcommand field takes no `env` or `default`".into());
4864 }
4865 let negate = match negate {
4866 None => None,
4867 Some(_) if !matches!(kind, Kind::Switch) || !matches!(role, Role::Flag { .. }) => {
4868 return error("`negate` is for `bool` flags".into());
4869 }
4870 Some(Some(name)) => Some(name),
4871 Some(None) => match &role {
4872 Role::Flag { long: Some(l), .. } => Some(format!("no-{l}")),
4873 _ => return error("`negate` without a name needs a `long` to prefix".into()),
4874 },
4875 };
4876 if let Some(no) = &negate
4877 && (no.is_empty() || no.starts_with('-') || no.contains('='))
4878 {
4879 return error(format!("`{no}` is not a usable long name"));
4880 }
4881 match (&default, &negate) {
4882 (Some(d), Some(_)) if d != "true" && d != "false" => {
4883 return error("a negatable flag's `default` is \"true\" or \"false\"".into());
4884 }
4885 (Some(_), None) if matches!(kind, Kind::Switch | Kind::Count(_)) => {
4886 return error("`default` needs a field that takes a value, or `negate`".into());
4887 }
4888 _ => {}
4889 }
4890 if (hint.is_some() || complete.is_some())
4891 && (matches!(kind, Kind::Switch | Kind::Count(_))
4892 || !matches!(role, Role::Flag { .. } | Role::Positional { .. }))
4893 {
4894 return error("`hint` and `complete` are for a field that takes a value".into());
4895 }
4896 if hint.is_some() && complete.is_some() {
4897 return error("`complete` answers alone: `hint` beside it would never be asked".into());
4898 }
4899 let spec = spec_name(&f.attrs)?;
4900 if spec.is_some() && matches!(kind, Kind::Switch | Kind::Count(_)) {
4901 return error("`spec` names the type of a field's value, and this field has none".into());
4902 }
4903 Ok(Field {
4904 ident,
4905 spec,
4906 kind,
4907 role,
4908 delimiter,
4909 choices,
4910 env,
4911 default,
4912 conflicts,
4913 overrides,
4914 requires,
4915 required,
4916 required_unless,
4917 group,
4918 default_missing,
4919 long_help: long_help.or_else(|| help.clone()).unwrap_or(doc_long_help),
4920 help: help.unwrap_or(doc_help),
4921 heading,
4922 hide,
4923 value_name: if tristate { None } else { flag_value_name },
4924 hint,
4925 complete,
4926 negative_numbers,
4927 hyphen_values,
4928 require_equals,
4929 keep_equals,
4930 negate,
4931 two_dashes,
4932 prefix,
4933 values,
4934 values_max,
4935 value_terminator,
4936 default_fn,
4937 default_note,
4938 default_ifs,
4939 stop_flags,
4940 plus,
4941 tristate,
4942 keywords,
4943 short_aliases,
4944 })
4945}
4946
4947fn is_bool_word(word: &str) -> bool {
4948 matches!(word, "true" | "false")
4949}
4950
4951fn value_count(count: &syn::Expr) -> syn::Result<(usize, Option<usize>)> {
4954 let number = |e: &syn::Expr| match e {
4955 syn::Expr::Lit(syn::ExprLit {
4956 lit: syn::Lit::Int(n),
4957 ..
4958 }) => n.base10_parse::<usize>(),
4959 _ => Err(syn::Error::new(e.span(), "expected a number")),
4960 };
4961 let (min, max) = match count {
4962 syn::Expr::Range(range) => {
4963 let min = range.start.as_deref().map(number).transpose()?.unwrap_or(1);
4964 let max = match (&range.end, &range.limits) {
4965 (None, _) => usize::MAX,
4966 (Some(end), syn::RangeLimits::Closed(_)) => number(end)?,
4967 (Some(end), syn::RangeLimits::HalfOpen(_)) => number(end)?.saturating_sub(1),
4968 };
4969 (min, Some(max))
4970 }
4971 exact => (number(exact)?, None),
4972 };
4973 if min == 0 || max.is_some_and(|max| max < min) {
4974 return Err(syn::Error::new(
4975 count.span(),
4976 "`values` is at least 1, and a range does not end below its start",
4977 ));
4978 }
4979 Ok((min, max))
4980}
4981
4982fn spellings(f: &Field) -> Vec<&str> {
4984 let mut longs = f.longs();
4985 longs.extend(f.negate.as_deref());
4986 longs
4987}
4988
4989fn check_duplicates(fields: &[Field]) -> syn::Result<()> {
4990 for (i, a) in fields.iter().enumerate() {
4991 for b in &fields[..i] {
4992 let theirs = b.shorts();
4993 let clash = a
4994 .shorts()
4995 .into_iter()
4996 .find(|c| theirs.contains(c))
4997 .map(|c| format!("-{c}"))
4998 .or_else(|| {
4999 let longs = spellings(b);
5000 spellings(a)
5001 .into_iter()
5002 .find(|l| longs.contains(l))
5003 .map(|l| format!("--{l}"))
5004 });
5005 if let Some(name) = clash {
5006 return Err(syn::Error::new(
5007 a.ident.span(),
5008 format!("`{name}` is already used by `{}`", b.ident),
5009 ));
5010 }
5011 }
5012 }
5013 Ok(())
5014}
5015
5016fn check_positional_order(positionals: &[&Field]) -> syn::Result<()> {
5019 let rank = |f: &Field| match f.kind {
5020 Kind::Required(_) => 0,
5021 Kind::Optional(_) => 1,
5022 Kind::Many(_) | Kind::Switch | Kind::Count(_) => 2,
5023 };
5024 for pair in positionals.windows(2) {
5025 if rank(pair[1]) < rank(pair[0]) || rank(pair[0]) == 2 {
5026 return Err(syn::Error::new(
5027 pair[1].ident.span(),
5028 "positionals must be required, then optional, then one `Vec` last",
5029 ));
5030 }
5031 }
5032 Ok(())
5033}
5034
5035fn last_segment(ty: &Type) -> Option<&syn::PathSegment> {
5036 match ty {
5037 Type::Path(path) if path.qself.is_none() => path.path.segments.last(),
5038 _ => None,
5039 }
5040}
5041
5042fn inner(segment: &syn::PathSegment) -> Option<&Type> {
5043 match &segment.arguments {
5044 PathArguments::AngleBracketed(args) => match args.args.first()? {
5045 GenericArgument::Type(inner) => Some(inner),
5046 _ => None,
5047 },
5048 _ => None,
5049 }
5050}
5051
5052fn kind(ty: &Type) -> Kind {
5053 if is_bool(ty) {
5054 return Kind::Switch;
5055 }
5056 if let Some(last) = last_segment(ty)
5057 && let Some(inner) = inner(last)
5058 {
5059 if last.ident == "Option" {
5060 return Kind::Optional(inner.clone());
5061 }
5062 if last.ident == "Vec" {
5063 return Kind::Many(inner.clone());
5064 }
5065 }
5066 Kind::Required(ty.clone())
5067}