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 crate::utils; 16 17use clap::{Args, Parser, Subcommand}; 18 19#[derive(Parser, PartialEq, Eq, Debug)] 20enum Opt { 21 /// Fetch stuff from GitHub 22 Fetch { 23 #[arg(long)] 24 all: bool, 25 /// Overwrite local branches. 26 #[arg(short, long)] 27 force: bool, 28 29 repo: String, 30 }, 31 32 Add { 33 #[arg(short, long)] 34 interactive: bool, 35 #[arg(short, long)] 36 verbose: bool, 37 }, 38} 39 40#[test] 41fn test_fetch() { 42 assert_eq!( 43 Opt::Fetch { 44 all: true, 45 force: false, 46 repo: "origin".to_string() 47 }, 48 Opt::try_parse_from(["test", "fetch", "--all", "origin"]).unwrap() 49 ); 50 assert_eq!( 51 Opt::Fetch { 52 all: false, 53 force: true, 54 repo: "origin".to_string() 55 }, 56 Opt::try_parse_from(["test", "fetch", "-f", "origin"]).unwrap() 57 ); 58} 59 60#[test] 61fn test_add() { 62 assert_eq!( 63 Opt::Add { 64 interactive: false, 65 verbose: false 66 }, 67 Opt::try_parse_from(["test", "add"]).unwrap() 68 ); 69 assert_eq!( 70 Opt::Add { 71 interactive: true, 72 verbose: true 73 }, 74 Opt::try_parse_from(["test", "add", "-i", "-v"]).unwrap() 75 ); 76} 77 78#[test] 79fn test_no_parse() { 80 let result = Opt::try_parse_from(["test", "badcmd", "-i", "-v"]); 81 assert!(result.is_err()); 82 83 let result = Opt::try_parse_from(["test", "add", "--badoption"]); 84 assert!(result.is_err()); 85 86 let result = Opt::try_parse_from(["test"]); 87 assert!(result.is_err()); 88} 89 90#[derive(Parser, PartialEq, Eq, Debug)] 91enum Opt2 { 92 DoSomething { arg: String }, 93} 94 95#[test] 96/// This test is specifically to make sure that hyphenated subcommands get 97/// processed correctly. 98fn test_hyphenated_subcommands() { 99 assert_eq!( 100 Opt2::DoSomething { 101 arg: "blah".to_string() 102 }, 103 Opt2::try_parse_from(["test", "do-something", "blah"]).unwrap() 104 ); 105} 106 107#[derive(Parser, PartialEq, Eq, Debug)] 108enum Opt3 { 109 Add, 110 Init, 111 Fetch, 112} 113 114#[test] 115fn test_null_commands() { 116 assert_eq!(Opt3::Add, Opt3::try_parse_from(["test", "add"]).unwrap()); 117 assert_eq!(Opt3::Init, Opt3::try_parse_from(["test", "init"]).unwrap()); 118 assert_eq!( 119 Opt3::Fetch, 120 Opt3::try_parse_from(["test", "fetch"]).unwrap() 121 ); 122} 123 124#[derive(Parser, PartialEq, Eq, Debug)] 125#[command(about = "Not shown")] 126struct Add { 127 file: String, 128} 129/// Not shown 130#[derive(Parser, PartialEq, Eq, Debug)] 131struct Fetch { 132 remote: String, 133} 134#[derive(Parser, PartialEq, Eq, Debug)] 135enum Opt4 { 136 // Not shown 137 /// Add a file 138 Add(Add), 139 Init, 140 /// download history from remote 141 Fetch(Fetch), 142} 143 144#[test] 145fn test_tuple_commands() { 146 assert_eq!( 147 Opt4::Add(Add { 148 file: "f".to_string() 149 }), 150 Opt4::try_parse_from(["test", "add", "f"]).unwrap() 151 ); 152 assert_eq!(Opt4::Init, Opt4::try_parse_from(["test", "init"]).unwrap()); 153 assert_eq!( 154 Opt4::Fetch(Fetch { 155 remote: "origin".to_string() 156 }), 157 Opt4::try_parse_from(["test", "fetch", "origin"]).unwrap() 158 ); 159 160 let output = utils::get_long_help::<Opt4>(); 161 162 assert!(output.contains("download history from remote")); 163 assert!(output.contains("Add a file")); 164 assert!(!output.contains("Not shown")); 165} 166 167#[test] 168fn global_passed_down() { 169 #[derive(Debug, PartialEq, Eq, Parser)] 170 struct Opt { 171 #[arg(global = true, long)] 172 other: bool, 173 #[command(subcommand)] 174 sub: Subcommands, 175 } 176 177 #[derive(Debug, PartialEq, Eq, Subcommand)] 178 enum Subcommands { 179 Add, 180 Global(GlobalCmd), 181 } 182 183 #[derive(Debug, PartialEq, Eq, Args)] 184 struct GlobalCmd { 185 #[arg(from_global)] 186 other: bool, 187 } 188 189 assert_eq!( 190 Opt::try_parse_from(["test", "global"]).unwrap(), 191 Opt { 192 other: false, 193 sub: Subcommands::Global(GlobalCmd { other: false }) 194 } 195 ); 196 197 assert_eq!( 198 Opt::try_parse_from(["test", "global", "--other"]).unwrap(), 199 Opt { 200 other: true, 201 sub: Subcommands::Global(GlobalCmd { other: true }) 202 } 203 ); 204} 205 206#[test] 207fn external_subcommand() { 208 #[derive(Debug, PartialEq, Eq, Parser)] 209 struct Opt { 210 #[command(subcommand)] 211 sub: Subcommands, 212 } 213 214 #[derive(Debug, PartialEq, Eq, Subcommand)] 215 enum Subcommands { 216 Add, 217 Remove, 218 #[command(external_subcommand)] 219 Other(Vec<String>), 220 } 221 222 assert_eq!( 223 Opt::try_parse_from(["test", "add"]).unwrap(), 224 Opt { 225 sub: Subcommands::Add 226 } 227 ); 228 229 assert_eq!( 230 Opt::try_parse_from(["test", "remove"]).unwrap(), 231 Opt { 232 sub: Subcommands::Remove 233 } 234 ); 235 236 assert!(Opt::try_parse_from(["test"]).is_err()); 237 238 assert_eq!( 239 Opt::try_parse_from(["test", "git", "status"]).unwrap(), 240 Opt { 241 sub: Subcommands::Other(vec!["git".into(), "status".into()]) 242 } 243 ); 244} 245 246#[test] 247fn external_subcommand_os_string() { 248 use std::ffi::OsString; 249 250 #[derive(Debug, PartialEq, Eq, Parser)] 251 struct Opt { 252 #[command(subcommand)] 253 sub: Subcommands, 254 } 255 256 #[derive(Debug, PartialEq, Eq, Subcommand)] 257 enum Subcommands { 258 #[command(external_subcommand)] 259 Other(Vec<OsString>), 260 } 261 262 assert_eq!( 263 Opt::try_parse_from(["test", "git", "status"]).unwrap(), 264 Opt { 265 sub: Subcommands::Other(vec!["git".into(), "status".into()]) 266 } 267 ); 268 269 assert!(Opt::try_parse_from(["test"]).is_err()); 270} 271 272#[test] 273fn external_subcommand_optional() { 274 #[derive(Debug, PartialEq, Eq, Parser)] 275 struct Opt { 276 #[command(subcommand)] 277 sub: Option<Subcommands>, 278 } 279 280 #[derive(Debug, PartialEq, Eq, Subcommand)] 281 enum Subcommands { 282 #[command(external_subcommand)] 283 Other(Vec<String>), 284 } 285 286 assert_eq!( 287 Opt::try_parse_from(["test", "git", "status"]).unwrap(), 288 Opt { 289 sub: Some(Subcommands::Other(vec!["git".into(), "status".into()])) 290 } 291 ); 292 293 assert_eq!(Opt::try_parse_from(["test"]).unwrap(), Opt { sub: None }); 294} 295 296#[test] 297fn enum_in_enum_subsubcommand() { 298 #[derive(Parser, Debug, PartialEq, Eq)] 299 pub enum Opt { 300 #[command(alias = "l")] 301 List, 302 #[command(subcommand, alias = "d")] 303 Daemon(DaemonCommand), 304 } 305 306 #[derive(Subcommand, Debug, PartialEq, Eq)] 307 pub enum DaemonCommand { 308 Start, 309 Stop, 310 } 311 312 let result = Opt::try_parse_from(["test"]); 313 assert!(result.is_err()); 314 315 let result = Opt::try_parse_from(["test", "list"]).unwrap(); 316 assert_eq!(Opt::List, result); 317 318 let result = Opt::try_parse_from(["test", "l"]).unwrap(); 319 assert_eq!(Opt::List, result); 320 321 let result = Opt::try_parse_from(["test", "daemon"]); 322 assert!(result.is_err()); 323 324 let result = Opt::try_parse_from(["test", "daemon", "start"]).unwrap(); 325 assert_eq!(Opt::Daemon(DaemonCommand::Start), result); 326 327 let result = Opt::try_parse_from(["test", "d", "start"]).unwrap(); 328 assert_eq!(Opt::Daemon(DaemonCommand::Start), result); 329} 330 331#[test] 332fn update_subcommands() { 333 #[derive(Parser, PartialEq, Eq, Debug)] 334 enum Opt { 335 Command1(Command1), 336 Command2(Command2), 337 } 338 339 #[derive(Parser, PartialEq, Eq, Debug)] 340 struct Command1 { 341 arg1: i32, 342 343 arg2: i32, 344 } 345 346 #[derive(Parser, PartialEq, Eq, Debug)] 347 struct Command2 { 348 arg2: i32, 349 } 350 351 // Full subcommand update 352 let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); 353 opt.try_update_from(["test", "command1", "42", "44"]) 354 .unwrap(); 355 assert_eq!( 356 Opt::try_parse_from(["test", "command1", "42", "44"]).unwrap(), 357 opt 358 ); 359 360 // Partial subcommand update 361 let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); 362 opt.try_update_from(["test", "command1", "42"]).unwrap(); 363 assert_eq!( 364 Opt::try_parse_from(["test", "command1", "42", "14"]).unwrap(), 365 opt 366 ); 367 368 // Change subcommand 369 let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); 370 opt.try_update_from(["test", "command2", "43"]).unwrap(); 371 assert_eq!( 372 Opt::try_parse_from(["test", "command2", "43"]).unwrap(), 373 opt 374 ); 375} 376 377#[test] 378fn update_subcommands_explicit_required() { 379 #[derive(Parser, PartialEq, Eq, Debug)] 380 #[command(subcommand_required = true)] 381 enum Opt { 382 Command1(Command1), 383 Command2(Command2), 384 } 385 386 #[derive(Parser, PartialEq, Eq, Debug)] 387 struct Command1 { 388 arg1: i32, 389 390 arg2: i32, 391 } 392 393 #[derive(Parser, PartialEq, Eq, Debug)] 394 struct Command2 { 395 arg2: i32, 396 } 397 398 // Full subcommand update 399 let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); 400 opt.try_update_from(["test"]).unwrap(); 401 assert_eq!(Opt::Command1(Command1 { arg1: 12, arg2: 14 }), opt); 402} 403 404#[test] 405fn update_sub_subcommands() { 406 #[derive(Parser, PartialEq, Eq, Debug)] 407 enum Opt { 408 #[command(subcommand)] 409 Child1(Child1), 410 #[command(subcommand)] 411 Child2(Child2), 412 } 413 414 #[derive(Subcommand, PartialEq, Eq, Debug)] 415 enum Child1 { 416 Command1(Command1), 417 Command2(Command2), 418 } 419 420 #[derive(Subcommand, PartialEq, Eq, Debug)] 421 enum Child2 { 422 Command1(Command1), 423 Command2(Command2), 424 } 425 426 #[derive(Args, PartialEq, Eq, Debug)] 427 struct Command1 { 428 arg1: i32, 429 430 arg2: i32, 431 } 432 433 #[derive(Args, PartialEq, Eq, Debug)] 434 struct Command2 { 435 arg2: i32, 436 } 437 438 // Full subcommand update 439 let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); 440 opt.try_update_from(["test", "child1", "command1", "42", "44"]) 441 .unwrap(); 442 assert_eq!( 443 Opt::try_parse_from(["test", "child1", "command1", "42", "44"]).unwrap(), 444 opt 445 ); 446 447 // Partial subcommand update 448 let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); 449 opt.try_update_from(["test", "child1", "command1", "42"]) 450 .unwrap(); 451 assert_eq!( 452 Opt::try_parse_from(["test", "child1", "command1", "42", "14"]).unwrap(), 453 opt 454 ); 455 456 // Partial subcommand update 457 let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); 458 opt.try_update_from(["test", "child1", "command2", "43"]) 459 .unwrap(); 460 assert_eq!( 461 Opt::try_parse_from(["test", "child1", "command2", "43"]).unwrap(), 462 opt 463 ); 464 465 // Change subcommand 466 let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); 467 opt.try_update_from(["test", "child2", "command2", "43"]) 468 .unwrap(); 469 assert_eq!( 470 Opt::try_parse_from(["test", "child2", "command2", "43"]).unwrap(), 471 opt 472 ); 473} 474 475#[test] 476fn update_ext_subcommand() { 477 #[derive(Parser, PartialEq, Eq, Debug)] 478 enum Opt { 479 Command1(Command1), 480 Command2(Command2), 481 #[command(external_subcommand)] 482 Ext(Vec<String>), 483 } 484 485 #[derive(Args, PartialEq, Eq, Debug)] 486 struct Command1 { 487 arg1: i32, 488 489 arg2: i32, 490 } 491 492 #[derive(Args, PartialEq, Eq, Debug)] 493 struct Command2 { 494 arg2: i32, 495 } 496 497 // Full subcommand update 498 let mut opt = Opt::Ext(vec!["12".into(), "14".into()]); 499 opt.try_update_from(["test", "ext", "42", "44"]).unwrap(); 500 assert_eq!( 501 Opt::try_parse_from(["test", "ext", "42", "44"]).unwrap(), 502 opt 503 ); 504 505 // No partial subcommand update 506 let mut opt = Opt::Ext(vec!["12".into(), "14".into()]); 507 opt.try_update_from(["test", "ext", "42"]).unwrap(); 508 assert_eq!(Opt::try_parse_from(["test", "ext", "42"]).unwrap(), opt); 509 510 // Change subcommand 511 let mut opt = Opt::Ext(vec!["12".into(), "14".into()]); 512 opt.try_update_from(["test", "command2", "43"]).unwrap(); 513 assert_eq!( 514 Opt::try_parse_from(["test", "command2", "43"]).unwrap(), 515 opt 516 ); 517 518 let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); 519 opt.try_update_from(["test", "ext", "42", "44"]).unwrap(); 520 assert_eq!( 521 Opt::try_parse_from(["test", "ext", "42", "44"]).unwrap(), 522 opt 523 ); 524} 525#[test] 526fn subcommand_name_not_literal() { 527 fn get_name() -> &'static str { 528 "renamed" 529 } 530 531 #[derive(Parser, PartialEq, Eq, Debug)] 532 struct Opt { 533 #[command(subcommand)] 534 subcmd: SubCmd, 535 } 536 537 #[derive(Subcommand, PartialEq, Eq, Debug)] 538 enum SubCmd { 539 #[command(name = get_name())] 540 SubCmd1, 541 } 542 543 assert!(Opt::try_parse_from(["test", "renamed"]).is_ok()); 544} 545 546#[test] 547fn skip_subcommand() { 548 #[derive(Debug, PartialEq, Eq, Parser)] 549 struct Opt { 550 #[command(subcommand)] 551 sub: Subcommands, 552 } 553 554 #[derive(Debug, PartialEq, Eq, Subcommand)] 555 enum Subcommands { 556 Add, 557 Remove, 558 559 #[allow(dead_code)] 560 #[command(skip)] 561 Skip, 562 563 #[allow(dead_code)] 564 #[command(skip)] 565 Other(Other), 566 } 567 568 #[allow(dead_code)] 569 #[derive(Debug, PartialEq, Eq)] 570 enum Other { 571 One, 572 Twp, 573 } 574 575 assert!(Subcommands::has_subcommand("add")); 576 assert!(Subcommands::has_subcommand("remove")); 577 assert!(!Subcommands::has_subcommand("skip")); 578 assert!(!Subcommands::has_subcommand("other")); 579 580 assert_eq!( 581 Opt::try_parse_from(["test", "add"]).unwrap(), 582 Opt { 583 sub: Subcommands::Add 584 } 585 ); 586 587 assert_eq!( 588 Opt::try_parse_from(["test", "remove"]).unwrap(), 589 Opt { 590 sub: Subcommands::Remove 591 } 592 ); 593 594 let res = Opt::try_parse_from(["test", "skip"]); 595 assert_eq!( 596 res.unwrap_err().kind(), 597 clap::error::ErrorKind::InvalidSubcommand, 598 ); 599 600 let res = Opt::try_parse_from(["test", "other"]); 601 assert_eq!( 602 res.unwrap_err().kind(), 603 clap::error::ErrorKind::InvalidSubcommand, 604 ); 605} 606 607#[test] 608fn built_in_subcommand_escaped() { 609 #[derive(Debug, PartialEq, Eq, Parser)] 610 enum Command { 611 Install { 612 arg: Option<String>, 613 }, 614 #[command(external_subcommand)] 615 Custom(Vec<String>), 616 } 617 618 assert_eq!( 619 Command::try_parse_from(["test", "install", "arg"]).unwrap(), 620 Command::Install { 621 arg: Some(String::from("arg")) 622 } 623 ); 624 assert_eq!( 625 Command::try_parse_from(["test", "--", "install"]).unwrap(), 626 Command::Custom(vec![String::from("install")]) 627 ); 628 assert_eq!( 629 Command::try_parse_from(["test", "--", "install", "arg"]).unwrap(), 630 Command::Custom(vec![String::from("install"), String::from("arg")]) 631 ); 632} 633