mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 06:42:33 +00:00
07b6e66eb7
Documenting the existing behavior is challenging which suggests it can cause user confusion. So long as its not too hard to explicitly specify actions, we should just do it. Fixes #4057
471 lines
12 KiB
Rust
471 lines
12 KiB
Rust
use clap::{ArgAction, Args, CommandFactory, Parser, Subcommand};
|
|
|
|
#[test]
|
|
fn arg_help_heading_applied() {
|
|
#[derive(Debug, Clone, Parser)]
|
|
struct CliOptions {
|
|
#[clap(long)]
|
|
#[clap(help_heading = Some("HEADING A"))]
|
|
should_be_in_section_a: u32,
|
|
|
|
#[clap(long)]
|
|
no_section: u32,
|
|
}
|
|
|
|
let cmd = CliOptions::command();
|
|
|
|
let should_be_in_section_a = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_section_a")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A"));
|
|
|
|
let should_be_in_section_b = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "no_section")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_section_b.get_help_heading(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn app_help_heading_applied() {
|
|
#[derive(Debug, Clone, Parser)]
|
|
#[clap(next_help_heading = "DEFAULT")]
|
|
struct CliOptions {
|
|
#[clap(long)]
|
|
#[clap(help_heading = Some("HEADING A"))]
|
|
should_be_in_section_a: u32,
|
|
|
|
#[clap(long)]
|
|
should_be_in_default_section: u32,
|
|
}
|
|
|
|
let cmd = CliOptions::command();
|
|
|
|
let should_be_in_section_a = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_section_a")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A"));
|
|
|
|
let should_be_in_default_section = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_default_section")
|
|
.unwrap();
|
|
assert_eq!(
|
|
should_be_in_default_section.get_help_heading(),
|
|
Some("DEFAULT")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn app_help_heading_flattened() {
|
|
// Used to help track the cause in tests
|
|
#![allow(clippy::enum_variant_names)]
|
|
|
|
#[derive(Debug, Clone, Parser)]
|
|
struct CliOptions {
|
|
#[clap(flatten)]
|
|
options_a: OptionsA,
|
|
|
|
#[clap(flatten)]
|
|
options_b: OptionsB,
|
|
|
|
#[clap(subcommand)]
|
|
sub_a: SubA,
|
|
|
|
#[clap(long)]
|
|
should_be_in_default_section: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Args)]
|
|
#[clap(next_help_heading = "HEADING A")]
|
|
struct OptionsA {
|
|
#[clap(long)]
|
|
should_be_in_section_a: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Args)]
|
|
#[clap(next_help_heading = "HEADING B")]
|
|
struct OptionsB {
|
|
#[clap(long)]
|
|
should_be_in_section_b: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Subcommand)]
|
|
enum SubA {
|
|
#[clap(flatten)]
|
|
SubB(SubB),
|
|
#[clap(subcommand)]
|
|
SubC(SubC),
|
|
SubAOne,
|
|
#[clap(next_help_heading = "SUB A")]
|
|
SubATwo {
|
|
should_be_in_sub_a: u32,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone, Subcommand)]
|
|
enum SubB {
|
|
#[clap(next_help_heading = "SUB B")]
|
|
SubBOne { should_be_in_sub_b: u32 },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Subcommand)]
|
|
enum SubC {
|
|
#[clap(next_help_heading = "SUB C")]
|
|
SubCOne { should_be_in_sub_c: u32 },
|
|
}
|
|
|
|
let cmd = CliOptions::command();
|
|
|
|
let should_be_in_section_a = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_section_a")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A"));
|
|
|
|
let should_be_in_section_b = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_section_b")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_section_b.get_help_heading(), Some("HEADING B"));
|
|
|
|
let should_be_in_default_section = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_default_section")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_default_section.get_help_heading(), None);
|
|
|
|
let sub_a_two = cmd.find_subcommand("sub-a-two").unwrap();
|
|
|
|
let should_be_in_sub_a = sub_a_two
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_sub_a")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_sub_a.get_help_heading(), Some("SUB A"));
|
|
|
|
let sub_b_one = cmd.find_subcommand("sub-b-one").unwrap();
|
|
|
|
let should_be_in_sub_b = sub_b_one
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_sub_b")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_sub_b.get_help_heading(), Some("SUB B"));
|
|
|
|
let sub_c = cmd.find_subcommand("sub-c").unwrap();
|
|
let sub_c_one = sub_c.find_subcommand("sub-c-one").unwrap();
|
|
|
|
let should_be_in_sub_c = sub_c_one
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_sub_c")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_sub_c.get_help_heading(), Some("SUB C"));
|
|
}
|
|
|
|
#[test]
|
|
fn flatten_field_with_help_heading() {
|
|
#[derive(Debug, Clone, Parser)]
|
|
struct CliOptions {
|
|
#[clap(flatten)]
|
|
#[clap(next_help_heading = "HEADING A")]
|
|
options_a: OptionsA,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Args)]
|
|
struct OptionsA {
|
|
#[clap(long)]
|
|
should_be_in_section_a: u32,
|
|
}
|
|
|
|
let cmd = CliOptions::command();
|
|
|
|
let should_be_in_section_a = cmd
|
|
.get_arguments()
|
|
.find(|a| a.get_id() == "should_be_in_section_a")
|
|
.unwrap();
|
|
assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A"));
|
|
}
|
|
|
|
// The challenge with this test is creating an error situation not caught by `clap`'s error checking
|
|
// but by the code that `clap_derive` generates.
|
|
//
|
|
// Ultimately, the easiest way to confirm is to put a debug statement in the desired error path.
|
|
#[test]
|
|
fn derive_generated_error_has_full_context() {
|
|
#[derive(Debug, Parser)]
|
|
#[clap(subcommand_negates_reqs = true)]
|
|
struct Opts {
|
|
#[clap(long)]
|
|
req_str: String,
|
|
|
|
#[clap(subcommand)]
|
|
cmd: Option<SubCommands>,
|
|
}
|
|
|
|
#[derive(Debug, Parser)]
|
|
enum SubCommands {
|
|
Sub {
|
|
#[clap(short, long, action = clap::ArgAction::Count)]
|
|
verbose: u8,
|
|
},
|
|
}
|
|
|
|
let result = Opts::try_parse_from(&["test", "sub"]);
|
|
assert!(
|
|
result.is_err(),
|
|
"`SubcommandsNegateReqs` with non-optional `req_str` should fail: {:?}",
|
|
result.unwrap()
|
|
);
|
|
|
|
let expected = r#"error: The following required argument was not provided: req_str
|
|
|
|
USAGE:
|
|
clap --req-str <REQ_STR>
|
|
clap <SUBCOMMAND>
|
|
|
|
For more information try --help
|
|
"#;
|
|
assert_eq!(result.unwrap_err().to_string(), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn derive_order_next_order() {
|
|
static HELP: &str = "test 1.2
|
|
|
|
USAGE:
|
|
test [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--flag-b first flag
|
|
--option-b <OPTION_B> first option
|
|
-h, --help Print help information
|
|
-V, --version Print version information
|
|
--flag-a second flag
|
|
--option-a <OPTION_A> second option
|
|
";
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[clap(name = "test", version = "1.2")]
|
|
struct Args {
|
|
#[clap(flatten)]
|
|
a: A,
|
|
#[clap(flatten)]
|
|
b: B,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
#[clap(next_display_order = 10000)]
|
|
struct A {
|
|
/// second flag
|
|
#[clap(long)]
|
|
flag_a: bool,
|
|
/// second option
|
|
#[clap(long)]
|
|
option_a: Option<String>,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
#[clap(next_display_order = 10)]
|
|
struct B {
|
|
/// first flag
|
|
#[clap(long)]
|
|
flag_b: bool,
|
|
/// first option
|
|
#[clap(long)]
|
|
option_b: Option<String>,
|
|
}
|
|
|
|
use clap::CommandFactory;
|
|
let mut cmd = Args::command();
|
|
|
|
let mut buffer: Vec<u8> = Default::default();
|
|
cmd.write_help(&mut buffer).unwrap();
|
|
let help = String::from_utf8(buffer).unwrap();
|
|
snapbox::assert_eq(HELP, help);
|
|
}
|
|
|
|
#[test]
|
|
fn derive_order_next_order_flatten() {
|
|
static HELP: &str = "test 1.2
|
|
|
|
USAGE:
|
|
test [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--flag-b first flag
|
|
--option-b <OPTION_B> first option
|
|
-h, --help Print help information
|
|
-V, --version Print version information
|
|
--flag-a second flag
|
|
--option-a <OPTION_A> second option
|
|
";
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[clap(name = "test", version = "1.2")]
|
|
struct Args {
|
|
#[clap(flatten)]
|
|
#[clap(next_display_order = 10000)]
|
|
a: A,
|
|
#[clap(flatten)]
|
|
#[clap(next_display_order = 10)]
|
|
b: B,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
struct A {
|
|
/// second flag
|
|
#[clap(long)]
|
|
flag_a: bool,
|
|
/// second option
|
|
#[clap(long)]
|
|
option_a: Option<String>,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
struct B {
|
|
/// first flag
|
|
#[clap(long)]
|
|
flag_b: bool,
|
|
/// first option
|
|
#[clap(long)]
|
|
option_b: Option<String>,
|
|
}
|
|
|
|
use clap::CommandFactory;
|
|
let mut cmd = Args::command();
|
|
|
|
let mut buffer: Vec<u8> = Default::default();
|
|
cmd.write_help(&mut buffer).unwrap();
|
|
let help = String::from_utf8(buffer).unwrap();
|
|
snapbox::assert_eq(HELP, help);
|
|
}
|
|
|
|
#[test]
|
|
fn derive_order_no_next_order() {
|
|
static HELP: &str = "test 1.2
|
|
|
|
USAGE:
|
|
test [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--flag-a first flag
|
|
--flag-b second flag
|
|
-h, --help Print help information
|
|
--option-a <OPTION_A> first option
|
|
--option-b <OPTION_B> second option
|
|
-V, --version Print version information
|
|
";
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[clap(name = "test", version = "1.2")]
|
|
#[clap(next_display_order = None)]
|
|
struct Args {
|
|
#[clap(flatten)]
|
|
a: A,
|
|
#[clap(flatten)]
|
|
b: B,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
struct A {
|
|
/// first flag
|
|
#[clap(long)]
|
|
flag_a: bool,
|
|
/// first option
|
|
#[clap(long)]
|
|
option_a: Option<String>,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
struct B {
|
|
/// second flag
|
|
#[clap(long)]
|
|
flag_b: bool,
|
|
/// second option
|
|
#[clap(long)]
|
|
option_b: Option<String>,
|
|
}
|
|
|
|
use clap::CommandFactory;
|
|
let mut cmd = Args::command();
|
|
|
|
let mut buffer: Vec<u8> = Default::default();
|
|
cmd.write_help(&mut buffer).unwrap();
|
|
let help = String::from_utf8(buffer).unwrap();
|
|
snapbox::assert_eq(HELP, help);
|
|
}
|
|
|
|
#[test]
|
|
fn derive_possible_value_help() {
|
|
static HELP: &str = "clap
|
|
Application help
|
|
|
|
USAGE:
|
|
clap <ARG>
|
|
|
|
ARGS:
|
|
<ARG>
|
|
Argument help
|
|
|
|
Possible values:
|
|
- foo: Foo help
|
|
- bar: Bar help
|
|
|
|
OPTIONS:
|
|
-h, --help
|
|
Print help information
|
|
";
|
|
|
|
/// Application help
|
|
#[derive(Parser, PartialEq, Debug)]
|
|
struct Args {
|
|
/// Argument help
|
|
#[clap(value_enum)]
|
|
arg: ArgChoice,
|
|
}
|
|
|
|
#[derive(clap::ValueEnum, PartialEq, Debug, Clone)]
|
|
enum ArgChoice {
|
|
/// Foo help
|
|
Foo,
|
|
/// Bar help
|
|
Bar,
|
|
}
|
|
|
|
use clap::CommandFactory;
|
|
let mut cmd = Args::command();
|
|
|
|
let mut buffer: Vec<u8> = Default::default();
|
|
cmd.write_long_help(&mut buffer).unwrap();
|
|
let help = String::from_utf8(buffer).unwrap();
|
|
snapbox::assert_eq(HELP, help);
|
|
}
|
|
|
|
#[test]
|
|
fn custom_help_flag() {
|
|
#[derive(Debug, Clone, Parser)]
|
|
#[clap(disable_help_flag = true)]
|
|
struct CliOptions {
|
|
#[clap(short = 'h', long = "verbose-help", action = ArgAction::Help)]
|
|
help: bool,
|
|
}
|
|
|
|
let result = CliOptions::try_parse_from(["cmd", "--verbose-help"]);
|
|
let err = result.unwrap_err();
|
|
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
|
|
}
|
|
|
|
#[test]
|
|
fn custom_version_flag() {
|
|
#[derive(Debug, Clone, Parser)]
|
|
#[clap(disable_version_flag = true, version = "2.0.0")]
|
|
struct CliOptions {
|
|
#[clap(short = 'V', long = "verbose-version", action = ArgAction::Version)]
|
|
version: bool,
|
|
}
|
|
|
|
let result = CliOptions::try_parse_from(["cmd", "--verbose-version"]);
|
|
let err = result.unwrap_err();
|
|
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
|
|
}
|