1// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, 2// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and 3// Ana Hobden (@hoverbear) <operator@hoverbear.org> 4// 5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or 6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license 7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your 8// option. This file may not be copied, modified, or distributed 9// except according to those terms. 10// 11// This work was derived from Structopt (https://github.com/TeXitoi/structopt) 12// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the 13// MIT/Apache 2.0 license. 14 15use proc_macro2::{Ident, Span, TokenStream}; 16use quote::{format_ident, quote, quote_spanned}; 17use syn::ext::IdentExt; 18use syn::{ 19 punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field, 20 Fields, Generics, 21}; 22 23use crate::item::{Item, Kind, Name}; 24use crate::utils::{inner_type, sub_type, Sp, Ty}; 25 26pub fn derive_args(input: &DeriveInput) -> Result<TokenStream, syn::Error> { 27 let ident = &input.ident; 28 29 match input.data { 30 Data::Struct(DataStruct { 31 fields: Fields::Named(ref fields), 32 .. 33 }) => { 34 let name = Name::Derived(ident.clone()); 35 let item = Item::from_args_struct(input, name)?; 36 let fields = fields 37 .named 38 .iter() 39 .map(|field| { 40 let item = Item::from_args_field(field, item.casing(), item.env_casing())?; 41 Ok((field, item)) 42 }) 43 .collect::<Result<Vec<_>, syn::Error>>()?; 44 gen_for_struct(&item, ident, &input.generics, &fields) 45 } 46 Data::Struct(DataStruct { 47 fields: Fields::Unit, 48 .. 49 }) => { 50 let name = Name::Derived(ident.clone()); 51 let item = Item::from_args_struct(input, name)?; 52 let fields = Punctuated::<Field, Comma>::new(); 53 let fields = fields 54 .iter() 55 .map(|field| { 56 let item = Item::from_args_field(field, item.casing(), item.env_casing())?; 57 Ok((field, item)) 58 }) 59 .collect::<Result<Vec<_>, syn::Error>>()?; 60 gen_for_struct(&item, ident, &input.generics, &fields) 61 } 62 _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"), 63 } 64} 65 66pub fn gen_for_struct( 67 item: &Item, 68 item_name: &Ident, 69 generics: &Generics, 70 fields: &[(&Field, Item)], 71) -> Result<TokenStream, syn::Error> { 72 if !matches!(&*item.kind(), Kind::Command(_)) { 73 abort! { item.kind().span(), 74 "`{}` cannot be used with `command`", 75 item.kind().name(), 76 } 77 } 78 79 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 80 81 let constructor = gen_constructor(fields)?; 82 let updater = gen_updater(fields, true)?; 83 let raw_deprecated = raw_deprecated(); 84 85 let app_var = Ident::new("__clap_app", Span::call_site()); 86 let augmentation = gen_augment(fields, &app_var, item, false)?; 87 let augmentation_update = gen_augment(fields, &app_var, item, true)?; 88 89 let group_id = if item.skip_group() { 90 quote!(None) 91 } else { 92 let group_id = item.ident().unraw().to_string(); 93 quote!(Some(clap::Id::from(#group_id))) 94 }; 95 96 Ok(quote! { 97 #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] 98 #[allow( 99 clippy::style, 100 clippy::complexity, 101 clippy::pedantic, 102 clippy::restriction, 103 clippy::perf, 104 clippy::deprecated, 105 clippy::nursery, 106 clippy::cargo, 107 clippy::suspicious_else_formatting, 108 clippy::almost_swapped, 109 )] 110 impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause { 111 fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> { 112 Self::from_arg_matches_mut(&mut __clap_arg_matches.clone()) 113 } 114 115 fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> { 116 #raw_deprecated 117 let v = #item_name #constructor; 118 ::std::result::Result::Ok(v) 119 } 120 121 fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { 122 self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone()) 123 } 124 125 fn update_from_arg_matches_mut(&mut self, __clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { 126 #raw_deprecated 127 #updater 128 ::std::result::Result::Ok(()) 129 } 130 } 131 132 #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] 133 #[allow( 134 clippy::style, 135 clippy::complexity, 136 clippy::pedantic, 137 clippy::restriction, 138 clippy::perf, 139 clippy::deprecated, 140 clippy::nursery, 141 clippy::cargo, 142 clippy::suspicious_else_formatting, 143 clippy::almost_swapped, 144 )] 145 impl #impl_generics clap::Args for #item_name #ty_generics #where_clause { 146 fn group_id() -> Option<clap::Id> { 147 #group_id 148 } 149 fn augment_args<'b>(#app_var: clap::Command) -> clap::Command { 150 #augmentation 151 } 152 fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command { 153 #augmentation_update 154 } 155 } 156 }) 157} 158 159/// Generate a block of code to add arguments/subcommands corresponding to 160/// the `fields` to an cmd. 161pub fn gen_augment( 162 fields: &[(&Field, Item)], 163 app_var: &Ident, 164 parent_item: &Item, 165 override_required: bool, 166) -> Result<TokenStream, syn::Error> { 167 let mut subcommand_specified = false; 168 let mut args = Vec::new(); 169 for (field, item) in fields { 170 let kind = item.kind(); 171 let genned = match &*kind { 172 Kind::Command(_) 173 | Kind::Value 174 | Kind::Skip(_, _) 175 | Kind::FromGlobal(_) 176 | Kind::ExternalSubcommand => None, 177 Kind::Subcommand(ty) => { 178 if subcommand_specified { 179 abort!( 180 field.span(), 181 "`#[command(subcommand)]` can only be used once per container" 182 ); 183 } 184 subcommand_specified = true; 185 186 let subcmd_type = match (**ty, sub_type(&field.ty)) { 187 (Ty::Option, Some(sub_type)) => sub_type, 188 _ => &field.ty, 189 }; 190 let implicit_methods = if **ty == Ty::Option { 191 quote!() 192 } else { 193 quote_spanned! { kind.span()=> 194 .subcommand_required(true) 195 .arg_required_else_help(true) 196 } 197 }; 198 199 let override_methods = if override_required { 200 quote_spanned! { kind.span()=> 201 .subcommand_required(false) 202 .arg_required_else_help(false) 203 } 204 } else { 205 quote!() 206 }; 207 208 Some(quote! { 209 let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var ); 210 let #app_var = #app_var 211 #implicit_methods 212 #override_methods; 213 }) 214 } 215 Kind::Flatten(ty) => { 216 let inner_type = match (**ty, sub_type(&field.ty)) { 217 (Ty::Option, Some(sub_type)) => sub_type, 218 _ => &field.ty, 219 }; 220 221 let next_help_heading = item.next_help_heading(); 222 let next_display_order = item.next_display_order(); 223 if override_required { 224 Some(quote_spanned! { kind.span()=> 225 let #app_var = #app_var 226 #next_help_heading 227 #next_display_order; 228 let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var); 229 }) 230 } else { 231 Some(quote_spanned! { kind.span()=> 232 let #app_var = #app_var 233 #next_help_heading 234 #next_display_order; 235 let #app_var = <#inner_type as clap::Args>::augment_args(#app_var); 236 }) 237 } 238 } 239 Kind::Arg(ty) => { 240 let value_parser = item.value_parser(&field.ty); 241 let action = item.action(&field.ty); 242 let value_name = item.value_name(); 243 244 let implicit_methods = match **ty { 245 Ty::Unit => { 246 // Leaving out `value_parser` as it will always fail 247 quote_spanned! { ty.span()=> 248 .value_name(#value_name) 249 #action 250 } 251 } 252 Ty::Option => { 253 quote_spanned! { ty.span()=> 254 .value_name(#value_name) 255 #value_parser 256 #action 257 } 258 } 259 260 Ty::OptionOption => quote_spanned! { ty.span()=> 261 .value_name(#value_name) 262 .num_args(0..=1) 263 #value_parser 264 #action 265 }, 266 267 Ty::OptionVec => { 268 if item.is_positional() { 269 quote_spanned! { ty.span()=> 270 .value_name(#value_name) 271 .num_args(1..) // action won't be sufficient for getting multiple 272 #value_parser 273 #action 274 } 275 } else { 276 quote_spanned! { ty.span()=> 277 .value_name(#value_name) 278 #value_parser 279 #action 280 } 281 } 282 } 283 284 Ty::Vec => { 285 if item.is_positional() { 286 quote_spanned! { ty.span()=> 287 .value_name(#value_name) 288 .num_args(1..) // action won't be sufficient for getting multiple 289 #value_parser 290 #action 291 } 292 } else { 293 quote_spanned! { ty.span()=> 294 .value_name(#value_name) 295 #value_parser 296 #action 297 } 298 } 299 } 300 301 Ty::VecVec | Ty::OptionVecVec => { 302 quote_spanned! { ty.span() => 303 .value_name(#value_name) 304 #value_parser 305 #action 306 } 307 } 308 309 Ty::Other => { 310 let required = item.find_default_method().is_none(); 311 // `ArgAction::takes_values` is assuming `ArgAction::default_value` will be 312 // set though that won't always be true but this should be good enough, 313 // otherwise we'll report an "arg required" error when unwrapping. 314 let action_value = action.args(); 315 quote_spanned! { ty.span()=> 316 .value_name(#value_name) 317 .required(#required && #action_value.takes_values()) 318 #value_parser 319 #action 320 } 321 } 322 }; 323 324 let id = item.id(); 325 let explicit_methods = item.field_methods(); 326 let deprecations = if !override_required { 327 item.deprecations() 328 } else { 329 quote!() 330 }; 331 let override_methods = if override_required { 332 quote_spanned! { kind.span()=> 333 .required(false) 334 } 335 } else { 336 quote!() 337 }; 338 339 Some(quote_spanned! { field.span()=> 340 let #app_var = #app_var.arg({ 341 #deprecations 342 343 #[allow(deprecated)] 344 let arg = clap::Arg::new(#id) 345 #implicit_methods; 346 347 let arg = arg 348 #explicit_methods; 349 350 let arg = arg 351 #override_methods; 352 353 arg 354 }); 355 }) 356 } 357 }; 358 args.push(genned); 359 } 360 361 let deprecations = if !override_required { 362 parent_item.deprecations() 363 } else { 364 quote!() 365 }; 366 let initial_app_methods = parent_item.initial_top_level_methods(); 367 let final_app_methods = parent_item.final_top_level_methods(); 368 let group_app_methods = if parent_item.skip_group() { 369 quote!() 370 } else { 371 let group_id = parent_item.ident().unraw().to_string(); 372 let literal_group_members = fields 373 .iter() 374 .filter_map(|(_field, item)| { 375 let kind = item.kind(); 376 if matches!(*kind, Kind::Arg(_)) { 377 Some(item.id()) 378 } else { 379 None 380 } 381 }) 382 .collect::<Vec<_>>(); 383 let literal_group_members_len = literal_group_members.len(); 384 let mut literal_group_members = quote! {{ 385 let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ]; 386 members 387 }}; 388 // HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in 389 // that situation 390 let possible_group_members_len = fields 391 .iter() 392 .filter(|(_field, item)| { 393 let kind = item.kind(); 394 matches!(*kind, Kind::Flatten(_)) 395 }) 396 .count(); 397 if 0 < possible_group_members_len { 398 literal_group_members = quote! {{ 399 let members: [clap::Id; 0] = []; 400 members 401 }}; 402 } 403 404 quote!( 405 .group( 406 clap::ArgGroup::new(#group_id) 407 .multiple(true) 408 .args(#literal_group_members) 409 ) 410 ) 411 }; 412 Ok(quote! {{ 413 #deprecations 414 let #app_var = #app_var 415 #initial_app_methods 416 #group_app_methods 417 ; 418 #( #args )* 419 #app_var #final_app_methods 420 }}) 421} 422 423pub fn gen_constructor(fields: &[(&Field, Item)]) -> Result<TokenStream, syn::Error> { 424 let fields = fields.iter().map(|(field, item)| { 425 let field_name = field.ident.as_ref().unwrap(); 426 let kind = item.kind(); 427 let arg_matches = format_ident!("__clap_arg_matches"); 428 let genned = match &*kind { 429 Kind::Command(_) 430 | Kind::Value 431 | Kind::ExternalSubcommand => { 432 abort! { kind.span(), 433 "`{}` cannot be used with `arg`", 434 kind.name(), 435 } 436 } 437 Kind::Subcommand(ty) => { 438 let subcmd_type = match (**ty, sub_type(&field.ty)) { 439 (Ty::Option, Some(sub_type)) => sub_type, 440 _ => &field.ty, 441 }; 442 match **ty { 443 Ty::Option => { 444 quote_spanned! { kind.span()=> 445 #field_name: { 446 if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) { 447 Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?) 448 } else { 449 None 450 } 451 } 452 } 453 }, 454 Ty::Other => { 455 quote_spanned! { kind.span()=> 456 #field_name: { 457 <#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)? 458 } 459 } 460 }, 461 Ty::Unit | 462 Ty::Vec | 463 Ty::OptionOption | 464 Ty::OptionVec | 465 Ty::VecVec | 466 Ty::OptionVecVec => { 467 abort!( 468 ty.span(), 469 "{} types are not supported for subcommand", 470 ty.as_str() 471 ); 472 } 473 } 474 } 475 476 Kind::Flatten(ty) => { 477 let inner_type = match (**ty, sub_type(&field.ty)) { 478 (Ty::Option, Some(sub_type)) => sub_type, 479 _ => &field.ty, 480 }; 481 match **ty { 482 Ty::Other => { 483 quote_spanned! { kind.span()=> 484 #field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)? 485 } 486 }, 487 Ty::Option => { 488 quote_spanned! { kind.span()=> 489 #field_name: { 490 let group_id = <#inner_type as clap::Args>::group_id() 491 .expect("`#[arg(flatten)]`ed field type implements `Args::group_id`"); 492 if #arg_matches.contains_id(group_id.as_str()) { 493 Some( 494 <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)? 495 ) 496 } else { 497 None 498 } 499 } 500 } 501 }, 502 Ty::Unit | 503 Ty::Vec | 504 Ty::OptionOption | 505 Ty::OptionVec | 506 Ty::VecVec | 507 Ty::OptionVecVec => { 508 abort!( 509 ty.span(), 510 "{} types are not supported for flatten", 511 ty.as_str() 512 ); 513 } 514 } 515 }, 516 517 Kind::Skip(val, _) => match val { 518 None => quote_spanned!(kind.span()=> #field_name: Default::default()), 519 Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()), 520 }, 521 522 Kind::Arg(ty) | Kind::FromGlobal(ty) => { 523 gen_parsers(item, ty, field_name, field, None)? 524 } 525 }; 526 Ok(genned) 527 }).collect::<Result<Vec<_>, syn::Error>>()?; 528 529 Ok(quote! {{ 530 #( #fields ),* 531 }}) 532} 533 534pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> Result<TokenStream, syn::Error> { 535 let mut genned_fields = Vec::new(); 536 for (field, item) in fields { 537 let field_name = field.ident.as_ref().unwrap(); 538 let kind = item.kind(); 539 540 let access = if use_self { 541 quote! { 542 #[allow(non_snake_case)] 543 let #field_name = &mut self.#field_name; 544 } 545 } else { 546 quote!() 547 }; 548 let arg_matches = format_ident!("__clap_arg_matches"); 549 550 let genned = match &*kind { 551 Kind::Command(_) | Kind::Value | Kind::ExternalSubcommand => { 552 abort! { kind.span(), 553 "`{}` cannot be used with `arg`", 554 kind.name(), 555 } 556 } 557 Kind::Subcommand(ty) => { 558 let subcmd_type = match (**ty, sub_type(&field.ty)) { 559 (Ty::Option, Some(sub_type)) => sub_type, 560 _ => &field.ty, 561 }; 562 563 let updater = quote_spanned! { ty.span()=> 564 <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?; 565 }; 566 567 let updater = match **ty { 568 Ty::Option => quote_spanned! { kind.span()=> 569 if let Some(#field_name) = #field_name.as_mut() { 570 #updater 571 } else { 572 *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut( 573 #arg_matches 574 )?); 575 } 576 }, 577 _ => quote_spanned! { kind.span()=> 578 #updater 579 }, 580 }; 581 582 quote_spanned! { kind.span()=> 583 { 584 #access 585 #updater 586 } 587 } 588 } 589 590 Kind::Flatten(ty) => { 591 let inner_type = match (**ty, sub_type(&field.ty)) { 592 (Ty::Option, Some(sub_type)) => sub_type, 593 _ => &field.ty, 594 }; 595 596 let updater = quote_spanned! { ty.span()=> 597 <#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?; 598 }; 599 600 let updater = match **ty { 601 Ty::Option => quote_spanned! { kind.span()=> 602 if let Some(#field_name) = #field_name.as_mut() { 603 #updater 604 } else { 605 *#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut( 606 #arg_matches 607 )?); 608 } 609 }, 610 _ => quote_spanned! { kind.span()=> 611 #updater 612 }, 613 }; 614 615 quote_spanned! { kind.span()=> 616 { 617 #access 618 #updater 619 } 620 } 621 } 622 623 Kind::Skip(_, _) => quote!(), 624 625 Kind::Arg(ty) | Kind::FromGlobal(ty) => { 626 gen_parsers(item, ty, field_name, field, Some(&access))? 627 } 628 }; 629 genned_fields.push(genned); 630 } 631 632 Ok(quote! { 633 #( #genned_fields )* 634 }) 635} 636 637fn gen_parsers( 638 item: &Item, 639 ty: &Sp<Ty>, 640 field_name: &Ident, 641 field: &Field, 642 update: Option<&TokenStream>, 643) -> Result<TokenStream, syn::Error> { 644 let span = ty.span(); 645 let convert_type = inner_type(&field.ty); 646 let id = item.id(); 647 let get_one = quote_spanned!(span=> remove_one::<#convert_type>); 648 let get_many = quote_spanned!(span=> remove_many::<#convert_type>); 649 let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>); 650 651 // Give this identifier the same hygiene 652 // as the `arg_matches` parameter definition. This 653 // allows us to refer to `arg_matches` within a `quote_spanned` block 654 let arg_matches = format_ident!("__clap_arg_matches"); 655 656 let field_value = match **ty { 657 Ty::Unit => { 658 quote_spanned! { ty.span()=> 659 () 660 } 661 } 662 663 Ty::Option => { 664 quote_spanned! { ty.span()=> 665 #arg_matches.#get_one(#id) 666 } 667 } 668 669 Ty::OptionOption => quote_spanned! { ty.span()=> 670 if #arg_matches.contains_id(#id) { 671 Some( 672 #arg_matches.#get_one(#id) 673 ) 674 } else { 675 None 676 } 677 }, 678 679 Ty::OptionVec => quote_spanned! { ty.span()=> 680 if #arg_matches.contains_id(#id) { 681 Some(#arg_matches.#get_many(#id) 682 .map(|v| v.collect::<Vec<_>>()) 683 .unwrap_or_else(Vec::new)) 684 } else { 685 None 686 } 687 }, 688 689 Ty::Vec => { 690 quote_spanned! { ty.span()=> 691 #arg_matches.#get_many(#id) 692 .map(|v| v.collect::<Vec<_>>()) 693 .unwrap_or_else(Vec::new) 694 } 695 } 696 697 Ty::VecVec => quote_spanned! { ty.span()=> 698 #arg_matches.#get_occurrences(#id) 699 .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>()) 700 .unwrap_or_else(Vec::new) 701 }, 702 703 Ty::OptionVecVec => quote_spanned! { ty.span()=> 704 #arg_matches.#get_occurrences(#id) 705 .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>()) 706 }, 707 708 Ty::Other => { 709 quote_spanned! { ty.span()=> 710 #arg_matches.#get_one(#id) 711 .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))? 712 } 713 } 714 }; 715 716 let genned = if let Some(access) = update { 717 quote_spanned! { field.span()=> 718 if #arg_matches.contains_id(#id) { 719 #access 720 *#field_name = #field_value 721 } 722 } 723 } else { 724 quote_spanned!(field.span()=> #field_name: #field_value ) 725 }; 726 Ok(genned) 727} 728 729#[cfg(feature = "raw-deprecated")] 730pub fn raw_deprecated() -> TokenStream { 731 quote! {} 732} 733 734#[cfg(not(feature = "raw-deprecated"))] 735pub fn raw_deprecated() -> TokenStream { 736 quote! { 737 #![allow(deprecated)] // Assuming any deprecation in here will be related to a deprecation in `Args` 738 739 } 740} 741